python-描述器

描述器协议:

  1. descr.__get__(self, obj, objtype=None)->value
  2. descr.__set(self, obj, value)->None
  3. descr.__delete(self, obj)->None
    实现了上述任意一个方法的类称为描述器,作为另外一个类的类属性时覆盖其行为。
    定义了set或delete的描述器称为数据描述器,仅定义get的描述器称为非数据描述器。
    描述器必须作为类的属性才可以发挥其作用。
    当使用"."运算符调用一个属性时,如果该属性是一个描述器讲影响其行为。

优先级:数据描述器 》__dict__ 》非数据描述器get
__dict__优先级大于非数据描述器(只定义get)

# __dict__优先级大于非数据描述器(只定义get)
class A:
    # 非数据描述器
    def __get__(self, obj, objtype):
        return 'A'

class B:
    name = A()

b = B()
print(B.name)  # A
print(b.name)  # A  __dict__中没有name,
print(b.__dict__)  # {}
b.name = 'B'   # __dict__中添加name
print(B.name) # A
print(b.name) # B
print(b.__dict__)  # {'name': 'B'}

数据描述优先级大于_dict_

class A:
    def __get__(self, obj, objtype):
        return 'A'
    
    def __set__(self, obj, value):
        pass 


class B:
    name = A()

b = B()
print(B.name)  # A
print(b.name)  # A
print(b.__dict__)  # {}
b.name = 'B'  # 会调用描述器的set方法并不会给对象b添加属性
print(B.name) # A
print(b.name) # A   数据描述器的优先级最高
print(b.__dict__)  # {}

__set_name__(self, owner, name)

class A:

    def __set_name__(self, owner, name):
        """
        owner: 描述器拥有者,类
        name: 拥有描述器的类给与描述器的名
        在创建所属类owner时发生
        """
        print(f"{owner}:{name}")
        # 描述器的一种用法是托管对实例数据的访问
        # 描述器是类的公开属性,当通过实例调用该属性时,访问的是实例的私有属性
        self.private_name = '_'+name 

    def __get__(self, obj, obj_type=None):
        """
        obj: 实例
        obj_type: 实例类型,是哪一类
        """
        if not obj:
            # 在使用类调用描述器时,obj为None
            return 'A'
        value = getattr(obj, self.private_name)
        return value 

    def __set__(self, obj, value):
        """
        赋值时调用
        """
        setattr(obj,self.private_name,value)

class B:
    x = A()  # 在创建类时会调用__set_name__(self, owner, name)方法
    # 不是在创建对象时调用

    def __init__(self, x=1) -> None:
        self.x = x

b = B()
print(B.x)
print(b.x)
print(b.__dict__)
b.x = 2
print(B.x)
print(b.x)
print(b.__dict__)
print(B.__dict__)
"""
<class '__main__.B'>:x
A
1
{'_x': 1}  # b的实际属性是_x,并不包含x属性,但是调用x属性时会调用到描述器的get方法
A
2
{'_x': 2}
{'__module__': '__main__', 'x': <__main__.A object at 0x0000025D75FB0A90>, '__init__': <function B.__init__ at 0x0000025D7620F430>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
"""

描述器的应用

类的属性、绑定方法、静态方法、类方法、__slots__都是基于描述器协议实现

方法和常规函数的不同只是第一个参数是对象实例,而且不用提供,默认传递。
用python来描述其运行原理如下:

# python等价实现
class MethodType:
    """
    方法类(等价实现)
    接受一个函数和一个实例对象两个参数进行创建
    """
    def __init__(self, func, obj):
        """
        func: 函数
        obj: 实例
        """
        self.func = func 
        self.obj = obj 
    def __call__(self, *args, **kw):
        """调用方法时把实例放在第一个位置"""
        return self.func(self.obj, *args, **kw)

绑定方法

class Function:
    """
    函数类, 函数类即一个非数据描述器
    get方法把函数转换为一个方法
    """
    def __init__(self) -> None:
        pass 

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self 
        # self即函数,obj即实例
        return MethodType(self, obj)


class A:
    def func(self, *args,**kw):
        """func是类属性, 当实例调用func时, 
        因为函数类是一个描述器,会调用其__get__方法
        返回该函数func(类调用), 实例调用则会把A的实例绑定到func的第一个参数self上
        """
        pass 
        

绑定方法是通过描述器实现,每一个函数类型都是一个描述器,当通过实例调用时,会把实例绑定到第一个参数上。

# 方法绑定运行逻辑如下
class FuncDescr:
    def __init__(self, func) -> None:
        self.func = func 

    def __get__(self, obj, objtype=None):
        return MethodType(self.func, obj)

def func(self, *args, **kw):
    print(args)
    print(kw)

class B:
    f = FuncDescr(func)

b = B()
b.f('descriptor', x=1)
"""
('descriptor',)
{'x': 1}

"""
class C:
    @staticmethod
    def f():
        pass 
    @classmethod
    def g(cls):
        pass 

    def h(self):
        pass 
print(C.__dict__['f'])
print(C.__dict__['g'])
print(C.__dict__['h'])
# <staticmethod object at 0x000001F9CE615DC0>
# <classmethod object at 0x000001F9CE615E20>
# <function C.h at 0x000001F9CE61F790>

静态方法

静态方法返回底层函数不做任何更改

class StaticMethod:
    def __init__(self, f) -> None:
        self.f = f 
    
    def __get__(self, obj, objtype):
        return self.f 

    def __call__(self, *args, **kw):
        return self.f(*args, **kw)

def s_f():
    print("static")
class D:
    f = StaticMethod(s_f)

d = D()
d.f()
D.f()
# static

类方法

class ClassMethod:
    def __init__(self, f) -> None:
        self.f = f

    def __get__(self, obj, objtype=None):
        if objtype is None:
            objtype = type(obj)
        
        return MethodType(self.f, objtype)


def c_f(cls):
    print(cls.name)

class E:
    name = 'E'
    f = ClassMethod(c_f)

e = E()
e.f()
E.f()

__slots__

__slots__是用来限制类的属性的,可以提高运行速度节省空间。
__slots__也是凭借描述器协议实现的,数据描述器的优先级是高于object.__dict__的(当object.__dict__中无同名属性)所以可以提高运行速度,当找到对应描述器即可返回,而不需要再寻找object.dict。如果是在object.__dict__为了O(1)复杂度找到属性,总会多申请一些空间来保存数据,而slots是保存在一个固定的位置,描述器中包含了slots属性的保存位置可以直接根据这个位置获取数据。
slots的运行逻辑用python模拟一个粗糙版

class Slot:
    def __init__(self):
        self.name = ''
        self.data = None
    
    def __set_name(self, owner, name):
        self.name=name 
    
    def __get__(self, obj, objtype=None):
        value = self.data 
        return value 
    
    def __set__(self, obj, value):
        self.data = value 


class F:
    # 粗糙版,原理大致类似
    x = Slot()
    y = Slot()
    slot_names = ['x', 'y']

    def __setattr__(self, name,value) -> None:
        if name not in self.slot_names:
            raise AttributeError(f"{type(self).__name__} object has not attribute{name}")
        super().__setattr__(name, value)
    

f = F()
print(f.__dict__)
print(F.__dict__)
f.x=1
f.y=2
print(f.x, f.y)  # 1,2
f.z=3  # AttributeError
        
posted @ 2022-08-07 22:11  店里最会撒谎白玉汤  阅读(104)  评论(0)    收藏  举报