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;

另外有需要云服务器可以了解下创新互联cdcxhl.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。


本文题目:33面向对象8_descriptors-创新互联
本文地址:http://scyanting.com/article/ggpcp.html