
"""
描述器练习,本练习是为了加固对python描述器的了解
下文中有些内容为了方便我直接写成了A,B类。
A代表装饰器类
B代表功能类
本文中实现了类的静态方法,类方法,已经属性装饰器。如下是各种方法的实例思路。
静态方法:
我们在调用静态方法时不论使用实例调用,还是类调用都没有注入效果。
1、fn = Staticmethod(fn),一个类的属性等于另一个类的实例,此模式会触发描述器。
2、在装饰类中定义变量变量fn
3、在__get__魔术方法中返回fn
类方法:
我们在调用类方法时不论使用实例调用,还是类调用都会将类作为第一参数注入。
1、fn = Classmethod(fn),一个类的属性等于另一个类的实例,此模式会触发描述器。
2、在装饰器类中定义变量收集fn
3、因为类方法是有注入效果的,所以我们返回的函数需要是fn(cls)这样的,但是fn的其他参数不变
4、这个场景我想到了偏函数,使用偏函数固定fn的第一参数为cls,cls可以通过__get__魔术方法的owner获取
属性装饰器:
属性装饰器是这几个例子中最难的一个,我试了好久也没思路,准备看下源码咋写的,然后就是pass,pass,pass。后来捋了一下思路准备搞出来了。
1、name = Property(name),name = name.setter(name),name = name.deleter(name),三个等价式先写出来。
2、描述器的触发动作是B类中的属性等于A实例,所以三个等价式对应函数的返回值都需要是A实例
3、理清楚这一点后,我们就需要整理下三个函数实现的效果
4、属性获取装饰器,只是返回查询的值,所以大概率我们只需要使用__get__方法就能实现。实现过程中我们需要注意实例调用还是类调用,区分下即可。
5、属性修改装饰器,这里个人觉得是利用到了零个python的特性,赋值及定义。Person类的属性name并未修改,通过等价式向装饰器类传入了一个叫name
的方法,这个方法和属性获取装饰器中栓传入的函数同名,但是内容已经不一样了。这时候我们需要在装饰器类中定义一个变量接收这个函数,这里我觉得用到了
闭包,原先那个name定义的函数已经消亡了,只是装饰器类中重新定义的变量记住了它。
6、使用B.x = xxx这种方式触发__set__魔术方法,在A类的set方法中使用保存的B类用于修改的方法,这个地方有点绕,简单点就是使用set魔术方法真正
触发属性的重新赋值,这里所需的self(功能类实例)和value都可以获取,直接带入到函数中即可。
7、属性删除装饰器和属性修改装饰器的实现逻辑是一样的,如果不太懂就参考属性修改装饰器再看一遍。
备注:功能类中要求被属性装饰器装饰的属性要名称一样,我觉得这个应该是为了方便操作,即便是不写一样的名字,你记住那个是查的,那个是改的应该也行。
下面就直接上源码了。本人也在学习中,有不对的请指正,有不懂的朋友可以直接问我,大家相互交流。
"""
from functools import partial
class Staticmethod:
def __init__(self, fn):
# print('in init !!!!')
# fn等于,这个方法就是普通的方法
# print(1,fn)
self.fn = fn
def __get__(self, instance, owner):
# print(2,self,instance,owner)
# 根据下下文中的等价式show = Staticmethod(show) = ,
# 不管类调还是实例调都恒等于这个值,没有注入效果。
return self.fn
def __repr__(self):
return ''
class Classmethod:
def __init__(self, fn):
# 同上fn等于display方法,我们不做处理它就是普通方法,注入效果需要我们自己实现
self.fn = fn
def __get__(self, instance, owner):
# print(self,instance,owner)
# self.fn(cls,x) = display(cls,x),此处我们使用偏函数把cls固定下来,然后把固定好的方法返回
return partial(self.fn, owner)
def __repr__(self):
return ''
class Property:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):
# print(1,self, instance, owner)
# property的调用方式是实例.属性直接返回结果,所以这里需要返回的结果是实例调用后的结果。fn是一个实例方法,需要传入self
if instance:
return self.fn(instance)
return self
def __set__(self, instance, value):
# print(2,self,instance,value)
self.setfn(instance,value)
def Setter(self,setfn):
# print(3,setfn)
self.setfn = setfn
return self
def Deleter(self,delfn):
# print(4,delfn)
self.delfn = delfn
return self
def __delete__(self, instance):
# print(5,self,instance)
self.delfn(instance)
class Person:
def __init__(self, name):
self.__name = name
# 先写出等价式show = Staticmethod(show),触发描述器。show等于Staticmethod的__get__方法返回值。这里的self就是一个参数,不带有注入效果
@Staticmethod
def show(self, x):
print('show {} {}'.format(self, x))
# 先写等价式display = Classmethod(display),触发描述器。dispaly等于classmethod的__get__方法返回值。
@Classmethod
def display(cls, x):
print('display {} {}'.format(cls, x))
'''先写等价式name = Property(name),触发描述器,name等于Property的__get__方法返回值。此时的name通过Person类和实例调用返回的值是不一样的,
#常规逻辑下应该类因该返回类的方法,实例应该返回实例的绑定方法。但这个我们可以通过A类的get方法控制,类调用我们返回Property的一个实例,
实例调用我们直接返回方法调用后的结果,效果等同于直接访问属性。
'''
@Property
def name(self):
return self.__name
'''先写等价式name = name.setter(name),__get__,__set__魔术方法的触发条件都是判断B类中的属性等于A实例。所以name.setter(name)的返回值
一定要是一个Property的实例才行。这一步的主要目的是把name对应的方法送到Property的类中,后续采用B.x = xxx的方式赋值时,就会触发A类的
__set__方法,__set__方法再调用已经保存的B类方法进行赋值'''
@name.Setter
def name(self,value):
self.__name = value
#同Setter方法
@name.Deleter
def name(self):
del self.__name
def __repr__(self):
return ''
Person.show(1, 2)
Person('laokoo').show(3, 4)
Person.display(1)
Person('laokoo').display(2)
laokoo = Person('laokoo')
print(Person.name)
print(laokoo.name)
print(laokoo.__dict__)
laokoo.name = 'jack'
print(laokoo.name)
print(laokoo.__dict__)
del laokoo.name
print(laokoo.__dict__)