Как работает декоратор @property?
Я хотел бы понять, как работает встроенная функция 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
аргументы getx
, setx
, delx
и строка doc.
В приведенном ниже коде 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
?
Я смущен.
Ответы
Ответ 1
Функция property()
возвращает специальный объект дескриптора descriptor object:
>>> 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)
поэтому foo
функция заменена на property(foo)
, который мы видели выше, это специальный объект. Затем, когда вы используете @foo.setter()
, вы вызываете метод property().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: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
Дескриптор Howto включает пример реализации чистого Python типа property()
:
class Property:
"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__)
Ответ 2
Документация говорит, что это просто ярлык для создания свойств readonly. Так
@property
def x(self):
return self._x
эквивалентно
def getx(self):
return self._x
x = property(getx)
Ответ 3
Вот минимальный пример того, как @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'
Ответ 4
Первая часть проста:
@property
def x(self): ...
совпадает с
def x(self): ...
x = property(x)
- который, в свою очередь, является упрощенным синтаксисом для создания
property
только с помощью getter.
Следующим шагом будет расширение этого свойства с помощью setter и deleter. И это происходит с помощью соответствующих методов:
@x.setter
def x(self, value): ...
возвращает новое свойство, которое наследует все от старого x
плюс заданный сеттер.
x.deleter
работает одинаково.
Ответ 5
Это следующее:
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
Ответ 6
Ниже приведен еще один пример того, как @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
и 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.
Вы можете использовать @property
также в абстрактных классах; Я приведу минимальный пример здесь.
Ответ 7
Я прочитал все посты здесь и понял, что нам может понадобиться реальный пример из жизни. Почему, собственно, у нас есть @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
при попытке доступа к нему напрямую, в то время как мы использовали @property.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')
(на случай, если вам интересно, но это другая форма, связанная с колбами).
Надеюсь этот пример будет полезен
Ответ 8
Эта точка зрения была прояснена многими людьми там, но вот прямая точка, которую я искал. Это то, что я считаю важным начать с декоратора @property. например:-
class UtilityMixin():
@property
def get_config(self):
return "This is property"
Вызов функции "get_config()" будет работать следующим образом.
util = UtilityMixin()
print(util.get_config)
Если вы заметили, я не использовал скобки "()" для вызова функции. Это основная вещь, которую я искал для @property decorator. Так что вы можете использовать свою функцию как переменную.
Ответ 9
Начнем с Python-декораторов.
Декоратор 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()
Пусть закончится все, чем с кейсом, но до этого давай поговорим о некоторых людях.
Геттеры и сеттеры используются во многих объектно-ориентированных языках программирования для обеспечения принципа инкапсуляции данных (рассматривается как связывание данных с методами, которые оперируют этими данными).
Эти методы, конечно, являются средством получения данных и средством изменения данных.
Согласно этому принципу атрибуты класса делаются закрытыми, чтобы скрыть их и защитить от другого кода.
Да, @property - это по сути питонский способ использования геттеров и сеттеров.
В Python есть замечательная концепция, называемая свойством, которая значительно упрощает жизнь объектно-ориентированного программиста.
Допустим, вы решили создать класс, который мог бы хранить температуру в градусах Цельсия.
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() - это встроенная функция, которая создает и возвращает объект свойства.
У объекта свойства есть три метода: 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_tength остается свойством вместо метода.
Теперь вы можете получить доступ к значению температуры, написав.
C = Celsius()
C.temperature
# instead of writing C.get_temperature()
Мы можем продолжить и не определять имена get_teuration и set_tempera, поскольку они не нужны и загрязняют пространство имен класса.
питонический способ решения вышеуказанной проблемы - это использование @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 температура (self)" и "def температура (self, x)".
Как видите, код определенно менее элегантен.
Теперь давайте поговорим об одном реальном практическом сценарии.
Допустим, вы разработали класс следующим образом:
class OurClass:
def __init__(self, a):
self.x = a
y = OurClass(10)
print(y.x)
Теперь давайте предположим, что наш класс стал популярным среди клиентов, и они начали использовать его в своих программах. Они выполняли все виды назначений для объекта.
И в один роковой день к нам пришел доверенный клиент и предположил, что значение "х" должно быть в диапазоне от 0 до 1000, это действительно ужасный сценарий!
Благодаря свойствам это легко: мы создаем версию свойства "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
Это здорово, не правда ли: вы можете начать с самой простой реализации, какой только можно себе представить, и вы можете позже перейти на версию свойства без необходимости менять интерфейс! Так что свойства - это не просто замена геттерам и сеттерам!
Вы можете проверить эту реализацию здесь
Ответ 10
property
является классом для декоратора @property
.
Вы всегда можете проверить это:
print(property) #<class 'property'>
Я переписал пример из help(property)
, чтобы показать, что синтаксис @property
class C:
def __init__(self):
self._x=None
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
c = C()
c.x="a"
print(c.x)
функционально идентичен синтаксису property()
:
class C:
def __init__(self):
self._x=None
def g(self):
return self._x
def s(self, v):
self._x = v
def d(self):
del self._x
prop = property(g,s,d)
c = C()
c.x="a"
print(c.x)
Как мы видим, разницы в том, как мы используем собственность, нет.
Чтобы ответить на вопрос, декоратор @property
реализован с помощью класса property
.
Итак, вопрос в том, чтобы немного объяснить класс property
.
Эта строка:
prop = property(g,s,d)
Была инициализация. Мы можем переписать это так:
prop = property(fget=g,fset=s,fdel=d)
Значение fget
, fset
и fdel
:
| 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
На следующем изображении показаны триплеты из класса property
:
![enter image description here]()
__get__
, __set__
и __delete__
должны быть переопределены overridden. Это реализация шаблона дескриптора в Python.
В общем, дескриптор - это атрибут объекта с "поведением привязки", тот, чей доступ к атрибуту был переопределен методами в протоколе дескриптора.
Мы также можем использовать методы свойств setter
, getter
и deleter
для привязки функции к свойству. Проверьте следующий пример. Метод s2
класса C
установит свойство удвоенное.
class C:
def __init__(self):
self._x=None
def g(self):
return self._x
def s(self, x):
self._x = x
def d(self):
del self._x
def s2(self,x):
self._x=x+x
x=property(g)
x=x.setter(s)
x=x.deleter(d)
c = C()
c.x="a"
print(c.x) # outputs "a"
C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"
Ответ 11
Свойство может быть объявлено двумя способами.
- Создание методов getter, setter для атрибута, а затем передача их в качестве аргумента функции свойство
- Использование декоратора @property.
Вы можете взглянуть на несколько примеров, которые я написал о свойствах в python.
Ответ 12
Вот еще один пример:
##
## 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
и если я прокомментирую self.x = 1234 в 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
Примечание. Логика getter состоит только в том, чтобы управлять этим значением, чтобы гарантировать, что с ним манипулирует - то же самое для операторов печати...
Примечание. Я привык к Lua и могу динамически создавать помощники 10+, когда я вызываю одну функцию, и я сделал что-то подобное для Python без использования свойств, и это работает в определенной степени, но, несмотря на то, что функции создаются перед тем, как их использовать, по-прежнему возникают проблемы, когда они вызываются до создания, что странно, поскольку оно не кодируется таким образом... Я предпочитаю гибкость мета-таблиц Lua и факт, что я могу использовать фактические сеттеры /getters вместо того, чтобы напрямую обращаться к переменной... Мне нравится, как быстро некоторые вещи могут быть созданы с Python, хотя, например, в программах gui. хотя я проектирую, возможно, не удастся без большого количества дополнительных библиотек - если я его закодирую в AutoHotkey, я могу напрямую обращаться к вызовам DLL, которые мне нужны, и то же самое можно сделать в Java, С#, C++ и других - возможно, я еще не нашел нужную вещь, но для этого проекта я могу переключиться с Python..
Примечание: вывод кода на этом форуме нарушен - мне пришлось добавить пробелы в первую часть кода для его работы - когда копирование/вставка гарантируют, что вы конвертируете все пробелы в вкладки.... Я использую вкладки для Python, потому что в файл, который составляет 10 000 строк, размер файла может быть от 512 КБ до 1 МБ с пробелами и от 100 до 200 КБ с вкладками, которые приравниваются к огромной разнице в размере файла и сокращению времени обработки...
Вкладки можно также настраивать для каждого пользователя - поэтому, если вы предпочитаете ширину 2 пробела, 4, 8 или все, что вы можете это сделать, это задумывается для разработчиков с дефицитом зрения.
Примечание. Все функции, определенные в классе, не имеют отступов должным образом из-за ошибки в программном обеспечении форума - убедитесь, что вы отступали, если вы копируете/вставляете
Ответ 13
@property - это декоратор, который преобразует метод в свойство (также называемый атрибутом).
Ответ 14
Одно замечание: для меня, для Python 2.x, @property
не работал так, как было объявлено, когда я не наследовал от object
:
class A():
pass
но работал когда:
class A(object):
pass
для Python 3 работал всегда.