zoukankan      html  css  js  c++  java
  • __getattr__在python2.x与python3.x中的区别及其对属性截取与代理类的影响

    python2.x中的新类型类(New-style class)与python3.x的类一致,均继承object类,而不继承object的类称为经典类(classic class),而对于这两种类,一般实例属性截取函数(generic instance attribute interception methods)的行为有所不同,其在3.x和2.x的新类型类中,不再被__x__操作符重载函数名(operator overloading name)的内建操作调用,对于该操作符重载函数名的搜索直接在类中搜索,而非实例中,而对于显式名的属性获取,包括__x__名,仍然要路经__getattr__,因此这是对于内建操作行为的主要影响,这种影响进而又影响到属性截取以及代理类。比如一个类定义了__getitem__索引重载函数,x是该类的一个实例,对于经典类来说,x[I]与x.__getitem__(I)等价,而对于新类型类来说,x[I]不再被__getattr__获取,而显式x.__getitem__仍然可以被获取。

    1.对属性截取的影响:

    首先看__getattr__在经典类与新类型类中表现的差异。

    (1)在新类型类中(下列代码在3.x中实现):

    >>> class c:
     data='spam'
     def __getattr__(self,name):
      print('getattr->'+name)
      return getattr(self.data,name)
     
    >>> x=c()
    >>> x[0]
    Traceback (most recent call last):
      File "<pyshell#7>", line 1, in <module>
        x[0]
    TypeError: 'c' object does not support indexing

    构造了一个名为c的类,类长__getattr__方法可截取实例属性,然后打印截取到的属性名,最后返回实例对象的data对象的name方法结果。而对于x[0]内建操作表达式,则抛出了异常,该异常为c对象不支持索引,因此可以看出x[0]是直接在类中进行搜索,而跳过了实例属性截取函数__getattr__。

    >>> getattr->__getitem__
    x.__getitem__(0)
    getattr->__getitem__
    's'

    而x.__getitem__(0)方法可以被__getattr__获取,类似的,对于其他内建操作,比如,x+'eggs',与x.__add__('eggs'),也有相同的反应。

    >>> getattr->__add__
    x.__add__('eggs')
    getattr->__add__
    'spameggs'
    >>> x+'eggs'
    Traceback (most recent call last):
      File "<pyshell#12>", line 1, in <module>
        x+'eggs'
    TypeError: unsupported operand type(s) for +: 'c' and 'str'
    >>> type(x).__getitem__(x,0)
    Traceback (most recent call last):
      File "<pyshell#18>", line 1, in <module>
        type(x).__getitem__(x,0)
    AttributeError: type object 'c' has no attribute '__getitem__'

    当用x的类(即c)调用__getitem__,可以预想到的,抛出AttributeError,因为c并没有__getitem__方法。

    (2)以上代码在经典类中(在2.x中实现):

    >>>  class c:
        data='spam'
        def __getattr__(self,name):
            print('getattr->'+name)
            return getattr(self.data,name)
        
      File "<pyshell#0>", line 2
        class c:
        ^
    IndentationError: unexpected indent
    >>> class c:
        data='spam'
        def __getattr__(self,name):
            print('getattr->'+name)
            return getattr(self.data,name)
    
        
    >>> x=c()
    >>> x[0]
    getattr->__getitem__
    's'
    >>> getattr->__getitem__
    x.__getitem__(0)
    getattr->__getitem__
    's'
    >>> getattr->__add__
    x.__add__('eggs')
    getattr->__add__
    'spameggs'
    >>> x+'eggs'
    getattr->__coerce__
    getattr->__add__
    'spameggs'

    可以看到,在经典类型中,测试全部通过。

    >>> type(x).__getitem__(0)
    
    Traceback (most recent call last):
      File "<pyshell#8>", line 1, in <module>
        type(x).__getitem__(0)
    TypeError: descriptor '__getitem__' requires a 'instance' object but received a 'int'

    但是,尝试用c类调用__getitem__,却抛出异常,主要是描述符(descriptor)的参数错误造成的,关于描述符的总结,将在后面的文章中专门整理。

    2.对代理类的影响

    实际上,在属性截取中,已经提到,在新类型类中,当直接用隐式的内建操作表达式,如x[i],x+等,抛出AttributError的异常,因为这种情况下,是直接从类开始搜索的,而c类中没有,所以才抛出了异常,那该怎么办呢?一个很自然的办法就是在类中,对要代理的隐式内建操作表达式进行重新定义,所以类就具备了要代理操作属性。

    >>> class c:
        data='spam'
        def __getattr__(self,name):
            print('getattr->'+name)
            return getattr(self.data,name)
        def __getitem__(self,i):
            print('getitem:'+str(i))
            return self.data[i]
        def __add__(self,other):
            print('add->'+other)
            return getattr(self.data,'__add__')(other)

    上述代码在3.x中实现,通过对类c重新定义__getitem__,__add__重新定义实现了代理索引和加操作。

    >>> x=c()
    >>> x.upper()
    getattr->upper
    'SPAM'

    可以看到__getattr__截取了一般方法upper()。

    >>> x[0]
    getitem:0
    's'
    >>> x.__getitem__(0)
    getitem:0
    's'
    >>> x+'eggs'
    add->eggs
    'spameggs'
    >>> x.__add__('eggs')
    add->eggs
    'spameggs'

    可以看到,代理成功。

    (3)进一步的理解

    事实上,子类继承基类(超类)的属性或者方法若在子类中没有重载,而子类实例若调用该属性,将不被__getattr__拦截,直接调用基类的属性。如下代码:

    >>> class c:
        def test(self):
            print('test from c')
    
            
    >>> class d(c):
        def __getattr__(self,attr):
            print('getattr'+attr)
    
            
    >>> x=d()
    >>> x.test()
    test from c
    ##### 愿你一寸一寸地攻城略地,一点一点地焕然一新 #####
  • 相关阅读:
    Sqoop详细知识
    数据分析与数据挖掘
    数仓 星形模型与雪花模型 简单理解
    mapreduce多进程与spark多线程比较
    ETL工具总结
    数据仓库概述
    利用 Azure Devops 创建和发布 Nuget 包
    设置 Nuget 本地源、在线私有源、自动构建打包
    简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析
    asp.net core 健康检查
  • 原文地址:https://www.cnblogs.com/johnyang/p/10461887.html
Copyright © 2011-2022 走看看