Как работает @ property decorator?
Я хотел бы понять, как работает встроенная функция property
. Меня смущает то, что property
также может использоваться в качестве декоратора, но он принимает аргументы только при использовании в качестве встроенной функции, а не при использовании в качестве декоратора.
Этот пример взят из документации :
class C(object):
def __init__(self):
self._x = None
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.")
property
'аргументы s таковы getx
, setx
, delx
и док-струна.
В приведенном ниже коде property
используется в качестве декоратора. Объектом его является функция x
, но в коде выше нет места для объектной функции в аргументах.
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
И, как создаются декораторы x.setter
и x.deleter
?
Я в замешательстве.
10 ответов:
Функция
property()
возвращает специальный объект дескриптора :>>> property() <property object at 0x10ff07940>
Именно этот объект имеет дополнительные методы:
>>> property().getter <built-in method getter of property object at 0x10ff07998> >>> property().setter <built-in method setter of property object at 0x10ff07940> >>> property().deleter <built-in method deleter of property object at 0x10ff07998>
Они также действуют как декораторы . Они возвращают новый объект свойства:
>>> property().getter(None) <property object at 0x10ff079f0>
, который является копией старого объекта, но с одной из функций, заменить.
Помните, что синтаксис
@decorator
- это просто синтаксический сахар; синтаксис:@property def foo(self): return self._foo
На самом деле означает то же самое, что и
def foo(self): return self._foo foo = property(foo)
Итак Функция заменяется на
Следующая последовательность также создает полное свойство, используя эти методы декоратора.property(foo)
, которая, как мы видели выше, является специальным объектом. Затем, когда вы используете@foo.setter()
, то, что вы делаете, это вызываете методproperty().setter
, который я показал вам выше, который возвращает новую копию свойства, но на этот раз с функцией setter, замененной оформленным методом.Сначала мы создаем некоторые функции и объект
property
с помощью только геттер:>>> def getter(self): print 'Get!' ... >>> def setter(self, value): print 'Set to {!r}!'.format(value) ... >>> def deleter(self): print 'Delete!' ... >>> prop = property(getter) >>> prop.fget is getter True >>> prop.fset is None True >>> prop.fdel is None True
Далее мы используем метод
.setter()
для добавления сеттера:>>> prop = prop.setter(setter) >>> prop.fget is getter True >>> prop.fset is setter True >>> prop.fdel is None True
Наконец, мы добавляем делетер с помощью метода
.deleter()
:И последнее, но не менее важное: объект>>> prop = prop.deleter(deleter) >>> prop.fget is getter True >>> prop.fset is setter True >>> prop.fdel is deleter True
property
действует как объект дескриптора , поэтому он имеет.__get__()
,.__set__()
и еще.__delete__()
методы для подключения к получению, установке и удалению атрибута экземпляра:>>> class Foo(object): pass ... >>> prop.__get__(Foo(), Foo) Get! >>> prop.__set__(Foo(), 'bar') Set to 'bar'! >>> prop.__delete__(Foo()) Delete!
Дескриптор Howto включает в себя чистый пример реализации python из
property()
тип:class Property(object): "Emulate PyProperty_Type() in Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("unreadable attribute") return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("can't delete attribute") self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)
Документация говорит, что это просто ярлык для создания свойств только для чтения. Итак
@property def x(self): return self._x
Эквивалентно
def getx(self): return self._x x = property(getx)
Первая часть проста:
@property def x(self): ...
То же самое, что
def x(self): ... x = property(x)
Следующим шагом будет расширение этого свойства с помощью сеттера и делетера. И это происходит с соответствующими методами:
- , который, в свою очередь, является упрощенным синтаксисом для создания
property
с помощью только геттера.@x.setter def x(self, value): ...
Возвращает новое свойство, которое наследует все от старого
x
плюс заданный сеттер.
x.deleter
работает точно так же.
Вот минимальный пример того, как
@property
может быть реализован:class Thing: def __init__(self, my_word): self._word = my_word @property def word(self): return self._word >>> print( Thing('ok').word ) 'ok'
В противном случае
word
остается методом, а не свойством.class Thing: def __init__(self, my_word): self._word = my_word def word(self): return self._word >>> print( Thing('ok').word() ) 'ok'
Это следующее:
class C(object): def __init__(self): self._x = None @property def x(self): """I'm the 'x' property.""" return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x
Это то же самое, что:
class C(object): def __init__(self): self._x = None def _x_get(self): return self._x def _x_set(self, value): self._x = value def _x_del(self): del self._x x = property(_x_get, _x_set, _x_del, "I'm the 'x' property.")
Это то же самое, что:
class C(object): def __init__(self): self._x = None def _x_get(self): return self._x def _x_set(self, value): self._x = value def _x_del(self): del self._x x = property(_x_get, doc="I'm the 'x' property.") x = x.setter(_x_set) x = x.deleter(_x_del)
Это то же самое, что:
class C(object): def __init__(self): self._x = None def _x_get(self): return self._x x = property(_x_get, doc="I'm the 'x' property.") def _x_set(self, value): self._x = value x = x.setter(_x_set) def _x_del(self): del self._x x = x.deleter(_x_del)
Что то же самое, что:
class C(object): def __init__(self): self._x = None @property def x(self): """I'm the 'x' property.""" return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x
Я прочитал все сообщения здесь и понял, что нам может понадобиться реальный жизненный пример, почему, собственно, у нас есть @ property? Итак, рассмотрим приложение Flask, в котором используется система аутентификации. Пользователь модели объявляется в
models.py
:class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(64), unique=True, index=True) username = db.Column(db.String(64), unique=True, index=True) password_hash = db.Column(db.String(128)) ... @property def password(self): raise AttributeError('password is not a readable attribute') @password.setter def password(self, password): self.password_hash = generate_password_hash(password) def verify_password(self, password): return check_password_hash(self.password_hash, password)
В этом коде мы "скрыли" атрибут
password
с помощью@property
, который запускает утверждениеAttributeError
, Когда вы пытаетесь получить к нему прямой доступ, в то время как мы использовали свойство@.setter для установки фактической переменной экземпляраpassword_hash
.Теперь в
auth/views.py
мы можем создать экземпляр пользователя с:... @auth.route('/register', methods=['GET', 'POST']) def register(): form = RegisterForm() if form.validate_on_submit(): user = User(email=form.email.data, username=form.username.data, password=form.password.data) db.session.add(user) db.session.commit() ...
Атрибут уведомления
password
, который приходит из регистрационной формы, когда пользователь заполняет форму. Подтверждение пароля происходит на переднем конце с помощьюEqualTo('password', message='Passwords must match')
(в случае, если вам интересно, но это другая тема, связанная с формами Колб).Я надеюсь, что этот пример будет полезен
Давайте начнем с декораторов Python.
Python decorator-это функция, которая помогает добавить некоторые дополнительные функции к уже определенной функции.
В Python все является объектом, в Python все является объектом. Функции в Python являются объектами первого класса, что означает, что на них можно ссылаться с помощью переменной, добавлять в списки, передавать в качестве аргументов другой функции и т. д.
Рассмотрим следующий фрагмент кода.
def decorator_func(fun): def wrapper_func(): print("Wrapper function started") fun() print("Given function decorated") # Wrapper function add something to the passed function and decorator # returns the wrapper function return wrapper_func def say_bye(): print("bye!!") say_bye = decorator_func(say_bye) say_bye() # Output: # Wrapper function started # bye # Given function decorated
Здесь, можно сказать, что функция decorator модифицировала нашу функцию say_hello и добавила в нее несколько дополнительных строк кода.
Синтаксис Python для декоратора
def decorator_func(fun): def wrapper_func(): print("Wrapper function started") fun() print("Given function decorated") # Wrapper function add something to the passed function and decorator # returns the wrapper function return wrapper_func @decorator_func def say_bye(): print("bye!!") say_bye()
Давайте закончим все, чем с прецедентным сценарием, но перед этим поговорим о некоторых oops priniciples.
Геттеры и сеттеры используются во многих объектно-ориентированных языках программирования для обеспечения принципа инкапсуляции данных(рассматривается как связывание данных с методами, которые работают на них данные.)
Эти методы, конечно, являются геттером для извлечения данных и сеттером для изменения данных. В соответствии с этим принципом атрибуты класса становятся частными, чтобы скрыть и защитить их от другого кода.Да, @свойством - это в основном для Python способ, чтобы использовать геттеры и сеттеры.
У Python есть замечательная концепция под названием property, которая значительно упрощает жизнь объектно-ориентированного программиста.Пусть предположим, что вы решили сделать класс, который мог бы хранить температуру в градусах Цельсия.
class Celsius: def __init__(self, temperature = 0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 def get_temperature(self): return self._temperature def set_temperature(self, value): if value < -273: raise ValueError("Temperature below -273 is not possible") self._temperature = value
Рефакторингу кода, вот как мы могли бы достичь его собственность.
В Python property () - это встроенная функция, которая создает и возвращает объект property.
Объект свойства имеет три метода: getter(), setter () и delete().class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 def get_temperature(self): print("Getting value") return self.temperature def set_temperature(self, value): if value < -273: raise ValueError("Temperature below -273 is not possible") print("Setting value") self.temperature = value temperature = property(get_temperature,set_temperature)
Здесь,
temperature = property(get_temperature,set_temperature)
Можно было бы разбить Как,
# make empty property temperature = property() # assign fget temperature = temperature.getter(get_temperature) # assign fset temperature = temperature.setter(set_temperature)
Укажите На Примечание:
- get_temperature остается свойством, а не методом.
Теперь вы можете получить доступ к значению температуры, написав.
C = Celsius() C.temperature # instead of writing C.get_temperature()
Мы можем продолжить и не определять имена get_temperature и set_temperature, поскольку они не нужны и загрязняют пространство имен класса.
Питонский способ справиться с вышеуказанной проблемой заключается в использовании@property .
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 @property def temperature(self): print("Getting value") return self.temperature @temperature.setter def temperature(self, value): if value < -273: raise ValueError("Temperature below -273 is not possible") print("Setting value") self.temperature = value
Примечания -
- метод, который используется для получения значения, украшен символом "@ property".
- метод, который должен функционировать как сеттер, украшен " @ temperature.setter", если бы функция называлась "x", мы должны были бы украсить ее"@x.setter".
Мы написали "два" метода с одинаковым названием и разным числом параметров "def temperature(self)" и "def temperature(self,x)".Как вы можете видеть, код определенно меньше элегантный.
Теперь давайте поговорим об одном реальном практическом сценарио. Предположим, вы разработали класс следующим образом:Теперь давайте предположим, что наш класс стал популярным среди клиентов, и они начали использовать его в своих программах, они выполняли все виды назначений объекту. И в один роковой день, доверенный клиент пришел к нам и предположил, что" x " должно быть значением между 0 и 1000, это действительно ужасный сценарий!class OurClass: def __init__(self, a): self.x = a y = OurClass(10) print(y.x)
Из-за свойства это просто: мы создаем версию свойства "x".
class OurClass: def __init__(self,x): self.x = x @property def x(self): return self.__x @x.setter def x(self, x): if x < 0: self.__x = 0 elif x > 1000: self.__x = 1000 else: self.__x = x
Это здорово, не правда ли: вы можете начать с самой простой реализации, которую только можно себе представить, и вы можете позже перейти на версию свойства без необходимости изменять интерфейс! Таким образом, свойства - это не просто замена геттеров и сеттеров!
Вы можете проверить эту реализацию здесь
Свойство может быть объявлено двумя способами.
- создание методов getter, setter для атрибута, а затем передача их в качестве аргумента в свойство функция
- с помощью декоратора @property .
Вы можете посмотреть на несколько примеров, которые я написал о свойствах в python.
Вот еще один пример:
## ## Python Properties Example ## class GetterSetterExample( object ): ## Set the default value for x ( we reference it using self.x, set a value using self.x = value ) __x = None ## ## On Class Initialization - do something... if we want.. ## def __init__( self ): ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set... self.x = 1234 return None ## ## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used.. ## @property def x( self, _default = None ): ## I added an optional default value argument as all getters should have this - set it to the default value you want to return... _value = ( self.__x, _default )[ self.__x == None ] ## Debugging - so you can see the order the calls are made... print( '[ Test Class ] Get x = ' + str( _value ) ) ## Return the value - we are a getter afterall... return _value ## ## Define the setter function for x... ## @x.setter def x( self, _value = None ): ## Debugging - so you can see the order the calls are made... print( '[ Test Class ] Set x = ' + str( _value ) ) ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway ) if ( _value > 0 ): self.__x = -_value else: self.__x = _value ## ## Define the deleter function for x... ## @x.deleter def x( self ): ## Unload the assignment / data for x if ( self.__x != None ): del self.__x ## ## To String / Output Function for the class - this will show the property value for each property we add... ## def __str__( self ): ## Output the x property data... print( '[ x ] ' + str( self.x ) ) ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used.... return '\n' ## ## ## _test = GetterSetterExample( ) print( _test ) ## For some reason the deleter isn't being called... del _test.x
В основном, то же самое, что и в Примере C( object), за исключением того, что я использую x вместо этого... Я также не инициализирую в _ _ init - ... хорошо.. Да, но его можно удалить, потому что __x определяется как часть класса....
Вывод:
[ Test Class ] Set x = 1234 [ Test Class ] Get x = -1234 [ x ] -1234
И если я комментирую себя.x = 1234 in init тогда выход:
[ Test Class ] Get x = None [ x ] None
И если я установлю _default = None в _default = 0 в функции getter ( как и все геттеры должны есть значение по умолчанию, но оно не передается значениями свойств из того, что я видел, поэтому вы можете определить его здесь, и это на самом деле не плохо, потому что вы можете определить значение по умолчанию один раз и использовать его везде) ie: def x( self, _default = 0 ):
[ Test Class ] Get x = 0 [ x ] 0
Примечание: логика геттера существует только для того, чтобы он манипулировал значением, чтобы гарантировать, что оно манипулируется им - то же самое для операторов печати...
Примечание: я привык к Lua и могу динамически создавать 10 + помощников, когда я звоню одна функция, и я сделал что-то подобное для Python без использования свойств, и это работает до некоторой степени, но, хотя функции создаются до их использования, иногда возникают проблемы с их вызовом до создания, что странно, поскольку это не кодируется таким образом... Я предпочитаю гибкость мета-таблиц Lua и тот факт, что я могу использовать фактические сеттеры / геттеры вместо того, чтобы по существу напрямую обращаться к переменной... Мне нравится, как быстро некоторые вещи могут быть построены. с Python, хотя-например, gui-программы. хотя одна из них, которую я разрабатываю, может быть невозможна без множества дополнительных библиотек - если я закодирую ее в AutoHotkey, я могу напрямую обращаться к вызовам dll, которые мне нужны, и то же самое можно сделать в Java, C#, C++ и т. д. - Возможно, я еще не нашел правильную вещь, но для этого проекта я могу переключиться с Python..
Примечание: вывод кода на этом форуме нарушен - мне пришлось добавить пробелы в первую часть кода, чтобы он работал - при копировании / вставке убедитесь, что вы преобразовали все пробелы в табуляции.... Я использую вкладки для Python, потому что в файле, который составляет 10 000 строк, размер файла может быть от 512 Кб до 1 МБ с пробелами и от 100 до 200 кб с вкладками, что означает огромную разницу в размере файла и сокращение времени обработки...
Вкладки также можно настроить для каждого пользователя - так что если вы предпочитаете ширину 2 пробелов, 4, 8 или что-то еще, что вы можете сделать, это означает, что это продуманно для разработчиков с дефицитом зрения.
Примечание: все функции, определенные в класс не отступает должным образом из-за ошибки в программном обеспечении форума-убедитесь, что вы отступаете, если вы копируете / вставляете
Ниже приведен еще один пример того, как
@property
может помочь, когда нужно рефакторировать код, который взят из здесь (я только суммирую его ниже):Представьте, что вы создали класс
Money
Вот так:class Money: def __init__(self, dollars, cents): self.dollars = dollars self.cents = cents
И пользователь создает библиотеку в зависимости от этого класса, где он / она использует, например,
Теперь предположим, что вы решили изменить свой классmoney = Money(27, 12) print("I have {} dollar and {} cents.".format(money.dollars, money.cents)) # prints I have 27 dollar and 12 cents.
Money
и избавиться от атрибутовdollars
иcents
, но вместо этого решили отслеживать только общее количество копеек:class Money: def __init__(self, dollars, cents): self.total_cents = dollars * 100 + cents
Если вышеупомянутый пользователь теперь пытается запустить свою библиотеку, как и раньше
money = Money(27, 12) print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
Это приведет к ошибке
Это означает, что теперь каждый, кто полагается на ваш исходный классAttributeError: объект 'Money' не имеет атрибута 'dollars'
Money
, должен будет изменить все строки кода, в которых используютсяdollars
иcents
, что может быть очень болезненно... Так как же этого избежать? С помощью@property
!То есть как:
class Money: def __init__(self, dollars, cents): self.total_cents = dollars * 100 + cents # Getter and setter for dollars... @property def dollars(self): return self.total_cents // 100 @dollars.setter def dollars(self, new_dollars): self.total_cents = 100 * new_dollars + self.cents # And the getter and setter for cents. @property def cents(self): return self.total_cents % 100 @cents.setter def cents(self, new_cents): self.total_cents = 100 * self.dollars + new_cents
Когда мы теперь зовем из нашей библиотеки
money = Money(27, 12) print("I have {} dollar and {} cents.".format(money.dollars, money.cents)) # prints I have 27 dollar and 12 cents.
Он будет работать, как и ожидалось, и нам не пришлось менять ни одной строки кода в нашей библиотеке! На самом деле, нам даже не нужно было бы знать, что библиотека, от которой мы зависим, изменилась.
Также прекрасно работает
setter
:money.dollars += 2 print("I have {} dollar and {} cents.".format(money.dollars, money.cents)) # prints I have 29 dollar and 12 cents. money.cents += 10 print("I have {} dollar and {} cents.".format(money.dollars, money.cents)) # prints I have 29 dollar and 22 cents.