• 流畅的python——19 动态属性和特性


    十九、动态属性和特性

    在 python 中,数据的属性和处理数据的方法统称属性(attribute)。方法只是可调用的属性。

    除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法(即读值方法和设值方法)修改数据属性。

    这与统一访问原则相符:不管服务是有存储还是计算实现的,一个模块提供的所有服务都应该通过统一的方式使用。

    除了特性,python 还提供了丰富的 API ,用于控制属性的访问权限,以实现动态属性。

    使用点号访问属性,调用 __getattr____setattr__ 特殊方法计算属性。用户自定义的特殊方法实现‘虚拟属性’,当访问不存在的属性时,即时计算属性的值。

    使用动态属性转换数据

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

    我们要记住重要的一点,仅当无法使用常规的方式获取属性(即在实例、类或超类中找不到指定的属性),解释器才会调用特殊的 __getattr__ 方法。

    In [14]: jj = json.loads(j)
    
    In [15]: jj
    Out[15]:
    {'Schedule': {'conferences': [{'serial': 115}],
      'events': [{'serial': 34505,
        'name': 'Why Schools Dont Use Open Source to Teach Programming',
        'event_type': '40-minute conference session',
        'time_start': '2014-07-23 11:30:00',
        'time_stop': '2014-07-23 12:10:00',
        'venue_serial': 1462,
        'description': 'Aside from the fact that high school programming...',
        'website_url': 'http://oscon.com/oscon2014/public/schedule/detail/34505',
        'speakers': [157509],
        'categories': ['Education']}],
      'speakers': [{'serial': 157509,
        'name': 'Robert Lefkowitz',
        'photo': None,
        'url': 'http://sharewave.com/',
        'position': 'CTO',
        'affiliation': 'Sharewave',
        'twitter': 'sharewaveteam',
        'bio': 'Robert r0ml Lefkowitz is the CTO at Sharewave, a startup...'}],
      'venues': [{'serial': 1462,
        'name': 'F151',
        'category': 'Conference Venues'}]}}
    
    In [16]: from collections import abc
    
    In [17]: class F:
        ...:     def __init__(self,mapping):
        ...:         self.__data = dict(mapping)  # 确保 __data 是字典;安全副本。
        ...:     def __getattr__(self,name):  # 仅当没有指定名称的属性才调用该方法。
        ...:         if hasattr(self.__data,name):
        ...:             return getattr(self.__data,name)
        ...:         else:
        ...:             return F.build(self.__data[name])
        ...:     @classmethod
        ...:     def build(cls,obj):
        ...:         if isinstance(obj,abc.Mapping):
        ...:             return cls(obj)
                     # 数据源是 JSON 格式,而在 JSON 中,只有字典和列表是集合类型。
                     # 必然是列表
        ...:         elif isinstance(obj,abc.MutableSequence):
        ...:             return [cls.build(item) for item in obj]
        ...:         else:
        ...:             return obj
    

    处理无效属性名

    In [93]: class F:
        ...:     def __init__(self,mapping):
        ...:         import keyword
        ...:         self.__data = {}
        ...:         for k,v in mapping.items():
        ...:             if keyword.iskeyword(k):  # 判断是否是关键字
        ...:                 k += '_'
        ...:             self.__data[k] = v
        ...:         # self.__data = dict(mapping)
        ...:     def __getattr__(self,name):
                     # 正常的对象,会先找对象属性,再调用 __getattr__ 方法,这里模仿正常的类;调用 keys 等方法就是通过这种方式处理的
        ...:         if hasattr(self.__data,name):
        ...:             return getattr(self.__data,name)
        ...:         else:
        ...:             return F.build(self.__data[name])
        ...:     @classmethod
        ...:     def build(cls,obj):
        ...:         if isinstance(obj,abc.Mapping):
        ...:             print(111)
        ...:             return cls(obj)
        ...:         elif isinstance(obj,abc.MutableSequence):
        ...:             print(222)
        ...:             return [cls.build(item) for item in obj]
        ...:         else:
        ...:             print(333)
        ...:             return obj
        ...:
    
    In [94]: c
    Out[94]: {'a': 1, 'b': 2}
    
    In [95]: cc = F(c)
    
    In [96]: cc.a
    333
    Out[96]: 1
    
    In [97]: cc.b
    333
    Out[97]: 2
    
    In [98]: cc.d
    ---------------------------------------------------------------------------
    KeyError                                  Traceback (most recent call last)
    <ipython-input-98-96574a29fab2> in <module>
    ----> 1 cc.d
    
    <ipython-input-93-13170314a21e> in __getattr__(self, name)
         12             return getattr(self.__data,name)
         13         else:
    ---> 14             return F.build(self.__data[name])
         15     @classmethod
         16     def build(cls,obj):
    
    KeyError: 'd'
    
    In [99]: cc.class
      File "<ipython-input-99-c0e5b4b68caa>", line 1
        cc.class
               ^
    SyntaxError: invalid syntax
    
    
    In [100]: c.class
      File "<ipython-input-100-1c3f5c07bf36>", line 1
        c.class
              ^
    SyntaxError: invalid syntax
    
    
    # 如果 JSON 对象中的键不是有效的 Python 标识符,也会遇到类似的问题
    # s.isidentifier() 方法能根据语言的语法判断 s 是否为有效的 Python 标识符。但是,
    # 把无效的标识符变成有效的属性名却不容易。对此,有两个简单的解决方法,一个是抛出异常,
    # 另一个是把无效的键换成通用名称,例如 attr_0、attr_1,等等。
    

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

    In [123]: class F:
         ...:     def __new__(cls, arg):  # __new__ 是类方法,第一个参数是类本身,余下的参数与 __init__ 方法一样,只不过没有 self。
         ...:         if isinstance(arg,abc.Mapping):
         # 默认的行为是委托给超类的 __new__ 方法。这里调用的是 object 基类的 __new__ 方法,把唯一的参数设为 FrozenJSON。真正的构建操作由解释器调用 C 语言实现的 object.__new__ 方法执行。
         ...:             return super().__new__(cls)
         ...:         elif isinstance(arg,abc.MutableSequence):
         ...:             return [cls(item) for item in arg]
         ...:         else:
         ...:             return arg
         ...:     def __init__(self,mapping):
         ...:         self.__data = {}
         ...:         for k,v in mapping.items():
         ...:             if keyword.iskeyword(k):
         ...:                 k += '_'
         ...:             self.__data[k] = v
         ...:     def __getattr__(self,name):
         ...:         if hasattr(self.__data, name):
         ...:             return getattr(self.__data,name)
         ...:         else:
         ...:             return F(self.__data[name])
         ...:
    
    In [124]: c
    Out[124]: {'a': 1, 'b': 2}
    
    In [125]: cc = F(c)
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-125-3874139b74f1> in <module>
    ----> 1 cc = F(c)
    
    <ipython-input-123-cc4722fd0a4f> in __init__(self, mapping)
         10         self.__data = {}
         11         for k,v in mapping.items():
    ---> 12             if keyword.iskeyword(k):
         13                 k += '_'
         14             self.__data[k] = v
    
    NameError: name 'keyword' is not defined
    
    In [126]: import keyword
    
    In [127]:
    
    In [127]: c
    Out[127]: {'a': 1, 'b': 2}
    
    In [128]: cc = F(c)
    
    In [129]: cc.a
    Out[129]: 1
    
    class Record:
        def __init__(self, **kwargs):  
            self.__dict__.update(kwargs)
    
    # Record.__init__ 方法展示了一个流行的 Python 技巧。我们知道,对象的 __dict__ 属性中存储着对象的属性——前提是类中没有声明 __slots__ 属性。因此,更新实例的 __dict__ 属性,把值设为一个映射,能快速地在那个实例中创建一堆属性。
    

    增加 __eq__ 方法

    class Record:
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
        def __eq__(self, other):
            if isinstance(other, Record):
                return self.__dict__ == other.__dict__
            else:
                return NotImplemented
    

    添加链接数据库功能

    # 自定义的异常通常是标志类,没有定义体。写一个文档字符串,说明异常的用途,比只写一个 pass 语句要好。
    class MissingDatabaseError(RuntimeError):  # 定义无数据库异常
     """需要数据库但没有指定数据库时抛出。""" 
    
    class DbRecord(Record):
     	__db = None  # 类属性存储一个数据库引用
        
     	@staticmethod
     	def set_db(db):
     		DbRecord.__db = db
            
    	@staticmethod
    	def get_db():
     		return DbRecord.__db
        
     	@classmethod
     	def fetch(cls, ident):
     		db = cls.get_db()
     		try:
     			return db[ident]
     		except TypeError:  # 捕获 typeerror 异常
     			if db is None:  # 如果 db 是None,抛出自定义异常
     				msg = "database not set; call '{}.set_db(my_db)'"
     				raise MissingDatabaseError(msg.format(cls.__name__))
     			else:  # 否则重新抛出 TypeError 异常,因为不知道怎么处理
     				raise
     	def __repr__(self):
     		if hasattr(self, 'serial'):  # 如果有 serial 属性,repr
     			cls_name = self.__class__.__name__
     			return '<{} serial={!r}>'.format(cls_name, self.serial)
     		else:
     			return super().__repr__()  # 否则调用继承的 repr
    

    raise

    In [1]: def a(x):
       ...:         print(x[0])
    
    In [2]: a(111)
    TypeError: 'int' object is not subscriptable
    
    In [3]: def a(x):
       ...:         print(x[None])
    
    In [4]: a([1,2])
    TypeError: list indices must be integers or slices, not NoneType
    
    In [5]: def a(x):
       ...:     try:
       ...:         print(x[None])
       ...:     except TypeError:
       ...:         pass
    
    In [7]: a([1,2,3])
    
    In [8]: def a(x):
       ...:     try:
       ...:         print(x[None])
       ...:     except TypeError:
       ...:         raise  # 不做异常处理,向上抛出异常。
    
    In [9]: a([1,2,3])
    TypeError: list indices must be integers or slices, not NoneType
    

    Event 类

    class Event(DbRecord):
        @property
        def venue(self):
            key = 'venue.{}'.format(self.venue_serial)
            return self.__class__.fetch(key)  # 为了防止 fetch 被复写
        # 如果 Record 类的行为更像映射,可以把动态的 __getattr__ 方法换成动态的 __getitem__ 方法,这样就不会出现由于覆盖或遮盖而引起的缺陷了。
        @property
        def speakers(self):
            if not hasattr(self, '_speaker_objs'):
                spkr_serials = self.__dict__['speakers']  # 防止 speakers 无限递归
                fetch = self.__class__.fetch
                self._speaker_objs = [fetch('speaker.{}'.format(key))
                                      for key in spkr_serials]
            return self._speaker_objs
        def __repr__(self):
            if hasattr(self, 'name'):
                    cls_name = self.__class__.__name__
                    return '<{} {!r}>'.format(cls_name, self.name)
            else:
                    return super().__repr__()
    

    load_db

    def load_db(db):
        raw_data = osconfeed.load()
        warnings.warn('loading ' + DB_NAME)
        for collection, rec_list in raw_data['Schedule'].items():
            record_type = collection[:-1]
            cls_name = record_type.capitalize()  # 首字母大写,可能的类名
            cls = globals().get(cls_name, DbRecord)  # 从模块的全局作用域中获取那个名称对应的对象
            if inspect.isclass(cls) and issubclass(cls, DbRecord):
                factory = cls
            else:
                factory = DbRecord
            for record in rec_list:
                        key = '{}.{}'.format(record_type, record['serial'])
                        record['serial'] = key
                        db[key] = factory(**record)
    

    使用特性验证属性

    订单中的商品类

    In [2]: class L:
       ...:     def __init__(self,des,weight,pric):
       ...:         self.des = des
       ...:         self.weight = weight
       ...:         self.pric = pric
       ...:     def subtotal(self):
       ...:         return self.weight * self.pric
    

    问题:重量为负值,金额为负值

    这个示例像玩具一样,但是没有想象中的那么好玩。下面是亚马逊早期的真实故事。

    我们发现顾客买书时可以把数量设为负数!然后,我们把金额打到顾客的信用卡上,苦苦等待他们把书寄出(想得美)。

    ​ ——Jeff Bezos

    ​ 亚马逊创始人和 CEO

    采用读值方法和设值方法

    In [4]: class L:
       ...:     def __init__(self,des,weight,pric):
       ...:         self.des = des
       ...:         self.weight = weight
       ...:         self.pric = pric
       ...:     def subtotal(self):
       ...:         return self.weight * self.pric
       ...:     @property
       ...:     def weight(self):
       ...:         return self.__weight
    # 被装饰的读值方法有个 .setter 属性,这个属性也是装饰器;这个装饰器把读值方法和设值方法绑定在一起。
       ...:     @weight.setter  
       ...:     def weight(self,value):
       ...:         if value > 0:
       ...:             self.__weight = value
       ...:         else:
       ...:             raise ValueError('value must be > 0')
       ...:
    
    In [5]: l = L('1',-1,3)
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-5-f726239e194f> in <module>
    ----> 1 l = L('1',-1,3)
    
    <ipython-input-4-0a57e214d933> in __init__(self, des, weight, pric)
          2     def __init__(self,des,weight,pric):
          3         self.des = des
    ----> 4         self.weight = weight
          5         self.pric = pric
          6     def subtotal(self):
    
    <ipython-input-4-0a57e214d933> in weight(self, value)
         14             self.__weight = value
         15         else:
    ---> 16             raise ValueError('value must be > 0')
    

    关于设值方法

    In [7]: class L:
       ...:     def __init__(self,des,weight,pric):
       ...:         self.des = des
       ...:         self.weight = weight
       ...:         self.pric = pric
       ...:     def subtotal(self):
       ...:         return self.weight * self.pric
       ...:     @property
       ...:     def weight(self):  # 读取 weight 的时候
       ...:         return self.__weight
       ...:     @weight.setter
       ...:     def weight222(self,value):  # 设值 weight222 的时候
       ...:         if value > 0:
       ...:             self.__weight = value
       ...:         else:
       ...:             raise ValueError('value must be > 0')
       ...:
    
    In [8]: l = L('1',1,1)
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-8-9fe30ebaadc4> in <module>
    ----> 1 l = L('1',1,1)
    
    <ipython-input-7-1ea4b9a6518b> in __init__(self, des, weight, pric)
          2     def __init__(self,des,weight,pric):
          3         self.des = des
    ----> 4         self.weight = weight
          5         self.pric = pric
          6     def subtotal(self):
    
    AttributeError: can't set attribute
    
    In [9]: class L:
       ...:     def __init__(self,des,weight,pric):
       ...:         self.des = des
       ...:         self.weight222 = weight
       ...:         self.pric = pric
       ...:     def subtotal(self):
       ...:         return self.weight * self.pric
       ...:     @property
       ...:     def weight(self):
       ...:         return self.__weight
       ...:     @weight.setter
       ...:     def weight222(self,value):
       ...:         if value > 0:
       ...:             self.__weight = value
       ...:         else:
       ...:             raise ValueError('value must be > 0')
       ...:
    
    In [10]: l = L('1',1,1)
    
    In [11]: l.weight = 2
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-11-b53379f4566b> in <module>
    ----> 1 l.weight = 2
    
    AttributeError: can't set attribute
    
    In [12]: l.weight222 = 2
    
    In [13]: l.weight
    Out[13]: 2
        
    # 所以这样做的结果:读从 weight 读 ,写从 weight222 写。
    

    特性全解析

    虽然内置的 property 经常用作装饰器,但它其实是一个类。在 Python 中,函数和类通常可以互换,因为二者都是可调用对象,而且没有实例化对象的 new 运算符,所以调用构造方法与调用工厂函数没有区别。此外,只要能返回新的可调用对象,代替被装饰的函数,二者都可以用作装饰器。

    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)  # 构建 property 对象,然后赋值给公开的类属性
        
     """
     某些情况下,这种经典形式比装饰器句法好;稍后讨论的特性工厂函数就是一例。但是,
    在方法众多的类定义体中使用装饰器的话,一眼就能看出哪些是读值方法,哪些是设值方
    法,而不用按照惯例,在方法名的前面加上 get 和 set。
    """
    

    特性会覆盖实例属性

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

    In [15]: class C:
        ...:     d = 'data'
        ...:     @property
        ...:     def p(self):
        ...:         return 'p'
        ...:
    
    In [16]: c = C()
    
    In [17]: vars(c)  # vars 返回对象的 __dict__ 属性,表明没有实例属性
    Out[17]: {}
    
    In [18]: c.d
    Out[18]: 'data'
    
    In [19]: c.d = '111'  # 赋值 实例属性 d
    
    In [20]: vars(c)
    Out[20]: {'d': '111'}
    
    In [22]: c.d
    Out[22]: '111'
    
    In [23]: C.d
    Out[23]: 'data'
        
        
    # property 特性
    
    In [24]: C.p  # 类属性 property,获取的是特性对象本身,不会运行特性的读值方法
    Out[24]: <property at 0x19a56c66ea8>
    
    In [25]: c.p  # property 管理的是实例属性
    Out[25]: 'p'
    
    In [26]: c.p = 'aaa'  # 不能赋值
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-26-e632e2858e77> in <module>
    ----> 1 c.p = 'aaa'
    
    AttributeError: can't set attribute
    
    In [27]: c.__dict__  # 实例属性中没有
    Out[27]: {'d': '111'}
    
    In [28]: c.__dict__['p'] = 'aaa'  # 这样可以
    
    In [29]: vars(c)  # 发现有实例属性 p 了
    Out[29]: {'d': '111', 'p': 'aaa'}
    
    In [30]: c.p  # 但是,property 特性会覆盖实例属性
    Out[30]: 'p'
    
    In [31]: C.p = 'bbb'  # 将 property 变成普通属性
    
    In [32]: c.p  # 发现 恢复为 实例属性优先
    Out[32]: 'aaa'
        
    # 用特性覆盖类属性
    In [33]: c.d
    Out[33]: '111'
    
    In [35]: C.d
    Out[35]: 'data'
    
    In [36]: C.d = property(lambda self:'ddd')
    
    In [37]: c.d
    Out[37]: 'ddd'
    
    In [38]: C.d
    Out[38]: <property at 0x19a56c374a8>
    
    In [39]: del C.d
    
    In [40]: c.d
    Out[40]: '111'
        
    # 也就是说特性会覆盖 类属性和实例属性,可以删除这个覆盖
    
    In [43]: cc = C()  # 重新定义一个对象,发现 类属性 property 还是没有的
    
    In [44]: cc.p
    Out[44]: 'bbb'
        
    In [45]: class C:  # 重新定义类,发现恢复了 property p
        ...:     d = 'data'
        ...:     @property
        ...:     def p(self):
        ...:         return 'p'
        ...:
    
    In [46]: cc = C()
    
    In [47]: cc.p
    Out[47]: 'p'
    
    In [48]: del C.p
    
    In [51]: cc.p = 'kk'
    
    In [52]: cc.p
    Out[52]: 'kk'
    

    本节的主要观点是,obj.attr 表达式不会从 obj 开始寻找 attr,而是从 obj.__class__ 开始,当类中没有名为 attr 的特性时,python 才会在 obj 实例中寻找。

    这条规则不仅适用于特性,还适用于一整类描述符——覆盖型描述符(overriding descriptor)。特性其实是覆盖型描述符。

    特性的文档

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

    使用经典句法实现 property

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

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

    定义一个特性工厂函数

    def quantity(storage_name):  # 存储的名称
        def qty_getter(instance):  # instance 表示对象
            return instance.__dict__[storage_name]
        def qty_setter(instance, value):
            if value > 0:
                instance.__dict__[storage_name] = value  # 闭包,跳过特性,防止无限递归
            else:
                 raise ValueError('value must be > 0')
        return property(qty_getter, qty_setter)
    
    # 存取值通过,property 特性;真正的值存储在实例属性中。
    

    处理属性删除操作

    删除属性

    del obj.attr
    

    通过特性删除属性

    @my_property.deleter  # 负责删除特性管理的属性
    
    In [9]: class B:
       ...:     def __init__(self):
       ...:         self.mem = ['a','b','c']
       ...:         self.ph = ['aa','bb','cc']
       ...:     @property
       ...:     def me(self):
       ...:         return self.mem[0]
       ...:     @me.deleter
       ...:     def me(self):
       ...:         print('delete:',self.mem.pop(0),self.ph.pop(0))
       ...:
    
    In [10]: b = B()
    
    In [11]: b.me
    Out[11]: 'a'
    
    In [12]: del b.me
    delete: a aa
    
    In [13]: b.me
    Out[13]: 'b'
    

    经典句法,删除是 fdel 参数:

    member = property(member_getter, fdel=member_deleter)
    

    不使用特性,可以使用特殊方法处理删除属性的操作。

    __delattr__
    

    处理属性的重要属性和函数

    影响属性处理方式的特殊属性

    __class__

    对象所属类的引用(即 obj.__class__type(obj) 的作用相同)。Python的某些特殊方法,只在对象的类中寻找,而不在实例中寻找,比如:__getattr__

    __dict__

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

    __slots__

    类可以诋毁能够以这个属性,限制实例能有那些属性。__slots__ 属性的值是一个字符串组成的元组,指明允许有的属性。如果 __slots__ 中没有 __dict__ ,那么该类的实例没有 __dict__ 属性,实例只允许有指定名称的属性。

    __slots__属性的值虽然可以是一个列表,但是最好始终使用元组,因为处理完类的定义体之后再修改 __slots__ 列表没有任何作用,所以使用可变的序列容易让人误解。

    处理属性的内置函数

    dir(obj)

    列出对象的大多数属性。

    官方文档
    (https://docs.python.org/3/library/functions.html#dir)说,
        dir 函数的目的是交互式使用,因此没有提供完整的属性列表,只列出一组“重要的”属性名。dir 函数能审查有或没有 __dict__ 属性的对象。dir 函数不会列出 __dict__ 属性本身,但会列出其中的
    键。dir 函数也不会列出类的几个特殊属性,例如 __mro__、__bases__ 和 __name__。
    如果没有指定可选的 object 参数,dir 函数会列出当前作用域中的名称。
    

    getattr(obj,name)

    从obj对象中获取 name字符串对应的属性。获取的属性可能来自对象所属的类或超类。如果没有指定的属性,getattr 函数抛出 AttributeError 异常,或者返回 default 参数的值(如果设定了这个参数的话)

    hasattr(obj,name)

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

    文档
    (https://docs.python.org/3/library/functions.html#hasattr)说道:
    “这个函数的实现方法是调用 getattr(object, name) 函数,看看是否抛出 AttributeError 异常。”
    

    setattr(obj,name,value)

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

    vars(obj)

    返回obj对象的 __dict__ 属性,如果实例所属的类定义了__slots__ 属性,实例没有 __dict__ 属性,那么vars 函数不能处理那个实例(相反,dir函数能处理这样的实例)。如果没有指定参数,那么 vars函数 的作用与 locals() 函数一样:返回表示本地作用域的字典。

    处理属性的特殊方法

    Python 文档“Data model”一章中的“3.3.9. Special method lookup”一节(https://docs.python.org/3/reference/datamodel.html#special-method-lookup)警告说:

    对用户自己定义的类来说,如果隐式调用特殊方法,仅当特殊方法在对象所属的类型上定义,而不是在对象的实例字典中定义时,才能确保调用成功。

    也就是说,要假定特殊方法从类上获取,即便操作目标是实例也是如此。因此,特殊方法不会被同名实例属性遮盖。

    使用点号或内置的 getattr\hasattr\setattr 函数存取属性都会触发以下对应的方法。

    但是,直接使用实例的 __dict__ 属性读写属性不会触发这些特殊方法——通常通过这种方式跳过特殊方法。

    __delattr__(self,name)

    del 语句触发 Class.__delattr__(obj,'attr') 方法。

    __dir__(self)

    把对象传给 dir函数使调用,列出属性。dir(obj) 触发 Class.__dir__(obj) 方法。

    __getattr__(self,name)

    仅当获取指定的属性失败,搜索过 obj, Class 和超类之后调用。表达式:obj.attr getarrt(obj,'attr') hasattr(obj,'attr') ,当找不到实例属性时触发 Class.__getattr__(obj,'attr')

    __getattribute__(self,name)

    尝试获取指定的属性时触发,不过,获取的属性时特殊属性或特殊方法时除外。点号与getattr和hasattr 内置函数会触发这个方法。调用 __getattribute__ 方法抛出 AttributeError 异常时,才会调用 __getattr__ 方法。 为了在获取 obj实例的的属性时不导致无限递归,__getattribute__ 方法的实现要使用 super().__getattribute__(obj,name)

    __setattr__(self,name,value)

    点号和 setattr 属性函数会触发 Class.__setattr__(obj,'attr',1)方法。

     其实,特殊方法 __getattribute__ 和 __setattr__ 不管怎样都会调用,几
    乎会影响每一次属性存取,因此比 __getattr__ 方法(只处理不存在的属性名)更
    难正确使用。与定义这些特殊方法相比,使用特性或描述符相对不易出错。
    

    __getitem____setitem____delitem__

    用于索引操作,如字典。以上分别表示获取、设置、删除数据

  • 相关阅读:
    android个版本对应的SDK level,最新包括android10.0
    SQL语句 存在就更新不存在就插入
    forward和sendredirect
    JavaBean
    Cookie单点登录跨域问题
    JSP
    JSP内置对象
    Spring学习笔记
    事务
    AOP实现方式
  • 原文地址:https://www.cnblogs.com/pythonwl/p/15793674.html
Copyright © 2020-2023  润新知