补充:
1、__name__可以获取类名,在python中函数也是一个类,故可以用函数.__name__的方式获取到函数的名字
一、类的装饰器
还记得以前的函数的装饰器吗?是不是基本结构是这样的:
def deco(fun): print('*'*50)
return fun @deco #相当于做了:Foo=deco(Foo) def Foo(): pass
那么类的装饰器又是怎么样的呢?其实和函数的装饰器原理一样。
因为函数也是一个对象不信你试试:
def test(): pass test.x = 1 print(test.__dict__)
def deco(obj): print('*'*50) obj.x = 1 obj.y = 2 return obj @deco #相当于做了:Foo=deco(Foo) class Foo: pass print(Foo.x) #这样就简单的对Foo类进行了装饰
#可上面这不就把添加的值写死了吗?下面这样才是正确的装饰器,用途是给你的类默认添加指定的数据属性:
def Typed(**kwargs): def deco(obj): for key, val in kwargs.items(): #给obj设置数据属性 setattr(obj, key, val) return obj return deco @Typed(x=1,y=2,z=3) #先执行Typed(x=1,y=2,z=3) 得到返回值deco 相当于 --》 @deco class Foo: pass print(Foo.__dict__)
#应用场景:上一章的最后谈到的描述符应用,在类中一个个定义类属性太繁琐,用装饰器就能很好的减少代码重复的问题
class Score: """ 这是关于限制分数格式的描述符 """ def __init__(self, name, type): self._name = name self._type = type def __set__(self, instance, value): if not isinstance(value, self._type): raise TypeError('%s must be %s' %(self._name, self._type)) if isinstance(value, int): if not 0 <= value <= 100: raise ValueError('Valid value must be in [0, 100]') instance.__dict__[self._name] = value def __get__(self, instance, owner): return instance.__dict__[self._name] def __delete__(self, instance): instance.__dict__.pop(self._name) def Typed(**kwargs): def deco(obj): for key, val in kwargs.items(): setattr(obj, key, Score(key, val)) return obj return deco @Typed(name=str, math=int, chinese=int, english=int) class Student: def __init__(self, name, math, chinese, english): self.name = name self.math = math self.chinese = chinese self.english = english def __repr__(self): return "<Student: {}, math: {}, chinese: {}, english: {}>".format( self.name, self.math, self.chinese, self.english ) stu1 = Student('alex', 76, 90, 95) print(stu1) #如果输入不满足分数描述符里的限制 # stu2 = Student(666, 100, 100, 100) # stu3 = Student('alex', 110, 90, 70) # stu4 = Student('alex', '90', 90, 70)
二、自定制property
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod@staticmethod@property,甚至__slots__
先补充一下之前的装饰器知识:
1、装饰器也可以是个类,详见下面例子
2、@XXX这种写法只是对@语句下面的第一个函数或类使用XXX装饰器进行处理
class A: def __init__(self,x): #注意如果装饰器为类,必须能接收参数 self.x = x @A # --> B=A(B) class B: pass @A # --> C=A(C) class C: pass
下面我们就自己做一个property
class Property: def __init__(self, func): self.func = func
def __get__(self, instance, owner): return self.func(instance) #注意这里需要回传对象instance class Residence : def __init__(self,name,length,width): self.name=name self.length=length self.width=width @Property # --> cal_area = Property(cal_area) def cal_area(self): return self.width*self.length p1 = Residence('my home',100,100) print(p1.cal_area)
到这里还有点小瑕疵,如果你用类去调用这个property呢?
系统的property会给你返回property对象,但是上面的代码则没实现这个并且会报错
下面完善它:
class Property: def __init__(self, func): self.func = func def __set__(self, instance, value): instance.__dict__[self.func] = value def __get__(self, instance, owner): if instance is None: #如果这里不判断的话,下面self.func(instance)会报错 #因为此时instance = None return self return self.func(instance) class Residence : def __init__(self,name,length,width): self.name=name self.length=length self.width=width @Property # --> cal_area = Property(cal_area) def cal_area(self): return self.width*self.length print(Residence.cal_area)
接着上面的例子,如果现在有一个场景:我需要反复调用cal_area方法,每次调用都重新运行一次cal_area方法
那我能不能利用装饰器,把cal_area的结果记录下来,然后下次再调用cal_area就直接把记录下的结果传过去呢?
这样是不是大大降低了程序的运算量
在此之上,我们在添加一个延迟计算的功能:
class Property: #不能设置__set__方法,需要的是非数据描述符才优先调用实例的字典 def __init__(self, func): self.func = func def __get__(self, instance, owner): if instance is None: return self res = self.func(instance) setattr(instance, self.func.__name__, res) #把运行结果添加到实例的字典中 return res class Residence : def __init__(self,name,length,width): self.name=name self.length=length self.width=width @Property def cal_area(self): print(' cal_area运行') return self.width*self.length r = Residence('room', 100, 100) print(r.cal_area) #实际只运行了一次,其他的都是调用第一次运行的结果 print(r.__dict__) #结果已经存入字典中 print(r.cal_area) print(r.cal_area)
#下面是property的一个小补充
如果使用@property,虽然是隐藏了你运行的痕迹 r.cal_area() 变成 r.cal_area;但是你怎么往里面传非self里的参数呢?
以下提供两种方式解决这个问题:
class Residence : def __init__(self,name,length,width): self.name=name self.length=length self.width=width @property def cal_area(self): return self.width*self.length @cal_area.setter def cal_area(self, *args): print(*args) return self.width*self.length @cal_area.deleter def cal_area(self): print('del执行') r = Residence('room', 100, 100) r.cal_area = [1,2,3] #触发@cal_area.setter下的cal_area del r.cal_area #触发@cal_area.deleter下的cal_area
OR
class Residence: def __init__(self, name, length, width): self.name = name self.length = length self.width = width def get_cal_area(self): return self.width * self.length def set_cal_area(self, *args): print('set触发,传入值为%s' %args) return self.width * self.length def del_cal_area(self): print('del执行') cal_area = property(get_cal_area, set_cal_area, del_cal_area) # 注意顺序不能乱 r = Residence('room', 100, 100) r.cal_area = [1, 2, 3] # 触发set_cal_area del r.cal_area
以上应用的两个小例子:
class Sell:
def __init__(self, origin_price, discount):
self.origin_price = origin_price
self.discount = discount
@property
def price(self):
return self.origin_price*self.discount
@price.setter
def price(self, val): #这里的val为传进来的值 200
self.origin_price = val
@price.deleter
def price(self):
del self.origin_price
s = Sell(100, 0.8) #触发@property下的 price
print(s.price)
s.price = 200 #触发@price.setter下的 price
print(s.price)
del s.price #触发@price.deleter下的 price
print(s.__dict__)
class Name:
def __init__(self, name):
self.name = name
@property #相当于name=property(name)
def name(self): #相当于是描述符中的 __get__
return self._name
@name.setter #相当于是描述符中的 __set__
def name(self, val): #获取到值
if not isinstance(val, str):
raise AttributeError
self._name = val
@name.deleter #相当于是描述符中的 __deleter__
def name(self):
del self._name
p1 = Name('alex') #触发property --> 触发@name.setter下的name
print(p1.name) #触发property --> 触发@property下的name
#p1.name = 1 #触发property --> 触发@name.setter下的name
del p1.name #触发property --> 触发@name.delete下的name
#自制classmethod、staticmethod
还是按照自制property的老思路来实现
class Classmethod: def __init__(self, func): self.func = func def __get__(self, instance, owner): #利用闭包来完成对cls类的默认添加 def wrapper(name, time): print('这里可以给sleep添加别的功能') return self.func(owner, name, time) return wrapper class Residence : value = 'human' def __init__(self,name,length,width): self.name=name self.length=length self.width=width @Classmethod def sleep(cls, name, time): return '%s %s sleep %s hours' % (cls.value, name, time) print(Residence.sleep('ssy', 10)) p1 = Residence('alex', 10, 10) print(p1.sleep('ssy', 10)) #实例对象也能调用,但是没啥意义
class Staticmethod: def __init__(self, func): self.func = func def __get__(self, instance, owner): return self.func class Residence : value = 'human' def __init__(self,name,length,width): self.name=name self.length=length self.width=width @Staticmethod def text(x,y): #也可以不传参数 return x,y print(Residence.__dict__) p1 = Residence('alex',10,10) print(p1.text(1,2))
# 元类:metaclass
都说python中一切皆对象,那我们定义的类又是由哪个类生成的呢?答案是:元类
元类是类的类,是类的模板;就如普通类是对象的模板一样
type是Python里的一个内建元类,用来直接控制其他类的生成:python中所有用class定义的类都是type实例出来的对象
利用type定义类:
def __init__(self, name): self.name = name def text(self): print('函数属性添加成功') Foo = type('Foo', (object,),{'x':1, '__init__':__init__, 'text':text} ) #第一个位置代表定义的类名 #第二个位置代表继承的类,python3中新式类默认继承了object,元组后面的,结尾代表可以接着继承别的类 #第三个位置代表给这个类的字典里添加属性 print(Foo, Foo.__dict__, Foo.x, sep='\n') f = Foo('alex') print(f.name) f.text()
自定义元类
1、 一个类如果没有声明自己的元类,默认就为type;但是我们也可以通过继承type来自定义元类
2、所有对象都继承了object类,可以调用他的很多方法;如 __new__()方法是用来生成对象的,下面会用到。注意传入的对象必须是type类
class MyType(type): def __init__(self, class_name, inherit_class, attribute): #class_name, inherit_class分别是'Foo', (), #attribute 为Foo自己的字典,不包括继承来的 self.class_name = class_name self.inherit_class = inherit_class self.attribute = attribute def __call__(self, *args, **kwargs): #自己定义__call__, 不用tpye的 # self, args, kwargs
#这几个参数分别为 Foo、实例化传入的值('alex')、{} obj = object.__new__(self)
#--> object.__new__(Foo) 默认用来产生对象的,这里相当于产生了一个Foo的对象赋给obj self.__init__(obj, *args, **kwargs)
#--> Foo.__init__(obj, *args, **kwargs),相当于把属性传入对象字典中 return obj
#--> 下面的f = obj,且'alex'也传入f的字典中 class Foo(metaclass=MyType): #实际做了 MyType('Foo', (), {}) --> MyType.__init__('Foo', (), {}) #同时,所有对象都继承了object类 def __init__(self, name): self.name = name f = Foo('alex') #Foo是MyType生成的对象,会触发MyType下的__call__;没定义__call__的话就默认继承object的; # 这里我指定了继承type的 print(f, f.__dict__, sep='\n')