python-描述器
描述器协议:
- descr.__get__(self, obj, objtype=None)->value
- descr.__set(self, obj, value)->None
- 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

浙公网安备 33010602011771号