Как работает декоратор @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

Указывает на примечание -

  1. Метод, который используется для получения значения, отмечен как "@property".
  2. Метод, который должен функционировать как установщик, украшен символом "@temperature.setter". Если бы функция называлась "x", мы должны были бы украсить его как "@x.setter".
  3. Мы написали "два" метода с одним и тем же именем и разным количеством параметров "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 работал всегда.