网站建设资讯

NEWS

网站建设资讯

33面向对象8_descriptors

 

为顺昌等地区用户提供了全套网页设计制作服务,及顺昌网站建设行业解决方案。主营业务为成都做网站、网站建设、顺昌网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!

descriptors描述器:

 

descriptor的表现:

用到3个魔术方法:__get__()、__set__()、__delete__();

object.__get__(self,instance,owner)

object.__set__(self,instance,value)

object.__delete__(self,instance)

self,指代当前实例,调用者;

instance,是owner的实例;

owner,是属性所属的类;

 

py中,一个类实现了__get__()、__set__()、__delete__()三个方法中的任何一个方法,就是描述器;

如果仅实现了__get__(),就是non-data descriptor非数据描述器;

如果同时实现了__get__()、__set__(),就是data descriptor数据描述器,如@property;

如果一个类的类属性设置为描述器,那么这个类它被称为owner属主,如B类中类属性x = A();

关键记住:类属性;

 

注:

当一个类的类属性,是另一个类的实例时,这“另一个类”上有__get__()、__set__()、__delete__()三者之一,它就是个描述器的类,在类属性上访问另一个类的实例时,它就会触发__get__()方法;如果是通过实例的属性访问另一个类的实例self.x = A(),它不会触发__get__()方法;

 

non-data descriptor和data descriptor:

理解1:

如果一个类的属性是一个数据描述器,对实例属性的操作(该实例属性与类属性名相同时)相当于操作类属性;

理解2:

官方是用优先级定义的;

一个类的类属性是一个数据描述器,对该类的实例属性的操作,该类的实例的__dict__优先级降低(数据描述器的优先级高于实例的__dict__);

如果是非数据描述器,则实例的__dict__高于描述器的优先级;

 

属性查找顺序:

实例的__dict__优先于non-data descriptor;

data descriptor优先于实例的__dict__;

__delete__有同样的效果,有此方法就是data descriptor;

 

B.x = 500   #对描述器不能这么用,赋值即定义,直接把类属性覆盖了,注意,不要直接用类来操作,尽管是在类上定义,也要用实例来操作,除非明确知道在干什么

print(B.x)

 

b = B()

b.x = 600   #虽触发了__set__(),未把类属性覆盖,也写不进__dict__中,被__set__()拦截了,对数据起到一定保护作用

 

本质:

查看实例的__dict__可知,data descriptor,实例的__dict__都被__set__()拦住,实例的属性名与类属性名相同时,写不进实例的__dict__中;

原来不是什么data descriptor优先级高,而是把实例的属性从__dict__中给去掉了(实例的属性名与类的属性名相同),造成了该属性如果是data descriptor优先访问的假象,说到底,属性访问的顺序从来就没变过;

 

py的描述器应用非常广泛;

py的所有方法(包括@staticmethod、@classmethod、__init__()),都是non-data descriptor,因此,实例可以重新定义和覆盖方法,这允许单个实例获取与同一类的其它实例不同的行为;

@property类实现是一个data descriptor,因此,实例不能覆盖属性的行为;

 

例:

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)   # X,AttributeError: 'int' object has no attribute 'a1'

输出:

A.__init__

a1

B.__init__

 

例:

class A:

    def __init__(self):

        print('A.__init__')

        self.a1 = 'a1'

 

   def __get__(self, instance, owner):   #类A中定义了__get__(),类A就是一个描述器,对类B的属性x读取,成为对类A的实例的访问就会调用__get__()

       print('A.__get__',self,instance,owner)

        # return self   #解决B.x.a1报错NoneType问题,黑魔法,通过属性描述器来操作属主,拿到属主的类,可动态的改所有属性

class B:

    x = A()   #当一个类的类属性,是另一个类的实例时,这“另一个类”上有__get__()、__set__()、__delete__()三者之一,它就是个描述器的类,在类属性上访问另一个类的实例时,它就会触发__get__()方法

    def __init__(self):

        print('B.__init__')

        # self.x = 100

        self.x = A()   #如果是通过实例的属性访问另一个类的实例self.x = A(),它不会触发__get__()方法

 

print(B.x)  #V,要在类属性上访问,才触发__get__(),该例__get__()方法返回None

# print(B.x.a1)   #X,AttributeError: 'NoneType' object has no attribute 'a1',解决办法:在类A的__get__()添加返回值return self

b = B()

print(b.x)

print(b.x.a1)   #实例属性上访问不会触发__get__()

输出:

A.__init__

A.__get__ <__main__.A object at 0x7f3d53b2bb38> None   #依次为A的实例,None没有类B的实例,类B

None

B.__init__

A.__init__

<__main__.A object at 0x7f3d53b2bba8>

a1

 

例:

class A:

    def __init__(self):

        print('A.__init__')

        self.a1 = 'a1test'

 

    def __get__(self, instance, owner):

        print('A.__get__',self,instance,owner)

        return self

 

    def __set__(self, instance, value):   #有__set__()后,类B的实例b的__dict__为空,只能向上访问类属性的

        print('A.__set__',self,instance,value)

 

class B:

    x = A()

    def __init__(self):

        print('B.__init__')

        # self.x = 100

        self.x = A()

 

print(B.x)

print("*"*20)

print(B.x.a1)

 

print("#"*20)

 

b = B()   #触发__set__()

print("*"*20)

print(b.x)   #数据描述器,对实例属性的操作(该实例属性与类属性的名字相同)相当于操作类属性,查看实例的__dict__(为空)可知(向上找了类属性)

print("*"*20)

print(b.x.a1)

 

print("#"*20)

 

print(b.__dict__)

print(B.__dict__)

 

# B.x = 500   #对描述器不能这么用,赋值即定义,直接把类属性覆盖了,注意,不要直接用类来操作,尽管是在类上定义,也要用实例来操作,除非明确知道在干什么

# print(B.x)

输出:

A.__init__

A.__get__ <__main__.A object at 0x7f63acbcd400> None

<__main__.A object at 0x7f63acbcd400>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> None

a1test

####################

B.__init__

A.__init__

A.__set__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70> <__main__.A object at 0x7f63acbcdba8>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70>

<__main__.A object at 0x7f63acbcd400>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70>

a1test

####################

{}

{'__module__': '__main__', 'x': <__main__.A object at 0x7f63acbcd400>, '__init__': , '__dict__': , '__weakref__': , '__doc__': None}

 

例:

class A:

    def __init__(self):

        print('A.__init__')

        self.a1 = 'a1test'

 

    def __get__(self, instance, owner):

        print('A.__get__',self,instance,owner)

        return self

 

    def __set__(self, instance, value):

        print('A.__set__',self,instance,value)

 

class B:

    x = A()

    def __init__(self):

        print('B.__init__')

        # self.x = 100

        self.x = A()

 

b = B()

print("*"*20)

b.x = 600   #虽触发了__set__(),未把类属性覆盖,也写不进实例的__dict__中(查看实例的__dict__可知),被__set__()拦截了,对数据起到一定保护作用

print("*"*20)

print(b.x)   #调用__get__()

print("*"*20)

print(b.__dict__)

输出:

A.__init__

B.__init__

A.__init__

A.__set__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> <__main__.A object at 0x7f05dd15ebe0>

********************

A.__set__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> 600

********************

A.__get__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8>

<__main__.A object at 0x7f05dd15eb70>

********************

{}

 

 

 

习题:

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

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

3、对实例的数据进行校验;

class Person:

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

        self.name = name

        self.age = age

 

1、

class StaticMethod:

    def __init__(self,fn):

        # print('__init__',fn)

        self.fn = fn

 

    def __get__(self, instance, owner):

        # print('__get__',self,instance,owner)

        return self.fn

 

class A:

    @StaticMethod

    def foo():   #类装饰器装饰完后,原函数消失了,foo=StaticMethod(foo),成为装饰器类的实例了,在类属性上访问另一个类的实例时就会触发__get__()方法

        print('test function')

 

    @staticmethod

    def foo2():

        print('test2 func')

 

f = A.foo

print(f)

f()

A.foo2()

A().foo()

A().foo2()

输出

test function

test2 func

test function

test2 func

 

2、

from functools import partial

 

class ClassMethod:

    def __init__(self,fn):

        print('__init__',fn)

        self.fn = fn

 

    def __get__(self, instance, cls):

        print('__get__', self, instance, cls)

        # return self.fn(cls)   #X,NoneType

        return partial(self.fn, cls)   #固定下来,返回一个新函数

 

class A:

    @ClassMethod

    def bar(cls):

        print(cls.__name__)

 

# print(A.bar)

# print()

A.bar()

print()

A().bar()

print()

print(A.__dict__)

输出:

__init__

__get__ <__main__.ClassMethod object at 0x7f2999039d68> None

A

 

__get__ <__main__.ClassMethod object at 0x7f2999039d68> <__main__.A object at 0x7f2999039da0>

A

 

{'__module__': '__main__', 'bar': <__main__.ClassMethod object at 0x7f2999039d68>, '__dict__': , '__weakref__': , '__doc__': None}

 

3、

class Typed:

    def __init__(self,type):

        self.type = type

 

    def __get__(self, instance, owner):

        pass

 

    def __set__(self, instance, value):

        print('T.__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('tom',18)

 

3、

改进:用装饰器+描述器,py中大量使用;

import inspect

 

 

class Typed:

    def __init__(self, type):

        self.type = type

 

    def __get__(self, instance, owner):

        pass

 

    def __set__(self, instance, value):

        print('set', self, instance, value)

        if not isinstance(value, self.type):

            raise ValueError(value)

 

 

class TypeAssert:

    def __init__(self, cls):

        self.cls = cls

        params = inspect.signature(self.cls).parameters

        # print(params)

        for name, param in params.items():

            print(name, param.annotation)

            if param.annotation != param.empty:

                setattr(self.cls, name, Typed(param.annotation))   #动态类属性注入

 

    def __call__(self, name, age):

        # params = inspect.signature(self.cls).parameters

        # print(params)

        # for name,param in params.items():

        #     print(name,param.annotation)

        #     if param.annotation != param.empty:

        #         setattr(self.cls,name,Typed(param.annotation))

        p = self.cls(name, age)

        return p

 

 

@TypeAssert

class Person:

    # name = Typed(str)   #动态类属性注入

    # age = Typed(age)

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

        self.name = name

        self.age = age

 

 

p1 = Person('jerry', 18)

p2 = Person('tom', 16)

print(id(p1))

print(id(p2))

输出:

name

age

set <__main__.Typed object at 0x7f596a121da0> <__main__.Person object at 0x7f596a121dd8> jerry

set <__main__.Typed object at 0x7f596a121cf8> <__main__.Person object at 0x7f596a121dd8> 18

set <__main__.Typed object at 0x7f596a121da0> <__main__.Person object at 0x7f596a0af940> tom

set <__main__.Typed object at 0x7f596a121cf8> <__main__.Person object at 0x7f596a0af940> 16

140022008389080

140022007920960

 

 

 

习题:

1、将链表,封装成容器:

要求:

1)提供__getitem__()、__iter__()、__setitem__();

2)使用一个列表,辅助完成上面的方法;

3)进阶:不使用列表,完成上面的方法;

 

2、实现类property装饰器,类名称为Property;

 

 

 


分享文章:33面向对象8_descriptors
文章地址:http://njwzjz.com/article/gcdshh.html