Как работает @ Property decorator?
я хотел бы понять, как встроенная функция property
строительство. Что меня смущает, так это ... --2--> также может использоваться в качестве декоратора, но он принимает аргументы только при использовании в качестве встроенной функции, а не при использовании в качестве декоратора.
этот пример из документация:
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.")
С getx
, setx
, delx
и строка doc.
в коде ниже property
используется в качестве оформителя. Этот объект его -x
function, но в приведенном выше коде нет места для объектной функции в аргументах.
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)
так foo
функция заменяется property(foo)
, который мы видели выше, является особым объектом. Затем, когда вы используете @foo.setter()
, то, что вы делаете, называется это property().setter
метод, который я показал вам выше, возвращает новую копию свойства, но на этот раз с функцией setter, замененной на decorated метод.
следующая последовательность также создает свойство full-on, используя те методы декоратора.
Сначала мы создаем некоторые функции и 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 с .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, где вы используете систему аутентификации.
Вы объявляете модель User 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-это функция, которая помогает добавить некоторые дополнительные функции к уже определенной функции.
в 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
здесь мы можем сказать, что функция декоратора изменила нашу функцию 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 в основном pythonic способ использования геттеров и сеттеров.
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, собственность ()-это встроенная функция, которая создает и возвращает объект.
объект свойства имеет три метода, 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 как они ненужные и загрязняют пространство имен класса.
на подходящие для Python способ для решения вышеуказанной проблемы необходимо использовать @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.сеттер", если бы функция называлась "x" , нам пришлось бы украсить ее "@х.сеттер".
- мы написали" два " метода с тем же именем и различным количеством параметров "температура def(self)" и "температура def(self,x)".
как вы можете видеть, код, безусловно, менее изящный.
теперь давайте поговорим об одном реальном практическом scenerio.
предположим, вы разработали класс следующим образом:
class OurClass:
def __init__(self, a):
self.x = a
y = OurClass(10)
print(y.x)
теперь давайте предположим, что наш класс стал популярным среди клиентов, и они начав использовать его в своих программах, они выполняли всевозможные задания объекту.
и в один судьбоносный день доверенный клиент пришел к нам и предположил, что" 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
это здорово, не так ли: вы можете начать с самой простой реализации, которую только можно себе представить, и вы можете позже перейти на свойство версия без необходимости менять интерфейс! Таким образом, свойства-это не просто замена геттеров и сеттеров!
вы можете проверить эту реализацию здесь
свойство может быть объявлено двумя способами.
- создание методов 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 в 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 строк, размер файла может быть от 512KB до 1MB с пробелами и от 100 до 200KB с вкладками, что эквивалентно огромной разнице в размере файла и сокращению времени обработки...
вкладки также можно настроить для каждого пользователя-так что если вы предпочитаете ширину 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
и 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.