"Почему магический закрытый атрибут делают здесь, чтобы программа
не сталкивается с бесконечным циклом "
На самом деле это не очень хорошее место, чтобы использовать двойное подчеркивание имен. Мне нравится этот урок, за исключением одной детали. Вы можете использовать одно подчеркивание или любой допустимый идентификатор питона , кроме , который занял свойство, и вы увидите тот же эффект.
A property
- это объект, который реализует протокол дескриптора . Это удобный дескриптор для общего случая использования дескриптора. Но мы можем создавать собственные типы дескрипторов.
По сути, дескриптор - это любой тип Python, который реализует любую комбинацию __get__
, __set__
или __delete__
.
Они будут вызваны, когда вы выполните some_object.some_attribute
, some_object.some_attribute = value
и del some_object.some_attribute
, где some_attribute
- дескриптор some_object.__class__
.
Итак, рассмотрим конкретный пример:
>>> class Foo:
... def __get__(self, obj, objtype):
... print('inside Foo.__get__')
... return 42
...
>>> class Bar:
... foo = Foo()
...
>>> bar = Bar()
>>> bar.foo
inside Foo.__get__
42
Дескрипторы перехватывать атрибут доступа и модификации, а также удаление в экземпляре класса, который имеет дескриптор в качестве атрибута , чтобы разрешить все виды забавных вещей.
Обратите внимание, дескриптор принадлежит классу:
>>> vars(bar)
{}
>>> vars(Bar)
mappingproxy({'__module__': '__main__', 'foo': <__main__.Foo object at 0x1025272e8>, '__dict__': <attribute '__dict__' of 'Bar' objects>, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None})
Если я установлю атрибут экземпляра с тем же именем, что и у атрибута класса, содержащего свойство, происходит нормальное поведение затенения в python:
>>> bar.foo = 99
>>> bar.foo
99
>>> vars(bar)
{'foo': 99}
Но мы можем контролировать это, мы можем реализовать __set__
:
>>> class Foo:
... def __get__(self, obj, objtype):
... return 42
... def __set__(self, obj, val):
... print('nah-ah-ah')
...
...
>>> class Bar:
... foo = Foo()
...
>>> bar = Bar()
>>> bar.foo
42
>>> bar.foo = 99
nah-ah-ah
>>> bar.foo
42
Объект property
позволяет вам предоставлять функции, которые будут делегированы при использовании property.__get__
, property.__set__
и property.__delete__
. Строки документации довольно информативны, просто используйте help(property)
в оболочке Python:
class property(object)
| property(fget=None, fset=None, fdel=None, doc=None)
|
| Property attribute.
|
| fget
| function to be used for getting an attribute value
| fset
| function to be used for setting an attribute value
| fdel
| function to be used for del'ing an attribute
| doc
| docstring
|
| Typical use is to define a managed attribute x:
|
| class C(object):
| def getx(self): return self._x
| def setx(self, value): self._x = value
| def delx(self): del self._x
| x = property(getx, setx, delx, "I'm the 'x' property.")
|
| Decorators make defining new properties or modifying existing ones easy:
|
| class C(object):
| @property
| def x(self):
| "I am the 'x' property."
| return self._x
| @x.setter
| def x(self, value):
| self._x = value
| @x.deleter
| def x(self):
| del self._x
Итак, что бы вы ни украшали с помощью @property.setter
, вы можете представить, что оно передано property(fset=<whatever>)
. Так что теперь, когда ваш экземпляр пытается установить x.some_attribute = value
, где .some_attribute
- это свойство на class X:
, вызывается property.__set__
, вы можете представить, что оно переходит x.some_attribute = value
в X.some_attribute.__set__(x, value)
Итак, чтобы понять суть вашего вопроса , почему бесконечная рекурсия, потому что использование obj.x = val
, где .x
является свойством , вызовет fset
, но в вашем fset вы используете obj.x = val
, и fset
снова вызывается , и вот ваша скрытая рекурсия.
Синтаксис @decorator
предназначен для удобства и всегда сначала принимает getter , но вы можете просто предоставить только установщик, используя длинную форму:
>>> class Weird:
... def setx(self, value):
... self._x = value
... x = property(fset=setx)
...
>>> w = Weird()
>>> w.x = 'foo'
>>> w.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: unreadable attribute
Я настоятельно рекомендую прочитать дескриптор HOWTO . Оповещение спойлера, classmethod
и staticmethod
- все это дескрипторы, и поэтому Python магически передает экземпляры в методы (то есть все функциональные объекты являются дескрипторами, а метод __get__
передает экземпляр в качестве первого аргумента в сама функция, когда к ней обращается экземпляр класса! . Она также показывает реализации Python всех этих вещей, включая то, как вы могли бы реализовать property
в чистом Python!