码迷,mamicode.com
首页 > 编程语言 > 详细

Python之路(十二):描述符,类装饰器,元类

时间:2018-11-16 13:29:09      阅读:180      评论:0      收藏:0      [点我收藏+]

标签:cme   elf   模板   工作流   htm   get   pre   play   执行   

python基础之面向对象(描述符、类装饰器及元类)

 

描述符

描述符(__get__,__set__,__delete__)   # 这里着重描述了python的底层实现原理

  1、 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。
    __get__():调用一个属性时,触发
    __set__():为一个属性赋值时,触发
    __delete__():采用del删除属性时,触发

技术分享图片
技术分享图片
1 class Foo:   #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
2     def __get__(self,instance,owner):
3         print(get方法)
4     def __set__(self, instance, value):
5         print(set方法)
6     def __delete__(self, instance):
7         print(delete方法)
技术分享图片

  2、描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

技术分享图片
技术分享图片
class Foo:
    def __get__(self,instance,owner):
        print(===>get方法)
    def __set__(self, instance, value):
        print(===>set方法)
    def __delete__(self, instance):
        print(===>delete方法)

#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
f1=Foo()
f1.name=egon
print(f1.name)
del f1.name
#疑问:何时,何地,会触发这三个方法的执行
技术分享图片

  3、描述符应用在什么时候,什么地方

技术分享图片
技术分享图片
class D:
    def __get__(self, instance, owner):
        print("-->get")
    def __set__(self, instance, value):
        print("-->set")
    def __delete__(self, instance):
        print("-->delete")
class E:
    e = D() # 描述谁?

ee = E()
ee.y = 10 # 此时描述的是e  y则不会被描述
ee.e      # 访问e属性,则会触发__get__
ee.e = 2  # 为e进行赋值操作,则会触发__set__
del ee.e  # 删除e的属性,则会触发__delete__
# print(ee.__dict__)
技术分享图片

  4、描述符分为俩种形式。

    a.数据描述符(至少实现了__get__()和__set__()两种方法)

技术分享图片
class Foo:
     def __set__(self, instance, value):
         print(set)
     def __get__(self, instance, owner):
         print(get)

    b.非数据描述符(没有实现__set__()方法)

技术分享图片
1 class Foo:
2     def __get__(self, instance, owner):
3         print(get)    

      注意事项:
      一、描述符本身应该定义成新式类,被代理的类也应该是新式类
      二、必须把描述符定义成另外一个类触发的类属性,不能为定义到构造函数

  5、严格遵循描述符的优先级别,由高到低

     a.类属性—》b.数据数据描述符—》c.实例属性—》d.非数据描述符—》e.找不到的属性触发__getattr__()

技术分享图片
技术分享图片
 1 class Foo:
 2     def __get__(self,instance,owner):
 3         print(===>get方法)
 4     def __set__(self, instance, value):
 5         print(===>set方法)
 6     def __delete__(self, instance):
 7         print(===>delete方法)
 8 
 9 class Bar:
10     x=Foo()   #调用foo()属性,会触发get方法
11 
12 print(Bar.x)  #类属性比描述符有更高的优先级,会触发get方法
13 Bar.x=1       #自己定义了一个类属性,并赋值给x,跟描述符没有关系,所以不会触发描述符的方法
14 # print(Bar.__dict__)
15 print(Bar.x)
16 
17 
18 ===>get方法
19 None
20 1
技术分享图片
技术分享图片
技术分享图片
#有get,set就是数据描述符,数据描述符比实例属性有更高的优化级

class Foo:
    def __get__(self,instance,owner):
        print(===>get方法)
    def __set__(self, instance, value):
        print(===>set方法)
    def __delete__(self, instance):
        print(===>delete方法)

class Bar:
    x = Foo()  # 调用foo()属性,会触发get方法

b1=Bar()   #在自己的属性字典里面找,找不到就去类里面找,会触发__get__方法
b1.x       #调用一个属性的时候触发get方法
b1.x=1     #为一个属性赋值的时候触发set方法
del b1.x   #采用del删除属性时触发delete方法

===>get方法
===>set方法
===>delete方法
技术分享图片
技术分享图片
技术分享图片
 1 #类属性>数据描述符>实例属性
 2 
 3 class Foo:
 4     def __get__(self,instance,owner):
 5         print(===>get方法)
 6     def __set__(self, instance, value):
 7         print(===>set方法)
 8     def __delete__(self, instance):
 9         print(===>delete方法)
10 
11 class Bar:
12     x = Foo()             #调用foo()属性,会触发get方法
13 
14 b1=Bar()                  #实例化
15 Bar.x=11111111111111111   #不会触发get方法
16 b1.x                      #会触发get方法
17 
18 del Bar.x                 #已经给删除,所以调用不了!报错:AttributeError: ‘Bar‘ object has no attribute ‘x‘
19 b1.x
技术分享图片
技术分享图片
技术分享图片
#实例属性>非数据描述符
class Foo:
    def __get__(self,instance,owner):
        print(===>get方法)

class Bar:
    x = Foo()

b1=Bar()
b1.x=1
print(b1.__dict__)  #在自己的属性字典里面,{‘x‘: 1}

{x: 1}
技术分享图片
技术分享图片
技术分享图片
 1 #非数据描述符>找不到
 2 
 3 class Foo:
 4     def __get__(self,instance,owner):
 5         print(===>get方法)
 6 
 7 class Bar:
 8     x = Foo()
 9     def __getattr__(self, item):
10         print(------------>)
11 
12 b1=Bar()
13 b1.xxxxxxxxxxxxxxxxxxx    #调用没有的xxxxxxx,就会触发__getattr__方法
14 
15 
16 ------------>    #解发__getattr__方法
技术分享图片

   6、关于描述符的应用(类型检测的应用)

技术分享图片
技术分享图片
class Typed:
    def __get__(self, instance,owner):
        print(get方法)
        print(instance参数【%s】 %instance)
        print(owner参数【%s】 %owner)       # owner是显示对象是属于谁拥有的
    def __set__(self, instance, value):
        print(set方法)
        print(instance参数【%s】 %instance) # instance是被描述类的对象(实例)
        print(value参数【%s】 %value)       # value是被描述的值
    def __delete__(self, instance):
        print(delete方法)
        print(instance参数【%s】% instance)
class People:
    name=Typed()
    def __init__(self,name,age,salary):
        self.name=name        #触发的是代理
        self.age=age
        self.salary=salary

p1=People(alex,13,13.3)
#‘alex‘             #触发set方法
p1.name             #触发get方法,没有返回值
p1.name=age       #触发set方法
print(p1.__dict__)
#{‘salary‘: 13.3, ‘age‘: 13}  # 因为name已经被描述,所以实例的属性字典并不存在name
# 当然也说明一点实例属性的权限并没有数据描述符的权限大


set方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
value参数【alex】
get方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
owner参数【<class __main__.People>】
set方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
value参数【age】
{salary: 13.3, age: 13}
技术分享图片

 

技术分享图片
技术分享图片
class Foo:
    def __init__(self,key,pd_type):
        self.key = key
        self.pd_type = pd_type
    def __get__(self, instance, owner):
        print("get")
        return instance.__dict__[self.key] # 返回值是 instace对象属性字典self.key所对应的值
    def __set__(self, instance, value):
        print(value) # 输出value所对应的值
        if not isinstance(value,self.pd_type): # 判断被描述的值 是否 属于这个类的
            raise TypeError("%s 传入的类型不是%s" %(value,self.pd_type)) # 为否 则抛出类型异常
        instance.__dict__[self.key] = value # True 对instance对象的属性字典进行赋值操作
    def __delete__(self, instance):
        print("delete")
        instance.__dict__.pop(self.key) # 如果进行删除操作,也是对instance对象的属性字典进行删除操作
class Sea:
    name = Foo("name",str)      # 向描述符传入俩个值
    history = Foo("history",int)
    def __init__(self,name,addr,history):
        self.name = name
        self.addr = addr
        self.history = history
s1 = Sea("北冰洋","北半球",10000)
print(s1.__dict__)
print(s1.name) # 对被描述的属性进行访问,触发__get__

北冰洋
10000
{addr: 北半球, history: 10000, name: 北冰洋}
get
北冰洋
技术分享图片

 

技术分享图片 装饰器和描述符实现类型检测的终极版本

  7、描述符总结

      描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

    描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

    a.利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

技术分享图片
技术分享图片
class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @property
    def area(self):
        return self.width * self.length

r1=Room(Tom,1,1)
print(r1.area)
技术分享图片

 

技术分享图片
技术分享图片
# 伪造的property
class Wzproperty:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        """ 如果类去调用 instance 为None"""
        print("get")
        if instance is None: 
            return self
        setattr(instance,self.func.__name__,self.func(instance)) # 给实例字典设置值,避免重复计算
        return self.func(instance)
class Sea:
    def __init__(self,name,history,speed):
        self.name = name
        self.history = history
        self.speed = speed
    @Wzproperty # test = Wzptoperty(test)
    def test(self):
        return self.history * self.speed
s1 = Sea("大西洋",10,20)
# print(Sea.__dict__)
# print(Sea.test) # 如果类去调用 描述符的instance 此时是None
print(s1.test)
print(s1.test) # 这一次就不会触发描述符,因为实例属性字典就有
"""因为有了为实例的属性字典设置了结果。所以会率先从自己的属性字典找
其次触发非数据描述符,同时也声明了实例属性的权限大于非数据描述。
如果给描述符+__set__,描述符就变为数据描述符,根据权限实例再去用不会先去
自己的属性字典,而是触发描述符的操作"""
print(s1.__dict__)

控制台输出
get
200
200
{test: 200, speed: 20, name: 大西洋, history: 10} # 实例的属性字典
技术分享图片

 

技术分享图片
技术分享图片
# 伪造的classmethod
class Wzclassmethod:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        print("get")
        def bar():
            return self.func(owner)  #  test(Sea)
        return bar
    def __set__(self, instance, value):
        print("set")
class Sea:
    long = 10
    kuan = 20
    @Wzclassmethod  # test = Wzclassmethod(test)
    def test(cls):
        print("长%s 宽%s" %(cls.long,cls.kuan))
Sea.test()
技术分享图片

 

技术分享图片
技术分享图片
# 伪造的staticmethod
import hashlib,time
class Wzstaticmethod:
    def __init__(self,func):
        self.func = func
    def __set__(self, instance, value):
        print("set")
    def __get__(self, instance, owner):
        print("get")
        def bar():
            if instance is None:
                return self.func()
        return bar
    def __delete__(self, instance):
        print("delete")
class Onepiece:
    def __init__(self):
        pass
    @Wzstaticmethod # test = Wzstaticmethod(test)
    def test(x=1):
        hash = hashlib.md5()
        hash.update(str(time.time()).encode("utf-8"))
        filename = hash.hexdigest()
        print(filename)
        return filename
# print(Onepiece.__dict__)
Onepiece.test()
技术分享图片

类装饰器

  1、基本框架

技术分享图片
技术分享图片
def deco(func):
    print(===================)
    return func  #fuc=test

@deco    #test=deco(test)
def test():
    print(test函数运行)
test()
技术分享图片

 

技术分享图片
技术分享图片
def deco(obj):
    print(============,obj)
    obj.x=1   #增加属性
    obj.y=2
    obj.z=3
    return obj

@deco   #Foo=deco(Foo)   #@deco语法糖的基本原理
class Foo:
    pass

print(Foo.__dict__)  #加到类的属性字典中

输出
============ <class __main__.Foo>
{__module__: __main__, z: 3, x: 1, __dict__: <attribute __dict__ of Foo objects>, __doc__: None, __weakref__: <attribute __weakref__ of Foo objects>, y: 2}
技术分享图片
技术分享图片
技术分享图片
def Typed(**kwargs):
    def deco(obj):
        obj.x=1
        obj.y=2
        obj.z=3
        return obj
    print(====>,kwargs)
    return deco

@Typed(x=2,y=3,z=4)  #typed(x=2,y=3,z=4)--->deco  会覆盖原有值
class Foo:
    pass
技术分享图片
技术分享图片
技术分享图片
def Typed(**kwargs):
    def deco(obj):
        for key,val in kwargs.items():
            setattr(obj,key,val)
        return obj
    return deco

@Typed(x=1,y=2,z=3)  #typed(x=1,y=2,z=3)--->deco
class Foo:
    pass
print(Foo.__dict__)

@Typed(name=egon)
class Bar:
    pass
print(Bar.name)

控制台输出
{y: 2, __dict__: <attribute __dict__ of Foo objects>, z: 3, __weakref__: <attribute __weakref__ of Foo objects>, __module__: __main__, x: 1, __doc__: None}

egon
技术分享图片

元类

  元类(metaclass)

技术分享图片
class Foo:
     pass
 
f1=Foo() #f1是通过Foo类实例化的对象

  python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)

  上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

技术分享图片
#type函数可以查看类型,也可以用来查看对象的类,二者是一样的
print(type(f1)) # 输出:<class ‘__main__.Foo‘>     表示,obj 对象由Foo类创建
print(type(Foo)) # 输出:<type ‘type‘>  

  1、辣么,什么是元类?

  • 元类是类的类,是类的模板
  • 元类是用来控制如何创建类的,正如类是创建对象的模板一样
  • 元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例Foo类是 type 类的一个实例)
  • type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

  创建类有俩种方式

技术分享图片
class Foo:
    def func(self):
        print(from func)
技术分享图片
技术分享图片
def func(self):
         print(from func)
x=1
Foo=type(Foo,(object,),{func:func,x:1})


type要接收三个参数
1、将要创建的类名
2、继承的类
3、类的属性字典
技术分享图片
技术分享图片
技术分享图片
# 方式1
class Foo:
    pass
# 方式2
Bar = type("Bar",(object,),{})
print(Foo.__dict__)
print(Bar.__dict__)


控制台输出
{__module__: __main__, __doc__: None, __weakref__: <attribute __weakref__ of Foo objects>, __dict__: <attribute __dict__ of Foo objects>}
{__module__: __main__, __doc__: None, __weakref__: <attribute __weakref__ of Bar objects>, __dict__: <attribute __dict__ of Bar objects>}
技术分享图片

  2、一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)

技术分享图片
技术分享图片
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
        print(a)
        print(b)
        print(c)
    def __call__(self, *args, **kwargs):
        print("call")

class Slamdunk(metaclass=Mytype): # Mytype("Slamdunk",(object,),{}) 实际上就是这么做,但是传了4个参数
    # 声明Foo类由Mytype创建,声明自己的元类
    def __init__(self,name):
        self.name = name

s1 = Slamdunk("樱木花道")
# 根据python一切皆对象,Slamdunk() 本质上就是在触发创建 Slamdunk类的 元类的__call__

控制台输出
<class __main__.Slamdunk>  # 元类创建的实例(对象)
Slamdunk # 实例名
() # 继承的类,在python3中都默认继承object,即都为新式类
{__qualname__: Slamdunk, __init__: <function Slamdunk.__init__ at 0x000002106AFBF840>, __module__: __main__} # 实例类的属性字典
call # 实例+() 触发了元类的__call__方法
技术分享图片
技术分享图片
技术分享图片
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
    def __call__(self, *args, **kwargs): # 传的值是怎么传进去的,就去怎么接收
        print("call")
        obj = object.__new__(self) # 生成一个实例
        self.__init__(obj,*args,**kwargs)   # 这里的self是Mytype产生的实例,这一步触发 Slamdunk 的构造方法
        return obj # __call__方法下的返回值是 self 产生的实例 赋值给s1
class Slamdunk(metaclass=Mytype):
    #  Slamdunk = Mytype("Slamdunk",(object,),{}) 实际上就是这么做,但是传了4个参数
    # 声明Foo类由Mytype创建,声明自己的元类
    # 触发元类的__init__(元类的构造方法)
    def __init__(self,name):
        self.name = name
s1 = Slamdunk("樱木花道")
# 根据python一切皆对象,Slamdunk() 本质上就是在触发创建 Slamdunk类的 元类的__call__
print(s1.__dict__) # 可以访问到实例对象的属性字典
技术分享图片

 

技术分享图片
技术分享图片
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
    def __call__(self, *args, **kwargs):
        obj = object.__new__(self)
        self.__init__(obj,*args,**kwargs) 
        return obj 
class Slamdunk(metaclass=Mytype):
    def __init__(self,name):
        self.name = name
s1 = Slamdunk("樱木花道")
print(s1.__dict__) 

控制台输出
<class __main__.Slamdunk>
{name: 樱木花道}

# 可以加断点体验
技术分享图片

python基础之面向对象(描述符、类装饰器及元类)

 

描述符

描述符(__get__,__set__,__delete__)   # 这里着重描述了python的底层实现原理

  1、 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。
    __get__():调用一个属性时,触发
    __set__():为一个属性赋值时,触发
    __delete__():采用del删除属性时,触发

技术分享图片
技术分享图片
1 class Foo:   #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
2     def __get__(self,instance,owner):
3         print(get方法)
4     def __set__(self, instance, value):
5         print(set方法)
6     def __delete__(self, instance):
7         print(delete方法)
技术分享图片

  2、描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

技术分享图片
技术分享图片
class Foo:
    def __get__(self,instance,owner):
        print(===>get方法)
    def __set__(self, instance, value):
        print(===>set方法)
    def __delete__(self, instance):
        print(===>delete方法)

#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
f1=Foo()
f1.name=egon
print(f1.name)
del f1.name
#疑问:何时,何地,会触发这三个方法的执行
技术分享图片

  3、描述符应用在什么时候,什么地方

技术分享图片
技术分享图片
class D:
    def __get__(self, instance, owner):
        print("-->get")
    def __set__(self, instance, value):
        print("-->set")
    def __delete__(self, instance):
        print("-->delete")
class E:
    e = D() # 描述谁?

ee = E()
ee.y = 10 # 此时描述的是e  y则不会被描述
ee.e      # 访问e属性,则会触发__get__
ee.e = 2  # 为e进行赋值操作,则会触发__set__
del ee.e  # 删除e的属性,则会触发__delete__
# print(ee.__dict__)
技术分享图片

  4、描述符分为俩种形式。

    a.数据描述符(至少实现了__get__()和__set__()两种方法)

技术分享图片
class Foo:
     def __set__(self, instance, value):
         print(set)
     def __get__(self, instance, owner):
         print(get)

    b.非数据描述符(没有实现__set__()方法)

技术分享图片
1 class Foo:
2     def __get__(self, instance, owner):
3         print(get)    

      注意事项:
      一、描述符本身应该定义成新式类,被代理的类也应该是新式类
      二、必须把描述符定义成另外一个类触发的类属性,不能为定义到构造函数

  5、严格遵循描述符的优先级别,由高到低

     a.类属性—》b.数据数据描述符—》c.实例属性—》d.非数据描述符—》e.找不到的属性触发__getattr__()

技术分享图片
技术分享图片
 1 class Foo:
 2     def __get__(self,instance,owner):
 3         print(===>get方法)
 4     def __set__(self, instance, value):
 5         print(===>set方法)
 6     def __delete__(self, instance):
 7         print(===>delete方法)
 8 
 9 class Bar:
10     x=Foo()   #调用foo()属性,会触发get方法
11 
12 print(Bar.x)  #类属性比描述符有更高的优先级,会触发get方法
13 Bar.x=1       #自己定义了一个类属性,并赋值给x,跟描述符没有关系,所以不会触发描述符的方法
14 # print(Bar.__dict__)
15 print(Bar.x)
16 
17 
18 ===>get方法
19 None
20 1
技术分享图片
技术分享图片
技术分享图片
#有get,set就是数据描述符,数据描述符比实例属性有更高的优化级

class Foo:
    def __get__(self,instance,owner):
        print(===>get方法)
    def __set__(self, instance, value):
        print(===>set方法)
    def __delete__(self, instance):
        print(===>delete方法)

class Bar:
    x = Foo()  # 调用foo()属性,会触发get方法

b1=Bar()   #在自己的属性字典里面找,找不到就去类里面找,会触发__get__方法
b1.x       #调用一个属性的时候触发get方法
b1.x=1     #为一个属性赋值的时候触发set方法
del b1.x   #采用del删除属性时触发delete方法

===>get方法
===>set方法
===>delete方法
技术分享图片
技术分享图片
技术分享图片
 1 #类属性>数据描述符>实例属性
 2 
 3 class Foo:
 4     def __get__(self,instance,owner):
 5         print(===>get方法)
 6     def __set__(self, instance, value):
 7         print(===>set方法)
 8     def __delete__(self, instance):
 9         print(===>delete方法)
10 
11 class Bar:
12     x = Foo()             #调用foo()属性,会触发get方法
13 
14 b1=Bar()                  #实例化
15 Bar.x=11111111111111111   #不会触发get方法
16 b1.x                      #会触发get方法
17 
18 del Bar.x                 #已经给删除,所以调用不了!报错:AttributeError: ‘Bar‘ object has no attribute ‘x‘
19 b1.x
技术分享图片
技术分享图片
技术分享图片
#实例属性>非数据描述符
class Foo:
    def __get__(self,instance,owner):
        print(===>get方法)

class Bar:
    x = Foo()

b1=Bar()
b1.x=1
print(b1.__dict__)  #在自己的属性字典里面,{‘x‘: 1}

{x: 1}
技术分享图片
技术分享图片
技术分享图片
 1 #非数据描述符>找不到
 2 
 3 class Foo:
 4     def __get__(self,instance,owner):
 5         print(===>get方法)
 6 
 7 class Bar:
 8     x = Foo()
 9     def __getattr__(self, item):
10         print(------------>)
11 
12 b1=Bar()
13 b1.xxxxxxxxxxxxxxxxxxx    #调用没有的xxxxxxx,就会触发__getattr__方法
14 
15 
16 ------------>    #解发__getattr__方法
技术分享图片

   6、关于描述符的应用(类型检测的应用)

技术分享图片
技术分享图片
class Typed:
    def __get__(self, instance,owner):
        print(get方法)
        print(instance参数【%s】 %instance)
        print(owner参数【%s】 %owner)       # owner是显示对象是属于谁拥有的
    def __set__(self, instance, value):
        print(set方法)
        print(instance参数【%s】 %instance) # instance是被描述类的对象(实例)
        print(value参数【%s】 %value)       # value是被描述的值
    def __delete__(self, instance):
        print(delete方法)
        print(instance参数【%s】% instance)
class People:
    name=Typed()
    def __init__(self,name,age,salary):
        self.name=name        #触发的是代理
        self.age=age
        self.salary=salary

p1=People(alex,13,13.3)
#‘alex‘             #触发set方法
p1.name             #触发get方法,没有返回值
p1.name=age       #触发set方法
print(p1.__dict__)
#{‘salary‘: 13.3, ‘age‘: 13}  # 因为name已经被描述,所以实例的属性字典并不存在name
# 当然也说明一点实例属性的权限并没有数据描述符的权限大


set方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
value参数【alex】
get方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
owner参数【<class __main__.People>】
set方法
instance参数【<__main__.People object at 0x000001CECBFF0080>】
value参数【age】
{salary: 13.3, age: 13}
技术分享图片

 

技术分享图片
技术分享图片
class Foo:
    def __init__(self,key,pd_type):
        self.key = key
        self.pd_type = pd_type
    def __get__(self, instance, owner):
        print("get")
        return instance.__dict__[self.key] # 返回值是 instace对象属性字典self.key所对应的值
    def __set__(self, instance, value):
        print(value) # 输出value所对应的值
        if not isinstance(value,self.pd_type): # 判断被描述的值 是否 属于这个类的
            raise TypeError("%s 传入的类型不是%s" %(value,self.pd_type)) # 为否 则抛出类型异常
        instance.__dict__[self.key] = value # True 对instance对象的属性字典进行赋值操作
    def __delete__(self, instance):
        print("delete")
        instance.__dict__.pop(self.key) # 如果进行删除操作,也是对instance对象的属性字典进行删除操作
class Sea:
    name = Foo("name",str)      # 向描述符传入俩个值
    history = Foo("history",int)
    def __init__(self,name,addr,history):
        self.name = name
        self.addr = addr
        self.history = history
s1 = Sea("北冰洋","北半球",10000)
print(s1.__dict__)
print(s1.name) # 对被描述的属性进行访问,触发__get__

北冰洋
10000
{addr: 北半球, history: 10000, name: 北冰洋}
get
北冰洋
技术分享图片

 

技术分享图片 装饰器和描述符实现类型检测的终极版本

  7、描述符总结

      描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

    描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

    a.利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

技术分享图片
技术分享图片
class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @property
    def area(self):
        return self.width * self.length

r1=Room(Tom,1,1)
print(r1.area)
技术分享图片

 

技术分享图片
技术分享图片
# 伪造的property
class Wzproperty:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        """ 如果类去调用 instance 为None"""
        print("get")
        if instance is None: 
            return self
        setattr(instance,self.func.__name__,self.func(instance)) # 给实例字典设置值,避免重复计算
        return self.func(instance)
class Sea:
    def __init__(self,name,history,speed):
        self.name = name
        self.history = history
        self.speed = speed
    @Wzproperty # test = Wzptoperty(test)
    def test(self):
        return self.history * self.speed
s1 = Sea("大西洋",10,20)
# print(Sea.__dict__)
# print(Sea.test) # 如果类去调用 描述符的instance 此时是None
print(s1.test)
print(s1.test) # 这一次就不会触发描述符,因为实例属性字典就有
"""因为有了为实例的属性字典设置了结果。所以会率先从自己的属性字典找
其次触发非数据描述符,同时也声明了实例属性的权限大于非数据描述。
如果给描述符+__set__,描述符就变为数据描述符,根据权限实例再去用不会先去
自己的属性字典,而是触发描述符的操作"""
print(s1.__dict__)

控制台输出
get
200
200
{test: 200, speed: 20, name: 大西洋, history: 10} # 实例的属性字典
技术分享图片

 

技术分享图片
技术分享图片
# 伪造的classmethod
class Wzclassmethod:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        print("get")
        def bar():
            return self.func(owner)  #  test(Sea)
        return bar
    def __set__(self, instance, value):
        print("set")
class Sea:
    long = 10
    kuan = 20
    @Wzclassmethod  # test = Wzclassmethod(test)
    def test(cls):
        print("长%s 宽%s" %(cls.long,cls.kuan))
Sea.test()
技术分享图片

 

技术分享图片
技术分享图片
# 伪造的staticmethod
import hashlib,time
class Wzstaticmethod:
    def __init__(self,func):
        self.func = func
    def __set__(self, instance, value):
        print("set")
    def __get__(self, instance, owner):
        print("get")
        def bar():
            if instance is None:
                return self.func()
        return bar
    def __delete__(self, instance):
        print("delete")
class Onepiece:
    def __init__(self):
        pass
    @Wzstaticmethod # test = Wzstaticmethod(test)
    def test(x=1):
        hash = hashlib.md5()
        hash.update(str(time.time()).encode("utf-8"))
        filename = hash.hexdigest()
        print(filename)
        return filename
# print(Onepiece.__dict__)
Onepiece.test()
技术分享图片

类装饰器

  1、基本框架

技术分享图片
技术分享图片
def deco(func):
    print(===================)
    return func  #fuc=test

@deco    #test=deco(test)
def test():
    print(test函数运行)
test()
技术分享图片

 

技术分享图片
技术分享图片
def deco(obj):
    print(============,obj)
    obj.x=1   #增加属性
    obj.y=2
    obj.z=3
    return obj

@deco   #Foo=deco(Foo)   #@deco语法糖的基本原理
class Foo:
    pass

print(Foo.__dict__)  #加到类的属性字典中

输出
============ <class __main__.Foo>
{__module__: __main__, z: 3, x: 1, __dict__: <attribute __dict__ of Foo objects>, __doc__: None, __weakref__: <attribute __weakref__ of Foo objects>, y: 2}
技术分享图片
技术分享图片
技术分享图片
def Typed(**kwargs):
    def deco(obj):
        obj.x=1
        obj.y=2
        obj.z=3
        return obj
    print(====>,kwargs)
    return deco

@Typed(x=2,y=3,z=4)  #typed(x=2,y=3,z=4)--->deco  会覆盖原有值
class Foo:
    pass
技术分享图片
技术分享图片
技术分享图片
def Typed(**kwargs):
    def deco(obj):
        for key,val in kwargs.items():
            setattr(obj,key,val)
        return obj
    return deco

@Typed(x=1,y=2,z=3)  #typed(x=1,y=2,z=3)--->deco
class Foo:
    pass
print(Foo.__dict__)

@Typed(name=egon)
class Bar:
    pass
print(Bar.name)

控制台输出
{y: 2, __dict__: <attribute __dict__ of Foo objects>, z: 3, __weakref__: <attribute __weakref__ of Foo objects>, __module__: __main__, x: 1, __doc__: None}

egon
技术分享图片

元类

  元类(metaclass)

技术分享图片
class Foo:
     pass
 
f1=Foo() #f1是通过Foo类实例化的对象

  python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)

  上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

技术分享图片
#type函数可以查看类型,也可以用来查看对象的类,二者是一样的
print(type(f1)) # 输出:<class ‘__main__.Foo‘>     表示,obj 对象由Foo类创建
print(type(Foo)) # 输出:<type ‘type‘>  

  1、辣么,什么是元类?

  • 元类是类的类,是类的模板
  • 元类是用来控制如何创建类的,正如类是创建对象的模板一样
  • 元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例Foo类是 type 类的一个实例)
  • type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

  创建类有俩种方式

技术分享图片
class Foo:
    def func(self):
        print(from func)
技术分享图片
技术分享图片
def func(self):
         print(from func)
x=1
Foo=type(Foo,(object,),{func:func,x:1})


type要接收三个参数
1、将要创建的类名
2、继承的类
3、类的属性字典
技术分享图片
技术分享图片
技术分享图片
# 方式1
class Foo:
    pass
# 方式2
Bar = type("Bar",(object,),{})
print(Foo.__dict__)
print(Bar.__dict__)


控制台输出
{__module__: __main__, __doc__: None, __weakref__: <attribute __weakref__ of Foo objects>, __dict__: <attribute __dict__ of Foo objects>}
{__module__: __main__, __doc__: None, __weakref__: <attribute __weakref__ of Bar objects>, __dict__: <attribute __dict__ of Bar objects>}
技术分享图片

  2、一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)

技术分享图片
技术分享图片
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
        print(a)
        print(b)
        print(c)
    def __call__(self, *args, **kwargs):
        print("call")

class Slamdunk(metaclass=Mytype): # Mytype("Slamdunk",(object,),{}) 实际上就是这么做,但是传了4个参数
    # 声明Foo类由Mytype创建,声明自己的元类
    def __init__(self,name):
        self.name = name

s1 = Slamdunk("樱木花道")
# 根据python一切皆对象,Slamdunk() 本质上就是在触发创建 Slamdunk类的 元类的__call__

控制台输出
<class __main__.Slamdunk>  # 元类创建的实例(对象)
Slamdunk # 实例名
() # 继承的类,在python3中都默认继承object,即都为新式类
{__qualname__: Slamdunk, __init__: <function Slamdunk.__init__ at 0x000002106AFBF840>, __module__: __main__} # 实例类的属性字典
call # 实例+() 触发了元类的__call__方法
技术分享图片
技术分享图片
技术分享图片
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
    def __call__(self, *args, **kwargs): # 传的值是怎么传进去的,就去怎么接收
        print("call")
        obj = object.__new__(self) # 生成一个实例
        self.__init__(obj,*args,**kwargs)   # 这里的self是Mytype产生的实例,这一步触发 Slamdunk 的构造方法
        return obj # __call__方法下的返回值是 self 产生的实例 赋值给s1
class Slamdunk(metaclass=Mytype):
    #  Slamdunk = Mytype("Slamdunk",(object,),{}) 实际上就是这么做,但是传了4个参数
    # 声明Foo类由Mytype创建,声明自己的元类
    # 触发元类的__init__(元类的构造方法)
    def __init__(self,name):
        self.name = name
s1 = Slamdunk("樱木花道")
# 根据python一切皆对象,Slamdunk() 本质上就是在触发创建 Slamdunk类的 元类的__call__
print(s1.__dict__) # 可以访问到实例对象的属性字典
技术分享图片

 

技术分享图片
技术分享图片
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
    def __call__(self, *args, **kwargs):
        obj = object.__new__(self)
        self.__init__(obj,*args,**kwargs) 
        return obj 
class Slamdunk(metaclass=Mytype):
    def __init__(self,name):
        self.name = name
s1 = Slamdunk("樱木花道")
print(s1.__dict__) 

控制台输出
<class __main__.Slamdunk>
{name: 樱木花道}

# 可以加断点体验
技术分享图片

Python之路(十二):描述符,类装饰器,元类

标签:cme   elf   模板   工作流   htm   get   pre   play   执行   

原文地址:https://www.cnblogs.com/Miracle-boy/p/9968511.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!