Как Python различает явно переданный None в качестве аргумента во встроенных функциях

Я экспериментировал со следующим кодом:

>>> f = object()

# It's obvious behavior:
>>> f.foo
Traceback (most recent call last):       
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'foo'

# However, the next one is surprising me!
>>> getattr(f, 'foo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'foo'

# And this one returns None as expected:
>>> getattr(f, 'foo', None)

Затем я нашел эту псевдо-подпись getattr() в PyCharm IDE:

def getattr(object, name, default=None): # known special case of getattr
    """
    getattr(object, name[, default]) -> value

    Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
    When a default argument is given, it is returned when the attribute doesn't
    exist; without it, an exception is raised in that case.
    """
    pass

Мой вопрос заключается в том, как python различает эти два сценария внутреннего использования getattr() (и, возможно, других функций)? И можно ли сделать что-то подобное полностью в коде на стороне клиента?


person Ivan Velichko    schedule 10.09.2015    source источник


Ответы (5)


Как сказал @scytale, псевдоподпись getattr не совсем соответствует ее реализации. Я видел попытки воспроизвести поведение в чистом Python, которые выглядят примерно так:

class MyObject(object):
    __marker = object()

    def getvalue(key, default=__marker):
        ...
        if key is __marker:
             # no value supplied for default
             ....

Другими словами, используйте значение маркера, которое вызывающая сторона не может легко предоставить, чтобы проверить, не задано ли какое-либо значение по умолчанию, а не None.

person ig0774    schedule 10.09.2015
comment
За исключением того, что getattr может принимать любой аргумент по умолчанию, включая ваш MyObject.__marker, и отличать его от случая, когда третий параметр отсутствует. Следовательно, ваш ответ не показывает, как getattr различает два случая (да, он может использовать скрытый объект, который нельзя получить со стороны Python, но это не так), а также не может показать, как вы могли бы сделать это в клиентском коде . - person skyking; 10.09.2015
comment
@skyking: я никогда не говорил, что это идеальное воспроизведение поведения. Однако этого достаточно для большинства случаев использования. То есть, я считал, как я могу добиться подобного результата, более важным, чем то, как это работает под капотом. - person ig0774; 10.09.2015
comment
Я бы предпочел использовать __marker = object(), так как он использует экземпляр объекта, а не тип объекта. - person Marcin Pietraszek; 23.02.2017
comment
@MarcinPietraszek: упс! Это то, что я намеревался! - person ig0774; 23.02.2017

getattr реализован на C, поэтому то, как это делается, немного отличается от того, как это делается на python. В C есть несколько соглашений о вызовах, getattr использует то, что называется METH_VARARG, что означает, что он ожидает неопределенных числовых позиционных аргументов, передаваемых как кортеж, затем функция проверяет, что это либо кортеж длины 2, либо 3 (через PyArg_UnpackTuple) и действует соответственно (при распаковке аргумент по умолчанию, если он опущен, будет указателем NULL, который отличается от любого объекта python).

Это похоже на то, что можно сделать в питоне:

def mygetattr(*args):
    if len(args) != 2 and len(args) != 3:
          raise Exception
    try:
        return getattr(args[0], args[1])
    except AttributeError:
        if len(args) == 3:
            return args[2]
        raise

Но обычно в python можно было бы явно указать обязательные параметры, а затем использовать *args для обработки необязательных параметров (например, def mygetattr(obj, key, *args):...)

person skyking    schedule 10.09.2015

getattr является встроенным - поэтому он реализован на C - псевдо-подпись не является точным руководством к тому, как это работает.

person scytale    schedule 10.09.2015
comment
Я знаю, но это не ответ на мой вопрос. Я спросил о том, как это реализовано под капотом и как я могу эмулировать такое поведение в клиентском коде. - person Ivan Velichko; 10.09.2015

Я бы пошел в том же направлении, что и @skyking, но также разрешил бы использовать именованные аргументы следующим образом:

def getattr(object, name, *args, **kwargs):
    if len(args) == 1:
        # third positional argument is used as default
        default = args[1]
    elif 'default' in kwargs and len(kwargs) == 1:
        # provided as named argument
        default = kwargs['default']
    elif len(kwargs) > 0 or len(args) > 0:
        # unknown arguments
    else:
        # no 'default' was supplied

В приведенном выше коде вы можете вставить код обработки и исключения, которые будут вызываться, где это уместно!

person Mahdi    schedule 29.11.2016

getattr docs говорит

getattr(объект, имя[, по умолчанию])

Возвращает значение именованного атрибута объекта. имя должно быть строкой. Если строка является именем одного из атрибутов объекта, результатом будет значение этого атрибута. Например, getattr(x, 'foobar') эквивалентен x.foobar. Если именованный атрибут не существует, возвращается значение по умолчанию, если оно предоставлено, в противном случае возникает ошибка AttributeError.

Если вы хотите реализовать то же самое, попробуйте это

def mygetattr(obj, attr, default):
    ret_val = defaule
    try:
        ret_val = getattr(obj, attr)
    except AttributeError:
        pass

    return ret_val
person Nilesh    schedule 10.09.2015
comment
Вопрос был о том, как по-разному обрабатывать случай, когда аргумент default опущен. Ваш пример этого не делает. - person interjay; 10.09.2015