zoukankan      html  css  js  c++  java
  • 使用property为类中的数据添加行为

    对于面向对象编程特别重要的是,关注行为和数据的分离

    在这之前,先来讨论一些“坏”的面向对象理论,这些都告诉我们绝不要直接访问属性(如Java):

    class Color:
        def __init__(self, rgb_value, name):
            self._rgb_value = rgb_value
            self._name = name
    
        def set_name(self, name):
            self._name = name
    
        def get_name(self):
            return self._name

    前缀有一个单下划线的变量表明他们是类私有的,接着get和set方法提供了对每个变量的访问方式,这个类在实际使用中一般采用如下的方式:

    >>> c = Color('#ff0000', 'bright red')
    >>> c.get_name()
    'bright red'
    >>> c.set_name('red')
    >>> c.get_name()
    'red'

    这并不像python喜欢的直接访问方式具有可读性:

    class Color:
        def __init__(self, rgb_value, name):
            self.rgb_value = rgb_value
            self.name = name

    调用如下:

    >>> c = Color('#ff0000', 'bright red')
    >>> print(c.name)
    bright red
    >>> c.name = "red"
    >>> print(c.name)
    red

    Java的这种方式方便在需要这些变量被赋值时添加额外的代码,例如我们想要验证输入值是否合理,则可以改变set_name()方法来实现:

    def set_name(self, name):
        if not name:
            raise Expception("Invalid Name")
        self._name = name

     但是这样会有一个问题,采用直接访问属性方法的代码,现在必须通过调用方法才能访问原有的属性,如果他们不改变自己的访问方式,那么代码就被破坏了。

    而在python中可以使用property关键字来处理该问题,加入我们原本使用直接成员访问的方法取访问属性,之后我们可以增加几个方法,在不改变访问接口的情况下,来对name这个变量进行取值和赋值。

    class Color:
        def __init__(self, rgb_value, name):
            self.rgb_value = rgb_value
            self._name = name
    
        def _set_name(self, name):
            if not name:
                raise Exception("Invalid Name")
            self._name = name
    
        def _get_name(self):
            return self._name
    
        name = property(_get_name, _set_name)

    先将name这个属性改为一个(半)私有的_name属性,接着我们添加两个(半)私有方法对这个变量进行取值和赋值,并在赋值的时候进行验证。最后我们在代码底部使用property关键字进行声明。

    现在Color类拥有了一个全新的name属性,这个name属性变为了一个property属性,需要通过调用我们刚刚添加的两个方法才能访问或者改变其值而Color类仍能以前一个版本中相同的方式来使用,同时它还能支持对name赋值时进行验证

    >>> c = Color('#ff0000', 'bright red')
    >>> print(c.name)
    bright red
    >>> c.name = 'red'
    >>> print(c.name)
    red
    >>> c.name =""
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "color2.py", line 8, in _set_name
        raise Exception("Invalid Name")
    Exception: Invalid Name

    这样,我们之前编写的任何代码仍然能够工作,但是,即便name变成了property属性,也不能保证100%的安全,如果有人使它设定为空字符串值,仍然可以通过直接访问_name属性的方式来达到目的。

     property是怎样工作的

    property函数实际上返回了一个对象,该对象通过我们指定的方法代理了全部对属性值访问或赋值的请求。

    property构造函数实际上还可以接受两个额外的参数:一个删除函数和一个property的文本字符串。在实际中很少用到删除函数,但是如果需要用到记录所删除的值,那么删除函数还是很有用的。同时在我们满足某个条件的情况下,删除函数还可以否决删除操作。文本字符串是一个用来描述该property的字符串。如果我们不提供文本字符串这个参数,那么该值将从property的第一个参数,也就是getter方法的文本字符串中复制过来。

    这里有个例子,说明了什么时候哪个方法被调用了:

    class Silly:
          def _get_silly(self):
              print("You are getting silly")
              return self._silly
          def _set_silly(self, value):
              print("You are making silly {}".format(value))
              self._silly = value
          def _del_silly(self):
              print("Whoah, you killed silly!")
              del self._silly
          silly = property(_get_silly, _set_silly,
                  _del_silly, "This is a silly property")

    调用如下:

    >>> s = Silly()
    >>> s.silly = "funny"
    You are making silly funny
    >>> s.silly
    You are getting silly
    'funny'
    >>> del s.silly
    Whoah, you killed silly!

    实际上,property通常只定义两个函数就可以了:getter函数和setter函数。

    创建property的另一种方法

    property函数本身也可以使用装饰器语法来使一个get函数变成property函数的参数。使用装饰器只需要为函数名添加一个@符号作为前缀,并把结果放在被装饰函数的定义之前就可以了。

    class Foo:
        @property
        def foo(self):
            return "bar"

    上面的用法是property成为了一个装饰器,这相当于foo = property(foo)。我们还可以为这个property指定一个setter函数:

    class Foo:
        @property
        def foo(self):
            return self._foo
    
        @foo.setter
        def foo(self, value):  # 与上面的函数名是一样的
            self._foo = value

    首先装饰了foo方式,使得它成为getter。接着用刚装饰过的foo方式的setter属性又装饰一个新方法,这个新方法的名字和刚装饰过的foo方法竟然是一样的。property函数返回的是一个对象,而这个对象被自动设置为拥有一个setter属性,而这个setter可以设置为装饰器去装饰其他的函数。

     何时该使用property属性

    python中数据、property属性、方法都是类的属性。方法只是一个可调用的属性(相当于动词),property属性也只是一个能帮助我们进行决策的自定义属性。 数据属性和property属性应该都是名词,数据属性和property属性之间唯一的区别,就是当property属性被检索、赋值或者删除的时候,我们可以自动调用一些自定义的动作

    假如有个定制化行为的普遍需求,它要求对那些难以计算或者查找起来花费多大的值(例如一个网络请求或者数据库查询)进行缓存。我们的目的是本地存储这个值以便面重复调用那些花费过大的计算。我们可以通过在property属性中使用自定义的getter来达到这个目的。当该值第一次被检索的时候,我们执行查找或计算。接着就可以将这个值以对象中的私有属性的形式缓存在本地。之后,当再次请求这个值时,我们就可以返回缓存的数据。

    from urllib.request import urlopen
    class WebPage:
    
        def __init__(self, url):
            self.url = url
            self._content = None
            
        @property
        def content(self):
            if not self._content:
                print("Retrieving New Page...")
                self._content = urlopen(self.url).read()
            return self._content

    我们可以测试这段代码,看看页面是不是只被检索了一次:

    >>> import time
    >>> webpage = WebPage("http://ccphillips.net/")
    >>> now = time.time()
    >>> content1 = webpage.content
    Retrieving New Page...
    >>> time.time() - now
    14.74434518814087
    >>> now = time.time()
    >>> content2 = webpage.content
    >>> time.time() - now
    2.50469708442688
    >>> content2 == content1
    True

    第一次加载页面内容花费了14s,第二次花费了2s,这只是将文本写入解释器的时间。

    自定义的getter对于需要依据对象中其他成员进行就按的属性,也是非常有帮助的。例如,要计算一个整数列表中各元素的平均值:

    class AverageList(list):
        @property
        def average(self):
            return sum(self) / len(self)

    它集成自list,我们能够轻易获得类列表的行为。通过在类中加入一个property属性,很快我们的列表就可以得到一个平均值属性:

    >>> a = AverageList([1,2,3,4])
    >>> a.average
    2.5

    参考:

    1、《Python3 面向对象编程》 [加]Dusty Philips 著

  • 相关阅读:
    WEB新手之sql注入
    WEB新手之do u know caidao?
    C#发送邮件三种方法,Localhost,SMTP,SSL-SMTP
    利用SMTP发送Mail详解
    DevExpress GridControl List绑定方式下新增行的方法
    技术收藏书签
    Oracle CONNECT BY 用法
    在 Oracle Database 11g 第 2 版中查询层次结构数据的快速入门
    Jquery选择器(转载)
    MVC 验证
  • 原文地址:https://www.cnblogs.com/anovana/p/8257728.html
Copyright © 2011-2022 走看看