• 流畅python学习笔记:第十九章:动态属性和特性


    首先来看一个json文件的读取。书中给出了一个json样例。该json文件有700多K,数据量充足,适合本章的例子。文件的具体内容可以在http://www.oreilly.com/pub/sc/osconfeed上查看。首先先下载数据生成json文件。

    def load():
        url='http://www.oreilly.com/pub/sc/osconfeed'
       
    JSON="osconfeed.json"
        if not
    os.path.exists(JSON):
            remote=urlopen(url)
            with open(JSON,'wb') as local:
                local.write(remote.read())
        with open(JSON) as fp:
            return json.load(fp)
    我们要访问json数据里面的例子,该如何访问呢,一般情况是
    print feed['Schedule']['speakers'][-1]['name'] 但是这种句法有个缺点,就是很冗长。能不能按照feed.Schedule.speakers[-1].name这种比较简洁的方式来访问呢。要实现这种访问。需要对数据做下重新处理。这里要用到__getattr__方法:代码如下:
    class FrozenJSON:
        def __init__(self,mapping):
            self.__data=dict(mapping)   (1)
        def __getattr__(self,name):
            if hasattr(self.__data,name):
                return getattr(self.__data,name)   (2)
            else:
                return FrozenJSON.build(self.__data[name])  (3)
        @classmethod
        def build(cls,obj):
            if isinstance(obj,dict):      (4)
                return cls(obj)
            elif isinstance(obj,list):      (5)
                return [cls.build(item) for item in obj]
            else:                  (6)
                return obj
     
    (1)构造一个字典,这样做确保传入的是字典
    (2)确保没有此属性的时候调用__getattr__
    (3)如果name是__data的属性,则返回那个属性。
    (4)如果判定是字典,则返回该字典对象
    (5)如果是列表,则将列表的每个元素递归的传给build方法,构建一个列表
    (6)如果既不是列表也不是字典,则直接返回元素
    这样实现我们就能按照前面的预期来访问元素了:raw_feed.Schedule.speakers[-1].name

    使用__new__方法来创建对象

    首先来介绍下__new__方法。我们通常都将__init__称为构造函数。其实在python中真正的构造函数应该是__new__。我们没有具体的去实现__new__方法。是因为从object类继承的实现已经足够了。来看一个例子:

    class A(object):
        def __init__(self):
            print '__init__'
        def
    __new__(cls, *args, **kwargs):
            print '__new__'
            print
    cls
            return object.__new__(cls, *args, **kwargs)
    if __name__=="__main__":
        a=A()

    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter19.py

    __new__

    <class '__main__.A'>

    __init__

    从结果可以看到首先是进入__new__,然后来生成一个对象的实例并返回。最后才是执行__init__。从这个例子可以看出在构造一个对象实例的时候,首先是进入__new__生成对象实例,然后再调用__init__方法进行初始赋值。那么我们用__new__方法来改造前面的FrozenJSON类。在前面的FrozenJSON实现中,build函数其实是不停的在递归各个字典对象,在递归过程中生成FronzenJSON实例进行处理。也就是第四步中的return cls(obj)。这里我们可以__new__来改造。
    class FrozenJSON1(object):
        def __new__(cls, args):
            if isinstance(args,dict):
                return object.__new__(cls)
            elif isinstance(args,list):
                return [cls(item) for item in arg]
            else:
                return args
        def __init__(self,mapping):
            self.__data=dict(mapping)
        def __getattr__(self,name):
            if hasattr(self.__data,name):
                return getattr(self.__data,name)
            else:
                return FrozenJSON(self.__data[name])
    上面代码部分中的__new__就是实现了build方法。在__getattr__中没有找到对应name属性时候,return FrozenJSON(self.__data[name])新建一个FrozenJSON对象进行往下递归

    使用特性验证属性:

    首先来看一个电商应用

    class LineItem(object):
        def __init__(self,description,weight,price):
            self.description=description
            self.weight=weight
            self.price=price
        def subtotal(self):
            return self.weight*self.price


    if __name__=="__main__":
        raisins=LineItem('Golden raisins',10,6.95)
        print raisins.subtotal()

    目前这个实现都是正常的,客户输入要的货物数量,和单价。在这里计算出总价。但是如果客户一个不小心,把货物数量设置成了负数,后果会怎样?

    if __name__=="__main__":
        raisins=LineItem('Golden raisins',10,6.95)
        print raisins.subtotal()
        raisins.weight=-20
        print raisins.subtotal()

    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter19.py

    69.5

    -139.0

    这个时候变成了给顾客钱。是不是很囧。一般来说这种情况下都会想到将变量设置为私有变量。然后则设置值的时候进行保护。

    class LineItem(object):
        def __init__(self,description,weight,price):
            self.description=description
            self.__weight=weight
            self.__price=price
        def set_value(self,new_value):
            if new_value <=0:
                raise ValueError('value must be > 0')
            else:
                self.__weight=new_value
        def subtotal(self):
            return self.__weight*self.__price

    if __name__=="__main__":
        raisins=LineItem('Golden raisins',10,6.95)
        print raisins.subtotal()
        raisins.set_value(0)

    数量和价格都被设置成了私有变量。要想设置值必须通过set_value的方式。而在set_value的时候设置了保护,当设置的值小于等于0的时候,弹出异常。

    Traceback (most recent call last):

      File "E:/py_prj/fluent_python/chapter19.py", line 76, in <module>

        raisins.set_value(0)

      File "E:/py_prj/fluent_python/chapter19.py", line 68, in set_value

        raise ValueError('value must be > 0')

    ValueError: value must be > 0

    对于这种情况,我们还有另外一种方法。那就是将属性变成一种特性。采用property方法。代码如下:

    class LineItem(object):
        def __init__(self,description,weight,price):
            self.description=description
            self.weight=weight
            self.price=price
        def subtotal(self):
            return self.weight*self.price
        @property
        def weight(self):
            return self.__weight
        @weight.setter
        def weight(self,value):
            if value <=0:
                raise ValueError('value must be > 0')
            else:
                self.__weight=value

    if __name__=="__main__":
        raisins=LineItem('Golden raisins',10,6.95)
        print raisins.subtotal()
        raisins.weight=0

    通过@property将weight变成一个特性,@weight.setter来进行赋值。虽然内置的property经常用作装饰器,但它其实是个类。代码可以改写成下面的样子:

    class LineItem(object):
        def __init__(self,description,weight,price):
            self.description=description
            self.weight=weight
            self.price=price
        def subtotal(self):
            return self.weight*self.price
        def get_weight(self):
            return self.__weight
        def set_weight(self,value):
            if value <= 0:
                raise ValueError('value must be  > 0')
            else:
                self.__weight=value
        weight=property(get_weight,set_weight)

    至于用哪种方法更好,这个属于见仁见智的看法。我个人觉得用装饰器的方式看起来更简洁一些。因为可以很明白的看出赋值和读值,而不用按照惯例在方法名的前面加上get和set

    接下来看下属性和特性的差别:

    class Class(object):
        data='the class data attr'
       
    @property
        def prop(self):
            return 'the prop value'


    if
    __name__=="__main__":
        obj=Class()
        print vars(obj)     (1)
        print obj.data      (2)
        obj.data='bar'
        print
    vars(obj)     (3)
        print obj.data      (4)
        print Class.data    (5)
    (1)    vars函数返回的是obj的__dict__函数,没有实例属性
    (2)    读取obj.data实际上读取的是Class.data的值
    (3)    为obj.data赋值后,创建一个实例属性。
    (4)    读取obj.data,获取的是实例属性的值。实例属性会覆盖类属性data
    (5)    类属性还是以前的样子,并没有被覆盖
     
    下面来看下特性的例子
    if __name__=="__main__":
        obj=Class()
        print Class.prop      (1)
        print obj.prop        (2)
        obj.__dict__['prop']='foo'     (3)
        print
    vars(obj)
        print obj.prop                  (4)
        Class.prop='baz'               
        print
    obj.prop                   (5)
    E:python2.7.11python.exe E:/py_prj/fluent_python/chapter19.py
    <property object at 0x01B4F540>
    the prop value
    {'prop': 'foo'}
    the prop value
    foo
    (1)    直接从Class中读取prop特性。获取的是特性本身
    (2)    读取obj.prop
    (3)    通过__dict__方法来给实例增加一个属性
    (4)    此时实例有2个实例属性,data和prop, 但是在调用prop的时候仍然是读取特性的方法,而不是实例属性。表明特性没有被实例属性覆盖
    (5)    当类的prop特性被覆盖后,销毁该特性对象。再次读取obj.prop的时候,Class.prop不再是特性了,因此不会覆盖obj.prop。
     
    总结:从这里可以看出,当读取实例属性的时候会覆盖类的属性。而在读取实例特性的时候,特性不会被实例属性覆盖,而依然是读取类的特性。除非类特性被销毁。
  • 相关阅读:
    java
    Java 自定义异常(转载)
    java中更新文件时,指定原文件的编码格式,防止编码格式不对,造成乱码
    tar命令压缩和解压
    微服务之服务注册与发现--Consul(转载)
    git push 时:报missing Change-Id in commit message footer的错误
    git 版本回退
    item 快捷键
    mac下mysql的卸载和安装
    JAVA正则表达式:Pattern类与Matcher类详解(转)
  • 原文地址:https://www.cnblogs.com/zhanghongfeng/p/7468881.html
Copyright © 2020-2023  润新知