python类的高级使用
python类
子类化内置类型
python里有一个祖先类object的内置类型,他是所有内置类型的共同祖先,也是所有没有指定父类的自定义类的祖先。当需要实现与某个内置类型具有相似行为的类时,最好使用子类化内置类型。
例如,我们想使用dict,但我们不希望有相同的值存在字典中,这时我们就自定义一个类
class MyDictError(ValueError):
""""有相同值时报错"""
class Mydict(dict):
def setitem(self, key, value):
if value in self.values():
if(key in self and self[key]!=value) or key not in self:
raise MyDictError('不能有相同值')
super().__setitem__(key,value)
my=Mydict()
my['key']='value'
my['other_key']='value'
报错:
raise MyDictError('不能有相同值')
main.MyDictError: 不能有相同值
正确的写法
my=Mydict()
my['key']='value'
my['other_key']='value1'
print(my)
输出
{'key': 'value', 'other_key': 'value1'}
访问超类中的方法
super是一个内置类,可用于访问属于某个对象的超类的属性,就是使用super可以调用父类的方法
class Mama:
def says(self):
print('do your homework')
class Sister(Mama):
def says(self):
Mama.says(self)
print('and clean your bedroom')
s=Sister()
s.says()
class Sister(Mama):
def says(self):
super(Sister,self).says()
print('and clean your bedroom')
class Sister(Mama):
def says(self):
super().says()
print('and clean your bedroom')
如果super不在方法内部使用,那必须给出参数
s=Sister()
super(s.__class__,s).says()
当super只提供了一个参数,那么super返回的是一个未绑定类型,与classmethod一起使用特别合适,@classmethod类方法只能访问类变量。不能访问实例变量。
class Pizza:
def __init__(self,toppings):
self.toppints=toppings
def __repr__(self):
return "Pizza with "+" and ".join(self.toppints)
@classmethod
def recomend(cls):
return cls(['spam','ham','eggs'])
class VikingPizza(Pizza):
@classmethod
def recomend(cls):
recomended=super(VikingPizza,cls).recomend()
print(type(recomended))
recomended.toppints+=['spam']*5
return recomended
print(Pizza.recomend())
print(VikingPizza.recomend())
python的方法解析顺序
python方法解析顺序是基于C3,C3是一个类的线性化(也叫优先级,即祖先的有序列表),这个列表用于属性查找。C3序列化会挑选最接近的祖先的方法:
class CommonBase:
def method(self):
print('CommonBase')
class Base1(CommonBase):
pass
class Base2(CommonBase):
def method(self):
print('Base2')
class MyClass(Base1,Base2):
pass
MyClass.__mro__
(, , , , )
>>> MyClass.mro()
[, , , , ]
类的mro属性保存了线性化的计算结果
使用super犯得错误
混合super与显示类的调用
class A(object):
def __init__(self):
print("A"," ")
super().__init__()
class B(object):
def __init__(self):
print("B"," ")
super().__init__()
class C(A,B):
def __init__(self):
print("C"," ")
A.__init__(self)
B.__init__(self)
C()
print(C.mro())
输出结果
C
A
B
B
[, , , ]
当C调用A.init(self)时,super(A,self).init()调用了B.init方法,产生了错误信息
不同种类的参数
这个问题就是在使用super初始化过程中传递参数,如果没有相同的签名,就是传递的参数个数不同,会报错
__author__ = 'Mr.Bool'
class CommonBase:
def __init__(self):
print('CommonBase')
super().__init__()
class Base1(CommonBase):
def __init__(self):
print('Base1')
super().__init__()
class Base2(CommonBase):
def __init__(self):
print('Base2')
super().__init__()
class MyClass(Base1,Base2):
def __init__(self,arg):
print('my base')
super().__init__(arg)
MyClass(10)
#使用super().__init__(arg)时,父类初始化没有传递参数,触发TypeError错误,解决方案就是使用魔法参数包装构造方法
class CommonBase:
def __init__(self,*args,**kwargs):
print('CommonBase')
super().__init__()
class Base1(CommonBase):
def __init__(self,*args,**kwargs):
print('Base1')
super().__init__()
class Base2(CommonBase):
def __init__(self,*args,**kwargs):
print('Base2')
super().__init__()
class MyClass(Base1,Base2):
def __init__(self,arg):
print('my base')
super().__init__(arg)
MyClass(10)
#这样做是解决了问题,但代码变得太过脆弱,因为使得构造函数可以接受任何参数,显示使用特定类的__init__()调用可以解决这个问题,但会引发混合super与显示类的调用冲突
编写类的一些好的实现
- 应该避免多重继承
- super使用必须一致,不能混合使用super和传统调用
- 无论python2还是3都应该显示继承object
- 调用父类之前使用mro查看类的层次结构
描述符
描述符允许自定义在一个引用对象的属性身上,描述符是实现复杂属性访问的基础,描述符本身是你自定义的一个类,定义了另一个类的属性的访问方式
定义描述符类基于3个特殊方法,3个方法组成描述符协议
1. set(self,obj,type=None):在设置属性时调用这一方法
2. get(self,obj,value):在读取属性时调用这一方法
3. delete(self,obj):对属性调用del时将调用这一方法
实现了1,2两个方法的描述符类被称为数据描述符,只实现了2的描述符类被称为非数据描述符
python中类对象的示例都有一个特殊方法getattribute(),每次调用示例对象的属性或方法都会先调用这个方法,这个方法下的属性查找顺序是:
1. 属性是否为实例的类对象的数据描述符
2. 查看该属性是否在对象的dict中
3. 查看该属性是否为实例的类对象的非数据描述符
优先级为1,2,3
class RevealAccess(object):
"""一个数据描述符,正常设定值并返回值,同时打印记录访问的信息"""
def __init__(self,initval=None,name="var"):
self.val=initval
self.name=name
def __get__(self, instance, owner):
print('调用',self.name)
print(instance)
print(owner)
return self.val
def __set__(self, instance, value):
print('更新',self.name)
self.val=value
class MyClass(object):
x=RevealAccess(10,'var "x"')
y=5
m=MyClass()
print(m.x)
m.x=20
m.y=10
print(m.__dict__)
#输出结果
调用 var "x"
<__main__.MyClass object at 0x000001D42CB11F60>
10
更新 var "x"
{'y': 10}
这样看就明朗了,dict没有x值,所以被数据描述符或非数据描述符修饰的属性,不会在dict中显示出来,每次查找属性是都会调用描述符类的get()方法并返回它的值,每次对属性赋值都会调用set()
描述符的使用场景
描述符可以将类属性的初始化延迟到被实例访问时,如果这些属性的初始化依赖全局应用上下文或者初始化代价很大,可以使用描述符解决
class InitOnAccess:
def __init__(self,klass,*args,**kwargs):
self.klass=klass
self.args=args
self.kwargs=kwargs
self._initialized=None
def __get__(self,instance,owner):
if self._initialized is None:
print("已初始化的")
self._initialized=self.klass(*self.args,**self.kwargs)
else:
print('内存中')
return self._initialized
class MyClass:
lazy_init=InitOnAccess(list,"argument")
m=MyClass()
m.lazy_init
m.lazy_init
#输出结果
已初始化的
内存中
property
property是一个描述符类,它可以将一个属性链接到一组方法上,property接收4个可选参数:fget、fset、fdel和doc,函数参数顺序就是这个,最后一个参数可以用来链接到属性的doc文档
class Rectangle:
def __init__(self,x1,y1,x2,y2):
self.x1,self.x2=x1,x2
self.y1,self.y2=y1,y2
def _width_get(self):
return self.x2-self.x1
def _width_set(self,value):
self.x2=self.x1+value
def _height_get(self):
return self.y2-self.y1
def _height_set(self,value):
self.y2=self.y1+value
width=property(_width_get,_width_set,doc="矩形宽度")
height=property(_height_get,_height_set,doc='矩形高度')
def __repr__(self):
return "{}({},{},{},{})".format(self.__class__.__name__,self.x1,self.x2,self.y1,self.y2)
r=Rectangle(10,10,25,34)
print(r.width,r.height)
help(Rectangle)
r.width=100
print(r)
#输出
15 24
Help on class Rectangle in module __main__:
class Rectangle(builtins.object)
| Methods defined here:
|
| __init__(self, x1, y1, x2, y2)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| height
| 矩形高度
|
| width
| 矩形宽度
Rectangle(10,110,10,34)
property在使用类的继承时,所创建的属性时利用当前类的方法实时创建,不会使用派生类中覆写的方法
class MyRectangle(Rectangle):
def _width_get(self):
return "{} 米".format(self.x2-self.x1)
print(Rectangle(10,10,25,34).width)
print(MyRectangle(10,10,25,34).width)
#输出结果
15
15
解决这个问题,需要在派生类中覆写整个property
class MyRectangle(Rectangle):
def _width_get(self):
return "{} 米".format(self.x2-self.x1)
width=property(_width_get,Rectangle.width.fset,doc="矩形宽度")
print(Rectangle(10,10,25,34).width)
print(MyRectangle(10,10,25,34).width)
#输出结果
15
15 米
以上存在一个问题就是写派生类很麻烦容易出错,所以使用property的最佳语法是使用property作装饰器
class Rectangle:
def __init__(self,x1,y1,x2,y2):
self.x1,self.x2=x1,x2
self.y1,self.y2=y1,y2
@property
def width(self):
"矩形宽度"
return self.x2-self.x1
@width.setter
def width(self,value):
self.x2=self.x1+value
@property
def height(self):
"矩形高度"
return self.y2-self.y1
@height.setter
def height(self,value):
self.y2=self.y1+value
def __repr__(self):
return "{}({},{},{},{})".format(self.__class__.__name__,self.x1,self.x2,self.y1,self.y2)
class MyRectangle(Rectangle):
def width(self):
return "{} 米".format(self.x2-self.x1)
print(MyRectangle(0,0,10,10).width())
元编程
元编程是一种编写计算机程序的技术,这些程序看作数据,你可以在运行时进行修改,生成和内省
元编程的两种主要方法:
1. 专注对基本元素内省的能力与实时创造和修改的能力,最简单的工具是修饰器,允许向现有函数、方法或类中添加附加功能。
2. 类的特殊方法,允许修改类实例的创建过程,最强大的工具为元类
第一种装饰器
这里只说说类装饰器,不大为人熟知,请看如下实例
def short_repr(cls):
cls.__repr__=lambda self:super(cls,self).__repr__()[:12]
return cls
@short_repr
class ClassWithLongName:
pass
xcv
print(ClassWithLongName())
#输出<__main__.Cl
上面实例展示出了类的好几种特性
1. 在运行时可以修改实例,也可以修改类对象,ClassWithLongName的方法被修改了 2. 函数也是描述符,根据描述符协议,可以在属性查找时执行实际绑定的,添加到类中,repr添加到ClassWithLongName中
3. super可以在类定义作用域外使用,传入参数要正确
4. 类装饰器可以用于类的定义
修改上上面装饰器
def short_repr(max_width=12):
"""缩短表示的参数化装饰器"""
def short(cls):
"""内部包装函数,是实际的装饰器"""
class ShortName(cls):
"""提供装饰器行为的子类"""
def __repr__(self):
return super().__repr__()[:max_width]
return ShortName
return short
不过是用上面的装饰器也会出现name,doc元数据发生变化的情况,这个不能使用wrap装饰器修改
new方法
因为new方法在init方法调用之前,所以使用new()方法可以覆写实例的创建过程,覆写new()的实现将会使用合适的参数调用器超类的super().new(),并返回之前修改实例
class InstanceCountClass:
instance_created=0
def __new__(cls, *args, **kwargs):
print('__new__() 调用',cls,args,kwargs)
instance=super().__new__(cls)
instance.number=cls.instance_created
cls.instance_created+=1
return instance
def __init__(self,attr):
print('__init__()调用',self,attr)
self.attr=attr
i1=InstanceCountClass('abc')
i2=InstanceCountClass('xyz')
print(i1.number,i1.instance_created)
print(i2.number,i2.instance_created)
#输出
__new__() 调用 ('abc',) {}
__init__()调用 <__main__.InstanceCountClass object at 0x000001D9B29C1F98> abc
__new__() 调用 ('xyz',) {}
__init__()调用 <__main__.InstanceCountClass object at 0x000001D9B29C8400> xyz
0 2
1 2
不调用init()方法
class NoInit(int):
def __new__(cls,v):
return super().__new__(cls,v) if v!=0 else None
def __init__(self,value):
print('调用__init__')
super().__init__()
print(type(NoInit(1)))
print(type(NoInit(-1)))
print(type(NoInit(0)))
#输出
调用__init__
调用__init__