美文网首页Python程序员python进阶
python 属性访问顺序 描述符

python 属性访问顺序 描述符

作者: 落羽归尘 | 来源:发表于2019-08-18 11:09 被阅读3次

概述

        了解和熟悉python中的属性访问顺序,有助于我们阅读源码,编写高质量代码,对python机制有个更深的理解。
        在讲解属性访问顺序之前,我们先熟悉一下与之有关的知识,__getattribute____getattr__,描述符等。随后我们通过例子来讲解python的属性访问顺序到底是怎样的。

前置知识

  • 方法__getattribute__
  • 方法__getattr__
  • 属性描述符
方法__getattribute__

        当实例对象访问属性或者方法时都需要调用到__getattribute__,之后才会在各个__dict__中查找相应的属性或方法。注意在方法__getattribute__内部,杜绝存在self.**,因为这样的话就又会调用到__getattribute__,极可能会递归调用造成错误。

class Test(object):
    def __init__(self,name):
        self.name = name
    
    def __getattribute__(self,key):
        print("访问属性:%s" % key)

t = Test("test1")
t.name
输出结果:
访问属性:name

但是当有实例方法时,调用实例方法时会报错,如下

class Test(object):
    def __init__(self,name):
        self.name = name
    
    def func(self):
        pass

    def __getattribute__(self,key):
        print("访问属性:%s" % key)

t = Test("test1")
t.name
t.func()
输出结果:
访问属性:name
访问属性:func
Traceback (most recent call last):
  File "c:/Users/DELL/Desktop/ssj/search/descrip.py", line 15, in <module>
    t.func()
TypeError: 'NoneType' object is not callable
方法__getattr__

        在当用户访问一个根本不存在的属性或者查找不到时,会调用__getattr__。用于查找属性的最后一步。

class Test(object):
    def __init__(self,name):
        self.name = name
    
    def __getattr__(self,key):
        print("调用方法__getattr__: %s" % key)

t = Test("test1")
t.name
t.n
输出结果:
调用方法__getattr__: n
属性描述符

        之所以会有描述符的存在,是因为__getattribute____getattr____setattr____delattr__等方法对属性的一般查找逻辑无法满足有效的对属性控制的需求,假如要实现me.age属性的类型设置,单去修改__setattr__满足这个需求,那这个方法便有可能对其他的属性的设置造成影响。在类中设置属性的控制行为不能很好地解决问题,Python给出的方案是:__getattribute____getattr____setattr____delattr__等方法用来实现属性查找、设置、删除的一般逻辑,而对属性的控制行为就由描述符管理。当Test对象访问x时,会自动调用Desc的__get__方法。
        数据描述符:不只定义了__get__方法,还定义了__set__或者__delete__。而非数据描述符,只定义了__get__方法。
以下例子是数据描述符示例:
        实例化Test后,调用对象me访问属性x,会自动调用类Desc的__get__方法,关于__get__方法的参数,self代表Desc实例,即x,obj代表Test的实例me,objtype代表Test这个类。

class Desc(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name
    def __get__(self, obj, objtype):
        print ('Retrieving', self.name)
        return self.val
    def __set__(self, obj, val):
        print ('Updating', self.name)
        self.val = val

class Test(object):
    x = Desc(10, 'var "x"')

me = Test(6)
me.x
输出结果:
Retrieving var "x"

        以上例子中,访问me.x时,会先调用me的__getattribute__()方法,再调用描述符对象的__get__方法。
当实例对象的字典中,有与描述符同名的属性时。如下例子

class Desc(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name
    def __get__(self, obj, objtype):
        print ('Retrieving', self.name)
        return 4
    def __set__(self, obj, val):
        print ('Updating', self.name)
        self.val = val

class Test(object):
    x = Desc(10, 'var "x"')

    def __init__(self,c):
        self.x = c

me = Test(6)
me.x

print(me.x)
输出结果:
Updating var "x"
Retrieving var "x"
Retrieving var "x"
4

        以上例子中,实例化Test时,会先调用描述符 __set_()方法,访问me.x时,会调用描述符 __get_()方法,最后得出me.x的值是4,由此可见,数据描述符存在时,会覆盖掉实例属性,也就是数据描述法的优先级高于实例属性。
那么非数据描述符呢,与实例属性的优先级比,如何:

class Desc(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name
    def __get__(self, obj, objtype):
        print ('Retrieving', self.name)
        return 4

class Test(object):
    x = Desc(10, 'var "x"')

    def __init__(self,c):
        self.x = c
        self.y = Desc(10, 'var "y"')

me = Test(6)
me.x

print(me.x)
输出结果:
6

        以上例子中,描述符__set_()方法和 __get_()都没有调用,说明非数据描述符的优先级低于实例属性的。

属性访问顺序

__dict__

        自定义属性都会有一个字典__dict__,包含了所有的实例属性,不包含实例方法

class Test(object):
    x = 1

    def __init__(self,c):
        self.y = c
    def func(self):
        pass
    @staticmethod
    def static():
        pass
    @classmethod
    def cla(cls):
        pass

me = Test(6)

print("实例的__dict__属性",me.__dict__)
print("类的__dict__属性",Test.__dict__)
输出结果:
实例的__dict__属性 {'y': 6}
类的__dict__属性 {'static': <staticmethod object at 0x0000014701B280F0>, '__init__': <function Test.__init__ at 0x0000014701B21950>, 'cla': <classmethod object at 0x0000014701B28128>, 'func': <function Test.func at 0x0000014701B219D8>, 'x': 1, '__doc__': None, '__dict__': <attribute '__dict__' of 'Test' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Test' objects>}

        实例的__dict__包含了实例属性y,但是不会有实例方法func和类属性x。类的__dict__包含了其余的东西(类属性,实例方法,静态方法,类方法等)。
        发生继承时:

class Test(object):
    x = 1

    def __init__(self,c):
        self.y = c
    def func(self):
        pass
    @staticmethod
    def static():
        pass
    @classmethod
    def cla(cls):
        pass

class Test1(Test):
    z=2
    def __init__(self,c):
        self.a = c
    def func1(self):
        pass
    @staticmethod
    def static1():
        pass
    @classmethod
    def cla1(cls):
        pass

me = Test1(6)

print("子类实例的__dict__属性",me.__dict__)
print("子类的__dict__属性",Test1.__dict__)
输出结果:
子类实例的__dict__属性 {'a': 6}
子类的__dict__属性 {'cla1': <classmethod object at 0x000002198524D4A8>, 'static1': <staticmethod object at 0x000002198524D470>, '__module__': '__main__', 'z': 2, '__init__': <function Test1.__init__ at 0x0000021985241BF8>, 'func1': <function Test1.func1 at 0x0000021985241C80>, '__doc__': None}

可见,每个实例对象和类都有自己的__dict__,互不影响。

属性查找顺序

其实,正常情况下,属性查找都是以一定的规则从__dict__中查找的。
        如果只有类属性x,没有实例属性x,当访问x的时候,会是如何呢?我们来看下面例子:

class Test(object):
    x = 1
    def __init__(self,c):
        pass
me = Test(6)
me.x

print(me.x)
输出结果:
1

        以上例子中,表明当没有实例属性时即差找不到实例属性,会查找类属性。
注意:当类中定义了__slots__属性时,对象就不会有__dict__属性了,这时访问属性时,是通过类似描述符的方式查找属性的。
        一般情况下,python的属性访问机制是:实例属性,类属性,父类属性,object属性;也就是先查找实例对象的__dict__属性,没有的话再查找类__dict__属性,再没有的话查找父类的__dict__属性,最后是基类object属性。当定义了数据描述符时时,会覆盖实例对象的__dict__属性;非数据描述符访问属性时,先查找对象的__dict__属性,没有的话再调用描述符的__get__方法。

总结一下属性查找的顺序:
  1. __getattribute__(), 无条件调用
  2. 数据描述符(优先于实例属性)
  3. 实例对象的字典(与描述符属性同名时,会被覆盖哦)
  4. 非数据描述符(只有__get__()方法)或者类属性
  5. __getattr__() 方法(属性不存在时调用)

如有不当之处,欢迎交流指正!

相关文章

  • python 属性访问顺序 描述符

    概述 了解和熟悉python中的属性访问顺序,有助于我们阅读源码,编写高质量代码,对python机制有个更深的理解...

  • JavaScript 改变对象属性赋值与取值操作的两种姿势

    从 ES5 开始,JavaScript 中每个属性都有相应的属性描述符。属性描述符有多个,可以分为数据描述符与访问...

  • js 对象属性描述符

    ECMAScript对象中⽬前存在的属性描述符主要有两种,数据描述符(数据属性)和存取描述符(访问器属性),数据描...

  • JavaScript基础

    访问描述符(accessor properties)Getter和Setter 一个属性要么是访问属性(acces...

  • python语法入门六

    属性访问 描述符 迭代器 生成器 模块导入 包 查询功能 属性访问 描述符 定制容器 迭代器 生成器 模块导入 f...

  • python中描述符的学习

    什么是描述符 描述符是Python新式类的关键点之一,它为对象属性提供强大的API,你可以认为描述符是表示对象属性...

  • Python 描述符对象 Descriptor Objects

    Reproduce from python描述符(descriptor)、属性(Property)、函数(类)装饰...

  • Python中的属性访问与描述符

    1、实例通过obj.item的形式调用属性: 2、实例对象查找属性或方法的入口getattribute: 3、对象...

  • 定义一个变量

    属性描述符(Property Descriptors) 我们普通的对象属性a的属性描述符(称为“数据描述符”,因为...

  • python属性描述符

    1.对象的自省机制 自省是通过一定的机制查询到对象的内部结构 dir(obj) dir(obj)可以获取一个对象所...

网友评论

    本文标题:python 属性访问顺序 描述符

    本文链接:https://www.haomeiwen.com/subject/qlolsctx.html