Python 面向对象(五) 描述器

使用到了__get__,__set__,__delete__中的任何一种方法的类就是描述器 

 描述器的定义

一个类实现了__get__,__set__,__delete__中任意一个,这个类就是描述器。

如果只实现了__get__,就叫非数据描述器non-data descriptor

如果同时实现了__get__,__set__,那就叫数据描述器data descriptor。

 如果一个类的类属性的值设置为了描述器,那这个类就称为owner(属主)。

  

回顾下就会发现我们曾用过@property(property是一个类)来将一个方法转为属性,其实property就是一个描述器。

class A:
    def __init__(self):
        self.a1 = 'a1'

class B:
    x = A()
    def __init__(self):
        pass

print(B.x.a1)
------运行结果-----
a1

  看下执行流程

class A:
    def __init__(self):
        print('A.init')
        self.a1 = 'a1'

class B:
    x = A()
    def __init__(self):
        print('B.init')
        pass

print(B.x.a1)
-----运行结果———
A.init
a1       # B根本就没有实例化

  一步一步加,慢慢观察实例的初始化顺序

class A:
    def __init__(self):
        print('A.init')
        self.a1 = 'a1'

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.x = 100

print(B.x.a1)

b = B()
print(B.x.a1)
print(b.x.a1)    # 抛出下面的AttributeError异常,因为按属性的mro搜索顺序来说,b.x也就是实例的x=100,100没有a1属性
-----运行结果-----
A.init
a1
B.init
a1
Traceback (most recent call last):
  File "C:/python/1117/description_test1.py", line 16, in <module>
    print(b.x.a1)
AttributeError: 'int' object has no attribute 'a1'

  实例中可以覆盖非数据描述器

class A:
    def __init__(self):
        print('A.init')
        self.a1 = 'a1'
    def __get__(self, instance, owner):
        print(self,instance,owner)

class B:
    x = A()
    def __init__(self):
        print('B.init')
        # self.x = 100

# print(B.x.a1)
print(B.x)  # 返回的是NOne

b = B()
print(B.x)
print(b.x)   #描述器跟类属性有关系
———运行结果——————————
A.init
<__main__.A object at 0x00000271CBDA1668> None <class '__main__.B'>
None
B.init
<__main__.A object at 0x00000271CBDA1668> None <class '__main__.B'>
None
<__main__.A object at 0x00000271CBDA1668> <__main__.B object at 0x00000271CBDA18D0> <class '__main__.B'>
None

  

class A:
    def __init__(self):
        print('A.init')
        self.a1 = 'a1'
    def __get__(self, instance, owner):
        print(self,instance,owner)

class B:
    x = A()
    def __init__(self):
        print('B.init')
        # self.x = 100
        self.x= A()

# print(B.x.a1)
# print(B.x)  # 返回的是NOne

b = B()
print(B.x)
print(b.x)   #描述器跟类属性有关系
print(b.x.a1)
————运行结果—————
A.init
B.init
A.init
<__main__.A object at 0x000001A6D1031668> None <class '__main__.B'>
None
<__main__.A object at 0x000001A6D1031860>
a1

  

第一种情况:
A类的类属性的值等于B类的实例,且B类实现了__get__,__set__,__delete__三种方法中任意一个时,也就是说B类是一个描述器,就会触发定义的方法(如__get__)

第二种情况:
如果A类的实例的值等于B类的实例,即使B类是一个描述器,也不会触发其中的三种方法

__set__

class A:
    def __init__(self):
        print('A.init')
        self.a1 = 'a1'
    def __get__(self, instance, owner):
        print(self,instance,owner)
        return self
    def __set__(self, instance, value):
        print(self,instance,value)

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.x = 100   #实例的属性
        # self.x= A()


b = B()
print(B.x)
print(b.x.a1)

print(b.__dict__)
print(B.__dict__)
——————运行结果——————
A.init
B.init
<__main__.A object at 0x00000193299127F0> <__main__.B object at 0x00000193299128D0> 100
<__main__.A object at 0x00000193299127F0> None <class '__main__.B'>
<__main__.A object at 0x00000193299127F0>
<__main__.A object at 0x00000193299127F0> <__main__.B object at 0x00000193299128D0> <class '__main__.B'>
a1
{}   #添加了__set__方法之后,b实例的字典没有任何属性
{'__module__': '__main__', '__doc__': None, '__dict__': <attribute '__dict__' of 'B' objects>, '__init__': <function B.__init__ at 0x0000019329902E18>, '__weakref__': <attribute '__weakref__' of 'B' objects>, 'x': <__main__.A object at 0x00000193299127F0>}

  添加了一个__set__方法后结果就发生了变化。

结论:

如果一个类的类属性是一个数据描述器的话,你对它的同名属性的操作,相当于操作类属性。

官方的说明是说:

如果一个类的类属性是一个数据描述器的话,在实例中定义了同名的属性时,类属性的查找顺序优先级优于实例属性。

然而,我们在上例中打印的__dict__字典中可以看到,根本不是什么优先级关系,实例的字典中根本就不存在同名的属性,同名的属性只存在于类的__dict__字典中。

所以实例的属性名与类属性名同名时,其实就是在操作类属性。

在实例中定义一个不同名的属性举例测试下:

class A:
    def __init__(self):
        print('A.init')
        self.a1 = 'a1'
    def __get__(self, instance, owner):
        print(self,instance,owner)
        return self
    def __set__(self, instance, value):
        print(self,instance,value)

class B:
    x = A()
    def __init__(self):
        print('B.init')
        self.y = 100  #不与类属性同名的属性 
        # self.x= A()


b = B()
print(B.x)
print(b.x.a1)
print(b.y)

print(b.__dict__)
print(B.__dict__)
————运行结果—————
A.init
B.init
<__main__.A object at 0x0000012BEE4C17F0> None <class '__main__.B'>
<__main__.A object at 0x0000012BEE4C17F0>
<__main__.A object at 0x0000012BEE4C17F0> <__main__.B object at 0x0000012BEE4C18D0> <class '__main__.B'>
a1
100
{'y': 100}  #不与类属性同名时,不会改变属性搜索顺序
{'__init__': <function B.__init__ at 0x0000012BEE4B2E18>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'B' objects>, 'x': <__main__.A object at 0x0000012BEE4C17F0>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'B' objects>}

  

练习:

1.实现StaticMethod装饰器,完成staticmethod装饰器的功能

2.实现ClassMethod装饰器,完成classmethod装饰器的功能

from functools import partial

class StaticMethod:  #防止冲突改名
    def __init__(self,fn):
        self.fn = fn
    def __get__(self, instance, owner):
        print(self,instance,owner)
        return self.fn

class ClassMethod:
    def __init__(self,fn):
        print(fn)
        self.fn = fn
    def __get__(self, instance, owner):
        print(self,instance,owner)
        # return self.fn(owner)
        return partial(self.fn,owner)  #partail偏函数固定某个参数,就返回了一个newfunction新函数

class A:
    @StaticMethod
    def foo(): #foo = StaticMethod(foo)
        print('foo')
    @ClassMethod
    def bar(cls): #bar=ClassMethod(bar)
        print(cls.__name__)

f = A.foo
print(f)
f()
print('~~~~~~~~~~~~~')
b = A.bar
print(b)
b()   #需要调用必须是函数
-----------运行结果---------
<function A.bar at 0x000001956B9D4840>
<__main__.StaticMethod object at 0x000001956B8728D0> None <class '__main__.A'>
<function A.foo at 0x000001956B9D47B8>
foo
~~~~~~~~~~~~~
<__main__.ClassMethod object at 0x000001956B872940> None <class '__main__.A'>
functools.partial(<function A.bar at 0x000001956B9D4840>, <class '__main__.A'>)
A

  

与对象绑定的叫方法
与function绑定的就叫函数

普通函数就用@staticmethod
                      def stcmth()
不想传参就用静态方法


类的方法就用@classmethod
                      def clsmtd(cls)
类或实例的类(解释器隐含a.__class__)传入cls
传入的是类或实例的类
类方法即使不实例化,通过类也可以直接调用。
实例化之后通过实例也可以调用。

传参时对参数类型进行检查:

第一种方法:

传统方法,在__init__时做条件判断

class Person:
    def __init__(self,name:str,age:int):
        if not self.checkdata(((name,str),(age,int))):
            raise TypeError()
        self.name = name
        self.age = age
    def checkdata(self,params):
        print(params)
        for dt,tp in params:
            # print(dt,tp)
            if not isinstance(dt,tp):  #第一种
                return False
        else:
            return True
        #     if type(dt) == tp:  #第二种
        #         continue
        #     else:
        #         return False
        # else:
        #     return True


p1 = Person('zhangsan',18)  #当传入类型不符时抛出TypeError异常,比如传入'18'字符串格式的age就会触发异常
print(p1.__dict__)
print(p1.name)
print(p1.age)
————运行结果————————
(('zhangsan', <class 'str'>), (18, <class 'int'>))
{'name': 'zhangsan', 'age': 18}
zhangsan
18

  

第二种:
使用数据描述器

class Typed:
    def __init__(self):
        pass

    def __get__(self, instance, owner):
        pass

    def __set__(self, instance, value):
        print('Typed.__set__:',self,instance,value)

class Person:
    name = Typed()
    age = Typed()

    def __init__(self,name:str,age:int):
        self.name = name
        self.age = age

p1 = Person('jerry',18)
————运行结果——————
Typed.__set__: <__main__.Typed object at 0x000002852A70FA58> <__main__.Person object at 0x000002852A7217B8> jerry
Typed.__set__: <__main__.Typed object at 0x000002852A721710> <__main__.Person object at 0x000002852A7217B8> 18

  

将类型作为参数传入Typed类,在__set__方法中做检查:

class Typed:
    def __init__(self,type):
        self.type = type
        pass

    def __get__(self, instance, owner):
        pass

    def __set__(self, instance, value):
        print('Typed.__set__:',self,instance,value)
        if not isinstance(value,self.type):
            raise ValueError(value)

class Person:
    name = Typed(str)
    age = Typed(int)

    def __init__(self,name:str,age:int):
        self.name = name
        self.age = age

p1 = Person('jerry',18)   #运行正常,说明传入的参数类型正确
------运行结果-------
Typed.__set__: <__main__.Typed object at 0x00000218FDBEFA58> <__main__.Person object at 0x00000218FDC01668> jerry
Typed.__set__: <__main__.Typed object at 0x00000218FDC01390> <__main__.Person object at 0x00000218FDC01668> 18

  当传入的参数不正确时就抛出ValueError异常:

p1 = Person('jerry','18')
———运行结果—————
Typed.__set__: <__main__.Typed object at 0x00000261222116A0> <__main__.Person object at 0x0000026122211710> jerry
Traceback (most recent call last):
Typed.__set__: <__main__.Typed object at 0x0000026122211668> <__main__.Person object at 0x0000026122211710> 18
  File "C:/python/1117/params_check_test3.py", line 23, in <module>
    p1 = Person('jerry','18')
  File "C:/python/1117/params_check_test3.py", line 21, in __init__
    self.age = age
  File "C:/python/1117/params_check_test3.py", line 13, in __set__
    raise ValueError(value)
ValueError: 18

  在设置值时,Typed类__set__方法拦截到之后判断传入的value和type类型是否一致。

第三种:

装饰器加数据描述器

class Typed:
    def __init__(self,name,type):
        self.name = name
        self.type = type

    def __get__(self, instance, owner):
        if instance is not None:
            return instance.__dict__[self.name]
        return self

    def __set__(self, instance, value):
        print(self,instance,value)
        if not isinstance(value,self.type):
            raise ValueError(value)
        instance.__dict__[self.name] = value
        # return self


import inspect
def typeassert(cls):
    params = inspect.signature(cls).parameters
    print(params)
    for name,param in params.items():
        print(param.name,param.annotation)
        if param.annotation != param.empty:
            setattr(cls,name,Typed(name,param.annotation))
    return cls

@typeassert
class Person:
    # name = Typed(str)
    # age = Typed(int)

    def __init__(self,name:str,age:int):
        self.name = name
        self.age = age

p1 = Person('zhangsan',19)
print(p1.__dict__)
print(p1.name,p1.age)
——————运行结果————————
OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])
name <class 'str'>
age <class 'int'>
<__main__.Typed object at 0x000001E818D91828> <__main__.Person object at 0x000001E818D91898> zhangsan
<__main__.Typed object at 0x000001E818D91940> <__main__.Person object at 0x000001E818D91898> 19
{'name': 'zhangsan', 'age': 19}
zhangsan 19

  

原文地址:https://www.cnblogs.com/i-honey/p/7862627.html