欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

python描述符和属性查找

程序员文章站 2022-04-14 15:29:42
python描述符 定义 一般说来,描述符是一种访问对象属性时候的绑定行为,如果这个对象属性定义了 ,`__set__() __delete__()`一种或者几种,那么就称之为描述符。描述符在属性查找的时候可以覆盖默认的属性查找行为。 如果一个对象定义了 和`__set__() __get__()` ......

python描述符

定义

一般说来,描述符是一种访问对象属性时候的绑定行为,如果这个对象属性定义了__get__(),__set__(), and __delete__()一种或者几种,那么就称之为描述符。描述符在属性查找的时候可以覆盖默认的属性查找行为。
如果一个对象定义了__get__()__set__()方法,那么称之为数据描述符,如果只定义了__get__()称之为非数据描述符。

描述符调用

描述符可以直接通过方法名调用,如d.__get__(obj)。也可以通过属性查找调用,如:obj.d,如果d定义了__get__(),那么就会调用d.__get__(obj)
描述符的调用实现定义在object.__getattribute__()方法中:

  • 对于实例对象b,将b.x转为type(b).__dict__['x'].__get__(b, type(b))
  • 对于类b,将b.x转为b.__dict__['x'].__get__(none, b)

__getattribute__()用纯python写的话类似下面:

def __getattribute__(self, key):
    "emulate type_getattro() in objects/typeobject.c"
    v = object.__getattribute__(self, key)
    if hasattr(v, '__get__'):
        return v.__get__(none, self)
    return v

注意点:

  1. 描述符由__getattribute__()方法调用
  2. 重载__getattribute__()方法可以屏蔽描述符的调用
  3. 描述符只能在新式类(即objects的子类)中才会起作用
  4. object.__getattribute__()type.__getattribute__()调用__get__()过程不同
  5. 属性查找时数据描述符总是覆盖实例属性(具体在中解释)
  6. 非数据描述符可能被实例属性覆盖

实例属性指的是对于实例b,存在于b.__dict__中的属性。b.__dict__只包含有实例特有的属性,不包含类共有的属性。

描述符应用

properties

property可以将一个类的方法构建成数据描述符,从而可以使得以类的属性调用的方式调用方法。
例如:

class c(object):
    def getx(self): return self.__x
    def setx(self, value): self.__x = value
    def delx(self): del self.__x
    x = property(getx, setx, delx, "i'm the 'x' property."

从而调用c.x隐式调用c.getx()
如果property用纯python写,那么等价于:

class property(object):
    "emulate pyproperty_type() in objects/descrobject.c"

    def __init__(self, fget=none, fset=none, fdel=none, doc=none):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is none and fget is not none:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=none):
        if obj is none:
            return self
        if self.fget is none:
            raise attributeerror("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is none:
            raise attributeerror("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is none:
            raise attributeerror("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

函数和方法

在python中,一切皆是对象。python的方法也不例外,所有python函数均是function这个类的实例,function定义了__get__()方法(使用type(方法).__dict__可以得到__get__这个属性),也就是所有函数均为非数据描述符。根据是实例调用还是类调用,描述符返回绑定方法和非绑定方法。

>>> class d(object):
...     def f(self, x):
...         return x
...
>>> d = d()
>>> d.__dict__['f']  # stored internally as a function
<function f at 0x00c45070>
>>> d.f              # get from a class becomes an unbound method
<unbound method d.f>
>>> d.f              # get from an instance becomes a bound method
<bound method d.f of <__main__.d object at 0x00b18c90>>

绑定方法:实例对象调用方法时候会传出一个self参数,将这个实例对象与方法绑定。
非绑定方法:类直接调用方法属性
将function类用纯python写,等价如下:

class function(object):
    . . .
    def __get__(self, obj, objtype=none):
        "simulate func_descr_get() in objects/funcobject.c"
        return types.methodtype(self, obj, objtype)

type.methodtype()手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建.

static method类和class method类

两者都是使用非数据描述符的方式构建描述符。
若使用纯python写,等价如下:

class staticmethod(object):
    "emulate pystaticmethod_type() in objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=none):
        return self.f
class classmethod(object):
    "emulate pyclassmethod_type() in objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=none):
        if klass is none:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

python属性查找

如果有obj = clz(), 那么obj.attr查找顺序如下:

  1. 调用__getattribute_
  2. 如果attr是出现在clz或其基类的__dict__中,且attr是data descriptor, 那么调用其__get__方法
  3. 如果attr出现在obj的__dict__中, 那么直接返回obj.__dict__['attr']
  4. 如果attr出现在clz或其基类的__dict__中
    4.1 如果attr是non-data descriptor,那么调用其__get__方法
    4.2 返回__dict__['attr']
  5. 如果clz有__getattr__方法,调用__getattr__方法,否则
  6. 抛出attributeerror

属性查找中__getattribute_方法是无论如何都会被调用的,且从python描述符调用章节可知__getattribute_实际上实现了描述符的调用。如果类同时定义了__getattr__方法,只有在__getattribute_显式调用__getattr__或者__getattribute_抛出attributeerror才会调用__getattr__

注意如果要重载__getattribute_方法,需要显式调用object.__getattribute__(self, name)以防止无限循环.
例如:

 class test(object):
    t = 5
    def __getattribute__(self, item):
        if item=='t':
            return super(test, self).__getattribute__(item)
        return 6
t = test()
print t.t
print t.s
结果为:
5
6

python属性赋值

python通过对象的__setattr__方法赋值,也可以通过setattr方法,事实上setattr就是通过调用__setattr__完成赋值。属性赋值也会受到数据描述符影响。
举例来说:x.b=3,属性赋值过程如下:

  1. 如果类重载了__setattr__方法,优先调用__setattr__
  2. 如果没有重载__setattr__方法,且b是数据描述符,就会调用b.__set__()
  3. 如果以上都没有,调用obj.__dict__['b'] = 3__setattr__方法的默认行为)

推测跟object.__getattribute__()一样,object.__setattr__()中实现了数据描述符的调用。

参考资料