动态属性和特性

使用动态属性访问

一、使用动态属性访问JSON类数据

  feed['Schedule']['events'][40]['name'],这种句法冗长,在Javascript中,可以使用feed.Schedule.events[40].name,获取那个值。

  在Python中,可以实现一个近似字典的类,达到同样的效果。

from collections import abc

class FrozenJSON:
    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.build(self.__data[name])
        
    @classmethod
    def build(cls,obj):
        if isinstance(obj, abc.Mapping):
            return cls(obj)
        elif isinstance(obj, abc.MutableSequence):
            return [cls.build(item) for item in obj]
        else:
            return obj

  FrozenJSON类的关键是__getattr__方法。(仅当无法使用常规的方式获取属性,即在实例、类或超类中找不到指定的属性,解释器才会调用特殊的__getattr__方法)

  FrozenJSON.build方法。能深入JSON数据的嵌套结构,使用类方法build把每一层嵌套转换成一个FrozenJSON实例。

处理无效属性名

二、处理无效属性名

  对名称为Python关键字的属性做特殊处理

import keyword

class FrozenJSON:
    def __init__(self,mapping):
        self.__data = {}
        for key,value in mapping.items():
            if keyword.iskeyword():
                key += '_'
            self.__data[key] = value

  对于数字开头的key,str类提供的s.isidentifier()方法,能根据语言的语法判断s是否为有效的Python标识符。

  解决办法:抛出异常或者换成通用名称,attr_0,attr_1。

三、使用__new__方法灵活的方式创建对象

  通常把__init__称为构造方法,这是从其他语言借鉴过来的术语。其实,用于构建实例的是特殊方法__new__;

  这是一个类方法,使用特殊方式处理,因此不必使用@classmethod装饰器,必须返回一个实例。

  返回的实例会作为第一个参数(即self)传给__init__方法。

  因为调用__init__方法时要传入实例,而且禁止返回任何值,所以__init__方法其实是“初始化方法”。真正的构造方法是__new__。

  我们不要自己编写__new__方法,因为从object类集成的实现已经足够了。

class FrozenJSON:

    def __new__(cls, arg):
        if isinstance(arg,abc.Mapping):
            return super().__new__(cls)
        elif isinstance(arg, abc.MutableSequence):
            return [cls.build(item) for item in arg]
        else:
            return arg

四、调整OSCON数据源的结构

  对象的__dict__属性中存储着对象的属性,前提是类中没有声明__slots__属性。

  更新实例的__dict__属性,把值设为一个映射,能快速地在那个实例中创建一堆属性。

class Record:
    def __init__(self,**kwargs):
        self.__dict__.update(**kwargs)

    def load_db(db):
        raw_data = data.load()
        for collection,rec_list in raw_data['Schedule'].items():
            record_type = collection[:-1]
            for record in rec_list:
                key = '{}.{}'.format(record_type,record['serial'])
                record['serial'] = key
                db[key] = Record(**kwargs)

特性全解析

五、特性全解析

  内置的property经常用作装饰器,但它其实是一个类。在Python中,函数和类通常可以互换,因为二者都是可调用的对象。

  property构造方法的完整签名如下:

    property(fget=None, fset=None, fdel=None, doc=None)

class LineItem:
    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 :
            self.__weight = value
        else:
            raise ValueError('Value must be > 0')
    
    weight = property(get_weight,set_weight)

  特性会覆盖实例属性

  特性都是类属性,但是特性管理的其实是实例属性的存取。

  (1)当实例和类有同名数据属性时,那么实例属性会覆盖类属性

class Foo:
    data = 'the Foo data attr'

    @property
    def prop(self):
        return 'the prop value'

>>> f = Foo()
>>> vars(f)
{}
>>> f.data
'the Foo data attr'
>>> f.data = 'Bar'
>>> vars(f)
{'data':'Bar'}
>>> Foo.data
'the Foo data attr'

  vars函数返回obj的__dict__属性,表明没有实例属性。

  (2)实例属性不会覆盖类特性

>>> f = Foo()
>>> Foo.prop
<property object at 0x00000234D2F473B8>
>>> f.prop
'the prop value'
>>> f.prop = 'fooooo'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> f.__dict__['prop'] = 'fooooo'
>>> vars(f)
{'prop': 'fooooo'}
>>> f.prop
'the prop value'
>>> Foo.prop
<property object at 0x00000234D2F473B8>
>>> Foo.prop = 'barrrrr'
>>> f.prop
'fooooo'

  直接从Foo中读取prop特性,获取的是特性对象本身,不会运行特性的读值方法。

  (3)新添加的类特性覆盖现有实例属性

>>> f.data
'bar'
>>> Foo.data
'the Foo data attr'
>>> Foo.data = property(lambda self:'xxxxxxxxxxx')
>>> f.data
'xxxxxxxxxxx'
>>> del Foo.data
>>> f.data
'bar'

  ▲ obj.attr这样的表达式不会从obj开始寻找attr,而是从obj.__class__开始,而且,仅当类中没有名为attr的特性时,Python才会在obj实例中寻找。

特性的文档:

  控制台中的help()函数或IDE等工具需要显示特性的文档时,会从特性的__doc__属性中提取信息。

为property对象设置文档字符串的方法时传入doc参数:

  weight = property(get_weight,set_weight, doc='weight in kilograms')

使用装饰器创建property对象时,读值方法的文档字符串作为一个整体,变成特性文档。

class Foo:
    @property
    def bar(self):
        '''The bar attrbute'''
        return self.__dict__['bar']

    @bar.setter
    def bar(self,value):
        self.__dict__['bar'] = value

print(help(Foo.bar))

特性工厂:

def quantity(storage_name):

    def qty_setter(instance,value):
        if value > 0:
            instance.__dict__[storage_name] = value
        else:
            raise ValueError('Value must be > 0')
def qty_getter(instance): return instance.__dict__[storage_name] return property(qty_getter,qty_setter) class LineItem: weight = quantity('weight') price = quantity('price') def __init__(self,description,weight,price): self.description = description self.weight = weight self.price = price def subtotal(self): return self.weight * self.price

属性删除操作:

class BlackKnight:
    def __init__(self):
        self.members = ['an arm', 'another arm',
                    'a leg', 'another leg']
        self.phrases = ["'Tis but a scratch.",
                        "It's just a flesh wound.",
                        "I'm invincible!",
                        "All right, we'll call it a draw."]
    @property
    def member(self):
        print('next member is:')
        return self.members[0]
    
    @member.deleter
    def member(self):
        text = 'BLACK KNIGHT (loses {})
-- {}'
        print(text.format(self.members.pop(0), self.phrases.pop(0)))

重要属性和函数

六、处理属性的重要属性和函数:

(1)__class__:

  对象所属类的引用(即obj.__class__与type(obj)的作业相同)。

(2)__dict__:

  一个映射,存储对象或类的可写属性。有__dict__属性的对象,任何时候都能随意设置新属性。

  如果类有__slots__属性,它的实例可能没有__dict__属性。

(3)__slots__:

  类可以定义这个属性,限制实例能有哪些属性。__slots__属性的值是一个字符串组成的元组,指明允许有的属性。

  如果__slots__中没有__dict__,那么该类的实例没有__dict__属性,实例只允许指定民称的属性。

内置函数:

(1)dir([object]):

  列出对象的大多数属性。

(2)getattr(object, name[, default]):

  从object对象中获取name字符串对应的属性。获取的属性可能来自对象所属的类或超类。

  如果没有指定的属性,getattr函数抛出AttributeError异常,或者返回default参数的值。

(3)hasattr(object, name):

  如果object对象中存在指定的属性,或者能以某种方式(继承)通过object对象获取指定的属性,返回True。

(4)setattr(object, name, value):

  把object对象指定的属性值设为value,前提是object对象能接受那个值。这个函数可能会创建一个新属性,或者覆盖现有的属性。

(5)vars([object])

  返回object对象的__dict__属性,如果实例所属的类定义了__slots__属性,实例没有__dict__属性,那么vars函数不能处理那个实例!

  相反dir函数能处理这样的实例。

  如果没有指定参数,那么vars()函数和locals()函数一样,返回表示本地作用域的字典。

处理属性的特殊方法:

(1)__delattr__(self, name):

  只要使用del语句删除属性,就会调用这个方法。

(2)__dir__(self):

  把对象传给dir函数时调用,列出属性。

(3)__getattr__(self, name):

  仅当获取指定的属性失败,搜索过obj、class和超类之后调用。

(4)__getattribute__(self, name):

  尝试获取指定的属性时总会调用这个方法,不过,寻找的属性时特殊属性或特殊方法时除外。

  点号与getattr和hasattr内置函数会触发这个方法。如:obj.attr和getattr(obj, 'attr', xx)

  调用__getattribute__方法且抛出AttributeError异常时,才会调用__getattr__方法。

  为了在获取obj实例的属性不导致无限递归,__getattribute__方法的实现要使用super().__getattribute__(obj. name)

(5)__setter__(self, name, value):

  尝试设置指定的属性时总会调用这个方法。点号和setattr内置函数会触发这个方法。

  obj.attr = xx 和 setter(obj,'attr',xx)

原文地址:https://www.cnblogs.com/5poi/p/11456066.html