Путаница о __get__ и __call__ в python
См. простой пример ниже:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
def __call__(self):
print('__call__ called')
class Temperature(object):
celsius = Celsius()
def __init__(self):
self.celsius1 = Celsius()
T = Temperature()
print('T.celsius:', T.celsius)
print('T.celsius1:', T.celsius1)
output
T.celsius: 0.0
T.celsius1: <__main__.Celsius object at 0x023544F0>
Интересно, почему у них разные результаты.
Я знаю, что T.celsius
вызовет вызовы __get__
и T.celsius1
__call__
.
Ответы
Ответ 1
Различия заключаются в том, что первым атрибутом является атрибут класса, а второй - атрибут экземпляра.
По документация, Если объект, реализующий хотя бы первый из методов Descriptor
(__get__
, __set__
и __delete__
) сохраняется в атрибуте класса, при обращении к нему будет вызываться метод __get__
. Это не относится к атрибуту экземпляра. Вы можете узнать больше из инструкции.
Метод объекта __call__
объекта вступает в игру только тогда, когда объект вызывается как функция:
>>> class Foo:
... def __call__(self):
... return "Hello there!"
...
>>> f = Foo()
>>> f()
'Hello There!'
Ответ 2
Из документация:
Следующие методы применяются только тогда, когда экземпляр класса содержащий метод (так называемый класс дескриптора), появляется в класс владельца (дескриптор должен быть либо в классе владельцев словарь или в словаре классов для одного из его родителей).
Таким образом, дескрипторы (т.е. объекты, реализующие __get__
, __set__
или __delete__
) должны быть членами класса, а не экземпляром.
При следующих изменениях:
Temperature.celsius2 = Celsius()
T = Temperature()
print('T.celsius:', T.celsius)
print('T.celsius1:', T.celsius1)
print('T.celsius2:', T.celsius2)
Вывод:
T.celsius: 0.0
T.celsius1: <__main__.Celsius object at 0x7fab8c8d0fd0>
T.celsius2:, 0.0
Другие ссылки:
Ответ 3
T.celcius
как атрибут класса Temperature
, поэтому он возвращает результат метода __get__
T.celcius, как и ожидалось.
T.celsius1
является атрибутом экземпляра T
, поэтому он возвращает переменную, так как дескрипторы вызываются только для новых объектов или классов стиля.
Метод __call__
будет использоваться, если вы должны сделать T.celsius()
.
Ответ 4
Если вы попробуете это, вы увидите тот же результат.
print('T.celsius:', T.celsius)
print('T.celsius1:', T.celsius1.value)
Какова цель самоуправления?
Ответ 5
Как уже отмечалось, экземпляры дескрипторов предназначены для использования в качестве атрибутов класса.
class Temperature(object):
celsius = Celsius()
def __init__(self):
self.celsius1 = Celsius()
Если вы хотите, чтобы self.celsius1
имел пользовательское поведение, переопределите __getattr__
метод:
class Temperature(object):
celsius = Celsius()
def __getattr__(self, name):
if name == 'celsius1':
return ...