Python “属性(property)”详解

    xiaoxiao2021-04-16  65

    Python中有一个被称为属性函数(property)的小概念,它可以做一些有用的事情。在这篇文章中,我们将看到如何能做以下几点:

    将类方法转换为只读属性重新实现一个属性的setter和getter方法

    在本文中,您将学习如何以几种不同的方式来使用内置的属性函数。希望读到文章的末尾时,你能看到它是多么有用。

    开始

    使用属性函数的最简单的方法之一是将它作为一个方法的装饰器来使用。这可以让你将一个类方法转变成一个类属性。当我需要做某些值的合并时,我发现这很有用。其他想要获取它作为方法使用的人,发现在写转换函数时它很有用。让我们来看一个简单的例子:

    Python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ######################################################################## class Person ( object ) :      """"""        #----------------------------------------------------------------------      def __init__ ( self , first_name , last_name ) :          """Constructor"""          self . first_name = first_name          self . last_name = last_name        #----------------------------------------------------------------------      @ property      def full_name ( self ) :          """         Return the full name         """          return "%s %s" % ( self . first_name , self . last_name )

    在上面的代码中,我们创建了两个类属性:self.first_nameself.last_name。接下来,我们创建了一个full_name方法,它有一个@property装饰器。这使我们能够在Python解释器会话中有如下的交互:

    Python 1 2 3 4 5 6 7 8 9 >>> person = Person ( "Mike" , "Driscoll" ) >>> person . full _name 'Mike Driscoll' >>> person . first _name 'Mike' >>> person . full_name = "Jackalope" Traceback ( most recent call last ) :    File "<string>" , line 1 , in < fragment > AttributeError : can' t set attribute

    正如你所看到的,因为我们将方法变成了属性,我们可以使用正常的点符号访问它。但是,如果我们试图将该属性设为其他值,我们会引发一个AttributeError错误。改变full_name属性的唯一方法是间接这样做:

    Python 1 2 3 >>> person . first_name = "Dan" >>> person . full _name 'Dan Driscoll'

    这是一种限制,因此让我们来看看另一个例子,其中我们可以创建一个允许设置的属性。

    使用Python property取代setter和getter方法

    让我们假设我们有一些遗留代码,它们是由一些对Python理解得不够好的人写的。如果你像我一样,你之前已经看到过这类的代码:

    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 from decimal import Decimal   ######################################################################## class Fees ( object ) :      """"""        #----------------------------------------------------------------------      def __init__ ( self ) :          """Constructor"""          self . _fee = None        #----------------------------------------------------------------------      def get_fee ( self ) :          """         Return the current fee         """          return self . _fee        #----------------------------------------------------------------------      def set_fee ( self , value ) :          """         Set the fee         """          if isinstance ( value , str ) :              self . _fee = Decimal ( value )          elif isinstance ( value , Decimal ) :              self . _fee = value

    要使用这个类,我们必须要使用定义的getter和setter方法​​:

    Python 1 2 3 4 >>> f = Fees ( ) >>> f . set_fee ( "1" ) >>> f . get_fee ( ) Decimal ( '1' )

    如果你想添加可以使用正常点符号访问的属性,而不破坏所有依赖于这段代码的应用程序,你可以通过添加一个属性函数非常简单地改变它:

    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 from decimal import Decimal   ######################################################################## class Fees ( object ) :      """"""        #----------------------------------------------------------------------      def __init__ ( self ) :          """Constructor"""          self . _fee = None        #----------------------------------------------------------------------      def get_fee ( self ) :          """         Return the current fee         """          return self . _fee        #----------------------------------------------------------------------      def set_fee ( self , value ) :          """         Set the fee         """          if isinstance ( value , str ) :              self . _fee = Decimal ( value )          elif isinstance ( value , Decimal ) :              self . _fee = value        fee = property ( get_fee , set_fee )

    我们在这段代码的末尾添加了一行。现在我们可以这样做:

    Python 1 2 3 4 5 6 7 >>> f = Fees ( ) >>> f . set_fee ( "1" ) >>> f . fee Decimal ( '1' ) >>> f . fee = "2" >>> f . get_fee ( ) Decimal ( '2' )

    正如你所看到的,当我们以这种方式使用属性函数时,它允许fee属性设置并获取值本身而不破坏原有代码。让我们使用属性装饰器来重写这段代码,看看我们是否能得到一个允许设置的属性值。

    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 from decimal import Decimal   ######################################################################## class Fees ( object ) :      """"""        #----------------------------------------------------------------------      def __init__ ( self ) :          """Constructor"""          self . _fee = None        #----------------------------------------------------------------------      @ property      def fee ( self ) :          """         The fee property - the getter         """          return self . _fee        #----------------------------------------------------------------------      @ fee . setter      def fee ( self , value ) :          """         The setter of the fee property         """          if isinstance ( value , str ) :              self . _fee = Decimal ( value )          elif isinstance ( value , Decimal ) :              self . _fee = value   #---------------------------------------------------------------------- if __name__ == "__main__" :      f = Fees ( )

    上面的代码演示了如何为fee属性创建一个setter方法。你可以用一个名为@fee.setter的装饰器装饰第二个方法名也为fee的方法来实现这个。当你如下所做时,setter被调用:

    Python 1 2 >>> f = Fees ( ) >>> f . fee = "1"

    如果你看属性函数的说明,它有fget, fset, fdel和doc几个参数。如果你想对属性使用del命令,你可以使用@fee.deleter创建另一个装饰器来装饰相同名字的函数从而实现删除的同样效果。

    在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:

    s = Student() s.score = 9999

    这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:

    class Student(object): def get_score(self): return self._score def set_score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value

    现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:

    >>> s = Student() >>> s.set_score(60) # ok! >>> s.get_score() 60 >>> s.set_score(9999) Traceback (most recent call last): ... ValueError: score must between 0 ~ 100!

    但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。

    有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说,这是必须要做到的!

    还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:

    class Student(object): @property def score(self): return self._score @score.setter def score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value

    @property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

    >>> s = Student() >>> s.score = 60 # OK,实际转化为s.set_score(60) >>> s.score # OK,实际转化为s.get_score() 60 >>> s.score = 9999 Traceback (most recent call last): ... ValueError: score must between 0 ~ 100!

    注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。

    还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

    class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2014 - self._birth

    上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。

    小结

    @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

    转载请注明原文地址: https://ju.6miu.com/read-673037.html

    最新回复(0)