Давайте начнем с синтаксиса "@decorator". Это на самом деле только синтаксический сахар, так что
@decorate
def myfunc():
pass
это просто сокращение для
def myfunc():
pass
myfunc = decorate(myfunc)
Обратите внимание, что функции python также являются объектами (а также классами и модулями FWIW), поэтому вы можете передавать функции в качестве аргументов другим функциям, возвращать функции из функций, сохранять функции в качестве переменных или атрибутов и т. Д.
Теперь с классом property
(да, это класс): это всего лишь общая реализация протокола descriptor
, который представляет собой механизм python для поддержки вычисляемых атрибутов.
Наивная реализация Python property
будет выглядеть примерно так (я игнорирую части fdel
и __del__
):
class propertytype(type):
# this is what will allow you
# to use `property` as decorator,
# it will return a new `property` instance
# with `func` as setter
def __call__(cls, func):
return cls(func)
class property(metaclass=propertytype):
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
# this is the getter
def __get__(self, instance, cls=None):
if instance is None:
return self
return self.fget(instance)
# this is the setter (if there's one)
def __set__(self, instance, value):
if not self.fset:
raise AttributeError("Attribute is read-only")
self.fset(instance, value)
# and this allows you to use`@myprop.setter`
# in your class definition
def setter(self, func):
self.fset = func
return self
И наконец: хотя рекомендуется создавать все атрибуты экземпляра объекта в инициализаторе (метод __init__
), вы можете установить существующие или новые атрибуты просто в любом месте и в любое время. За исключением нескольких типов, которые (в основном из соображений реализации) используют совершенно другой способ хранения атрибутов (вы можете посмотреть slots
, если хотите узнать больше об этом), обычные объекты Python, в основном, скрыты, поэтому myobj.foo = 'bar'
обычно просто хранит 'bar'
в self.__dict__['foo']
. Ну, конечно, если вы не используете вычисляемые атрибуты;)
Хорошо, теперь у нас есть строительные блоки, давайте проанализируем, что происходит с вашим классом:
class P:
# let's ignore the initializer for now
@property
def x(self):
return self.__x
@x.setter
def x(self, x):
if x < 0:
self.__x = 0
else:
self.__x = x
Это можно переписать как
class P:
# let's ignore the initializer for now
def _getx(self):
return self.__x
def _setx(self):
if x < 0:
self.__x = 0
else:
self.__x = x
x = property(_getx, setx)
Так что теперь с
p = P()
когда мы делаем:
p.x = 5
правила разрешения атрибутов (реализованные в object.__setattr__(self, name, value)
) фактически будут искать «x» на «P», находить наше свойство «x», и, поскольку это дескриптор привязки (у него есть метод __set__
), вызывать x.__set__(p, 5)
, который, в свою очередь, будет вызывать self.fset(p, 5)
(ср. Определение property.__set__()
), который будет вызывать p._setx(5)
.
А если мы вернули инициализатор:
class P:
def __init__(self, x):
self.x = x
# getter / setter / property definition here
затем происходит очень точное событие (за исключением того, что экземпляр P
называется self
вместо p
) - в действительности он вызывает P._setx(self, x)
.
Единственное отличие от вашей первоначальной реализации заключается в том, что при использовании property
имеется декоратор, функции получения и установки не становятся методами класса, они только живут как атрибуты fget
и fset
x
объект недвижимости.