关于python,@property decorator是如何工作的?

How does the @property decorator work?

我想了解一下内置函数property是如何工作的。令我困惑的是,property也可以用作修饰器,但它只在用作内置函数时接受参数,而不在用作修饰器时接受参数。

此示例来自文档:

1
2
3
4
5
6
7
8
9
10
11
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的参数是getxsetxdelx和doc字符串。

在下面的代码中,property用作修饰器。它的对象是x函数,但在上面的代码中,在参数中没有对象函数的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.setterx.deleter装饰师是如何创造的?我很困惑。


property()函数返回一个特殊的描述符对象:

1
2
>>> property()
<property object at 0x10ff07940>

正是这个对象有额外的方法:

1
2
3
4
5
6
>>> 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>

它们也起装饰作用。它们返回一个新的属性对象:

1
2
>>> property().getter(None)
<property object at 0x10ff079f0>

这是旧对象的副本,但替换了其中一个函数。

记住,@decorator语法只是语法上的糖分;语法:

1
2
@property
def foo(self): return self._foo

真正的意义和

1
2
def foo(self): return self._foo
foo = property(foo)

因此,foo函数被property(foo)所取代,我们在上面看到这是一个特殊的对象。然后,当您使用@foo.setter()时,您所做的是调用我在上面向您展示的property().setter方法,它返回属性的新副本,但这次用修饰方法替换了setter函数。

下面的序列还使用这些修饰器方法创建了一个完全打开属性。

首先,我们创建一些函数和一个只有getter的property对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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()方法添加一个setter:

1
2
3
4
5
6
7
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

最后我们用.deleter()方法添加了一个删除程序:

1
2
3
4
5
6
7
>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

最后但并非最不重要的是,property对象充当描述符对象,因此它有.__get__().__set__().__delete__()方法来钩住实例属性的获取、设置和删除:

1
2
3
4
5
6
7
8
>>> class Foo(object): pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

描述符howto包含property()类型的纯python示例实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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__)


文档说它只是创建只读属性的快捷方式。所以

1
2
3
@property
def x(self):
    return self._x

等于

1
2
3
def getx(self):
    return self._x
x = property(getx)


第一部分很简单:

1
2
@property
def x(self): ...

是一样的

1
2
def x(self): ...
x = property(x)
  • 反过来,它是只使用getter创建property的简化语法。

下一步是使用setter和deleter扩展此属性。这是通过适当的方法实现的:

1
2
@x.setter
def x(self, value): ...

返回一个新属性,该属性继承旧x加上给定的setter的所有内容。

x.deleter的工作原理相同。


下面是如何实现@property的最小示例:

1
2
3
4
5
6
7
8
9
class Thing:
    def __init__(self, my_word):
        self._word = my_word
    @property
    def word(self):
        return self._word

>>> print( Thing('ok').word )
'ok'

否则,word仍然是方法而不是属性。

1
2
3
4
5
6
7
8
class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'


以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.")

相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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如何帮助您(我只在下面进行总结):

假设您创建了一个类Money,如下所示:

1
2
3
4
class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

用户根据他/她使用的这个类创建一个库,例如

1
2
3
4
money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

现在让我们假设您决定更改Money类,并去掉dollarscents属性,但决定只跟踪美分总数:

1
2
3
class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

如果上述用户现在试图像以前一样运行其库

1
2
3
money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

它会导致一个错误

AttributeError: 'Money' object has no attribute 'dollars'

这意味着现在每个依赖于您原来的Money类的人都必须更改所有使用dollarscents的代码行,这可能会非常痛苦……那么,这怎么能避免呢?使用@property

就是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

当我们现在从图书馆打电话时

1
2
3
4
money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

它将如预期的那样工作,我们不需要在我们的库中更改一行代码!事实上,我们甚至不必知道我们所依赖的图书馆发生了变化。

同时,setter也很好地工作:

1
2
3
4
5
6
7
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?所以,考虑使用认证系统的flask应用程序。您在models.py中声明模型用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)

在这段代码中,我们使用@property来"隐藏"属性password,当您试图直接访问它时,它会触发AttributeError断言,而我们使用@property.setter来设置实际的实例变量password_hash

现在在auth/views.py中,我们可以用以下方法实例化用户:

1
2
3
4
5
6
7
8
9
10
11
...
@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')进行(如果您不知道,但它是与主题相关的另一种烧瓶形式)。

我希望这个例子有用


这一点已被许多人澄清,但这里有一个直接点,我正在搜索。我觉得从@property decorator开始这一点很重要。例如:

1
2
3
4
class UtilityMixin():
    @property
    def get_config(self):
        return"This is property"

函数"get_config()"的调用将像这样工作。

1
2
util = UtilityMixin()
print(util.get_config)

如果您注意到我没有使用"()"括号来调用函数。这是我搜索@property decorator的基本内容。这样就可以像变量一样使用函数。


让我们从Python装饰器开始。

python修饰器是一个帮助向已经定义的函数添加一些附加功能的函数。

在python中,一切都是一个对象,在python中,一切都是一个对象。python中的函数是第一类对象,这意味着它们可以被变量引用、添加到列表中、作为参数传递给另一个函数等。

考虑下面的代码段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 ou hello函数并在其中添加了一些额外的代码行。

decorator的python语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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。

许多面向对象编程语言中都使用getter和setter来确保数据封装的原则(被视为数据与对这些数据进行操作的方法的组合)。

这些方法当然是检索数据的getter和更改数据的setter。

根据这个原则,类的属性被设置为私有的,以隐藏和保护它们不受其他代码的影响。

是的,@property基本上是一种使用getter和setter的Python式方法。

python有一个伟大的概念,叫做属性,它使面向对象的程序员的生活简单得多。

让我们假设您决定创建一个以摄氏度为单位存储温度的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

在这里,

1
temperature = property(get_temperature,set_temperature)

可能被分解为,

1
2
3
4
5
6
# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

注意事项:

  • get_temperature保持属性而不是方法。

现在你可以通过写来获取温度值。

1
2
3
C = Celsius()
C.temperature
# instead of writing C.get_temperature()

我们可以继续,而不定义名称get-temperature和set-temperature,因为它们是不必要的,会污染类名称空间。

解决上述问题的方法是使用@property。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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"修饰。
  • 必须作为setter函数的方法是用"@temperature.setter"修饰的,如果函数被称为"x",我们就必须用"@x.setter"修饰它。
  • 我们用相同的名称和不同数量的参数"def温度(self)"和"def温度(self,x)"编写了"两个"方法。
  • 正如您所看到的,代码绝对不那么优雅。

    现在,我们来谈谈现实生活中的一个场景。

    假设您设计了一个类,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    class OurClass:

        def __init__(self, a):
            self.x = a


    y = OurClass(10)
    print(y.x)

    现在,让我们进一步假设我们的类在客户中变得流行,并且他们开始在他们的程序中使用它,他们对对象做了各种分配。

    有一天,一位值得信赖的客户来到我们这里,建议"x"的值必须在0到1000之间,这真是一个可怕的场景!

    由于属性的原因,这很容易:我们创建了一个属性版本"x"。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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的替代品!

    您可以在这里检查这个实现


    属性可以用两种方式声明。

    • 为属性创建getter、setter方法,然后将这些方法作为参数传递给属性函数
    • 使用@property decorator。

    您可以看看我在python中编写的几个关于属性的示例。


    下面是另一个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    ##
    ## 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 '
    '


    ##
    ##
    ##
    _test = GetterSetterExample( )
    print( _test )

    ## For some reason the deleter isn't being called...
    del _test.x

    基本上,与c(object)示例相同,只是我使用x来代替…我也不会在初始化中初始化…好。。是的,但是它可以被删除,因为uux被定义为类的一部分……

    输出是:

    1
    2
    3
    [ Test Class ] Set x = 1234
    [ Test Class ] Get x = -1234
    [ x ] -1234

    如果我在init中注释掉self.x=1234,那么输出是:

    1
    2
    [ Test Class ] Get x = None
    [ x ] None

    如果我在getter函数中将默认值=none设置为默认值=0(因为所有getter都应该有一个默认值,但它不是由我看到的属性值传入的,所以您可以在这里定义它,实际上这并不坏,因为您可以定义一次默认值并在任何地方使用它),即:def x(self,_default=0):

    1
    2
    [ Test Class ] Get x = 0
    [ x ] 0

    注意:getter逻辑只是让值被它操纵,以确保它被它操纵-对于print语句也是如此…

    注意:我习惯于Lua,当我调用单个函数时可以动态地创建10多个助手,并且我在不使用属性的情况下为python创建了类似的东西,并且它在一定程度上起作用,但是,即使在使用之前已经创建了这些函数,但在创建之前调用它们时仍然存在一些问题,这就是范围,因为它没有以这种方式编码…我更喜欢Lua元表的灵活性,我可以使用实际的setter/getter,而不是直接访问变量……我很喜欢用python构建一些东西的速度,比如gui程序。虽然我设计一个不可能没有很多额外的图书馆-如果我在AutoHotkey编码,我可以直接访问我需要的DLL调用,同样可以在爪哇,C,C,和更多-也许我还没有找到正确的东西,但对于这个项目,我可以从Python切换。

    注意:这个论坛中的代码输出是中断的-我必须在代码的第一部分添加空格,以便它工作-复制/粘贴时,请确保将所有空格转换为制表符…我在python中使用制表符,因为在一个10000行的文件中,文件大小可以是512KB到1MB(含空格),而制表符可以是100到200KB(含制表符),这相当于文件大小的巨大差异,并且减少了处理时间…

    标签也可以调整每个用户-因此,如果你喜欢2个空间宽度,4, 8或任何你可以做的,这意味着它是有思想的开发有视力缺陷。

    注意:在论坛中定义的所有函数不会因为论坛软件中的错误而被正确地缩进——如果您复制/粘贴,请确保您缩进它。


    一句话:对于我来说,对于python 2.x,@property在我没有继承表单对象时没有像广告中那样工作:

    1
    2
    class A():
        pass

    但工作时间:

    1
    2
    class A(object):
        pass

    对于Pyton 3,一直都在工作