类----来源于cookbook第八章
记录第八章中比较有意思的部分
示例的字符串表示
定义魔法方法__repr__(self)
和__str__(self)
其中前者尽可能描述eval(repr(x)) == x
,后者描述一下该对象的作用
1 2 3 4 5 6 7 8
| class c: def __init__(self): self.x : int = 1 def __repr__(self): return "{0.x}".format(self)
|
格式化代码{0.x}用于指代0的属性x
"{0.x}".format(self)中0的含义就是self
定义魔法方法__format__(self, code)
即可
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Data: def __init__(self): self.x = 1
def __format__(self, code): code = "the x is {}" if code == "" else code return code.format(self.x)
d = Data() print(format(d))
|
上下文管理协议
定义魔法方法__enter__(self)
和__exit__(self, exc_ty, exc_val, tb)
即可
- 当遇到
with
语句时,__enter__()
首先被触发,其返回值会被放在with..as..
中的as
里
- 然后开始执行
with
之中的代码
- 最后
__exit__()
函数被调用用于清理,其三个参数分别代表异常类型、异常值和对挂起的异常的追溯。当它返回True
时,表示异常已经被清理干净
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Con: def __init__(self): ... def __enter__(self): print("enter !") return [1,2] def __exit__(self, exc_ty, exc_val, tb): print(exc_ty, exc_val, tb) print("exit !") return True
with Con() as c: for i in c: print(i) raise Exception("end")
print("test over")
|
节省内存的方法
只需要为类中增加__slot__
这个类属性,就能大大减少内存占用
当存在这个属性的时候,会使用一种更加紧凑的方式进行表示,不再为每个实例创建__dict__字典,而是一个固定长度的小数组
所以只要包含这个属性,就没法动态为类增加删除属性
1 2 3 4 5 6 7 8 9
| class Data: __slots__ = ["year", "month", "day"]
def __init__(self, year, month, day): self.year = year self.month = month self.day = day
|
可管理的属性
使用@property
来管理一个属性,可以为值的获取、修改和删除增加额外的限制
此时这个类方法已经类似于属性了,不能被调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Data: def __init__(self, first): self.first = first @property def first(self): print("get !") return self._first
@first.setter def first(self, value): print("set !") self._first = value
@first.deleter def first(self): print("delete !") del self._first
a = Data(123) print(a.first) a.first = 1 del a.first
|
注意类中__init__中的初始化的方式,如果不这样写,会导致在初始化时不进行检查
本质是将一系列方法绑定到一起,property的属性fget, fset,
fdel分别对应了绑定的方法
super调用父类的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class A: def __init__(self): print("A init")
def pri(self): print("A")
class B(A): def __init__(self): print("B init") super().__init__()
def pri(self): print("B") super().pri()
b = B() b.pri()
|
super()
函数可以避免多态继承时重复调用父类构造函数的问题。
其本质是基于方法解析顺序表(MRO),MRO将多个构造函数线性排列,可以使用class.__mro__
查看
super()
函数本身可以带两个参数,具体而言为super(subclass, self)
,含义为搜索subclass
的父类,并返回父类的代理对象,而self
这是在调用代理对象的方式时被传递。
比如一般都是这样调用super(subclass, self).method()
,这会调用subclass
的父类的method
方法,且将self
传递进去。
描述符
可以使用描述符来设计一种通用的数据结构,且可以对数据进行检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Integer: def __init__(self, name): self.name = name
def __get__(self, instance, cls): if instance is None: return self else: return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value, int): raise TypeError("not a int") instance.__dict__[self.name] = value def __del__(self, instance): del instance.__dict__[self.name]
class Point: x = Integer("x") y = Integer("y") def __init__(self, x, y): self.x = x self.y = y
|
描述符定义就像上面Integer
所示,感觉有点奇怪,下面进行解释。
首先注意到在使用描述符的类中(此处为Point
类),变量声明不是位于任何函数中,而是在类中声明,即在类的层次上定义。
对于一个实例p = Point(1,2)
因为可以看到,从类中调用描述符和从实例中调用描述符,传入的instance
变量是不同的;且这两者调用从功能上来说也本应该是不同的(从类中调用没有实际意义,因为变量还没被初始化),所以在get
和set
中的处理方法也是不同的
属性惰性求值
本质上是利用描述符中类调用和实例调用存在差异的特性来实现的
即利用一个属性重写同名方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class lazy: def __init__(self, func): self.func = func
def __get__(self, instance, cls): if instance is None: return self else: value = self.func(instance) setattr(instance, self.func.__name__, value) return value
class circle: def __init__(self, r): self.r = r @lazy def area(self): print("computing...") return 3.14*self.r**2
c = circle(2) print(c.r) print(c.__dict__) print(c.area) print(c.__dict__) print(c.area)
|
纯虚基类
使用abc
模块的ABCMeta
、abstractmethod
来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from abc import ABCMeta, abstractmethod
class IStream(metaclass = ABCMeta): @abstractmethod def read(self, maxbytes = -1): ... @abstractmethod def write(self, data): ... @property @abstractmethod def name(self): ...
|
abstractmethod
可以跟其他装饰器一起使用,但是必须是abstractmethod
紧靠函数