跳转至

第6章 面向对象编程进阶

6.1 类的定制

  • 特殊属性和特殊方法
  • 又称为魔法属性或魔法方法
  • 协议
  • 一组非强制性的特殊方法

6.1.1 常用特殊属性

__dict__

  • __dict__是一个属性字典
  • 多数数据类型都有自己的__dict__属性
  • 模块:模块中定义的函数、类、变量等构成的字典
  • 函数:初始状态为空字典
  • 类:普通方法、类方法、静态方法、类属性
  • 对象:实例属性
1
2
def test_dict():
    pass
1
test_dict.__dict__
1
{}
1
2
test_dict.x = 0
test_dict.__dict__
1
{'x': 0}
1
2
3
4
class TestDict:
    x_class = 0
    def __init__(self):
        self.x_obj = 1
1
TestDict.__dict__
1
2
3
4
5
6
mappingproxy({'__module__': '__main__',
              'x_class': 0,
              '__init__': <function __main__.TestDict.__init__(self)>,
              '__dict__': <attribute '__dict__' of 'TestDict' objects>,
              '__weakref__': <attribute '__weakref__' of 'TestDict' objects>,
              '__doc__': None})
1
2
td = TestDict()
td.__dict__
1
{'x_obj': 1}
  • 派生类的__dict__不会继承基类的__dict__属性,它们的__dict___是独立的
1
2
class SubTestDict(TestDict):
    y_class = 2
1
SubTestDict.__dict__
1
mappingproxy({'__module__': '__main__', 'y_class': 2, '__doc__': None})
  • 一些内置数据类型的实例没有__dict__属性
  • listdictsettuple
1
2
l = []
l.__dict__
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-10-95dfd23436f0> in <module>
      1 l = []
----> 2 l.__dict__


AttributeError: 'list' object has no attribute '__dict__'

__base____bases__

  • __base__
  • 当前类的基类
  • 多重继承的情况下是第一个基类
  • __bases__
  • 当前类的所有基类构成的元组

__slots__

  • 用于指定一组实例属性名称,实例属性的名字只能来自其中
  • 类中包含__slots__属性时
  • 利用固定大小的数组来存储实例属性
  • 实例不再有__dict__属性
  • 能够节约内存空间
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class TestSlot1:
    def __init__(self):
        self.x = 0
        self.y = 1

class TestSlot2:
    __slots__ = ['x', 'y']
    def __init__(self):
        self.x = 0
        self.y = 1
1
2
3
ts1 = TestSlot1()
ts2 = TestSlot2()
ts1.__dict__
1
{'x': 0, 'y': 1}
1
ts2.m=0
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-5-96e2e2204de7> in <module>
----> 1 ts2.m=0


AttributeError: 'TestSlot2' object has no attribute 'm'
  • 不在__slots__中的属性不能被动态绑定
  • 用于限制实例的动态绑定
1
2
def fun(self):
    pass
1
ts1.fun = fun
1
ts2.m = fun
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-16-192314586c86> in <module>
----> 1 ts2.fun = fun


AttributeError: 'TestSlot2' object has no attribute 'fun'

6.1.2 对象运算(运算符重载)

  • Python中运算符的的执行过程实际上是调用了对象的特殊方法
  • 例如,+运算符相当于__add__方法,-运算符相当于__sub__方法
  • 在类定义中根据需要实现这些方法,其实例之间就能够利用运算符进行运算
1
x, y = 1, 2
1
2
x + y
x.__add__(y)
1
3
1
2
x - y
x.__sub__(y)
1
-1
 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 PointwiseVector:
    def __init__(self, value):
        self.v = value

    def check_len(self, o):
        if len(o.v) != len(self.v):
            return False
        return True

    def __add__(self, o):
        if not self.check_len(o):
            return None
        return self.__class__([v1 + v2 for v1, v2 in zip(self.v, o.v)])

    def __sub__(self, o):
        if not self.check_len(o):
            return None
        return self.__class__([v1 - v2 for v1, v2 in zip(self.v, o.v)])

    def __mul__(self, o):
        if not self.check_len(o):
            return None
        return self.__class__([v1 * v2 for v1, v2 in zip(self.v, o.v)])

    def __truediv__(self, o):
        if not self.check_len(o):
            return None
        return self.__class__([v1 / v2 for v1, v2 in zip(self.v, o.v)])
1
2
o1 = PointwiseVector([1, 2, 3])
o2 = PointwiseVector([1, 2, 3])
1
(o1 + o2).v
1
[2, 4, 6]
1
(o1 - o2).v
1
[0, 0, 0]
1
(o1 * o2).v
1
[1, 4, 9]
1
(o1 / o2).v
1
[1.0, 1.0, 1.0]
  • 常用运算符的特殊方法
运算符 特殊方法 作用
+-*/** __add____sub____mul____truediv____pow__ 算术运算符
%// __mod____floordiv__ 取余、整除
+x-x __pos____neg__ 正、负
>>=<<===!= __gt____ge____lt____le____eq____ne__ 比较运算符
+=-=*=/= __iadd____isub____imul____idiv__ 增强运算符
  • 反向的特殊方法
  • 当两个对象利用双目运算符时,首先检查左侧的对象是否实现了相应的特殊方法,如果有则调用左侧对象中的方法;否则,检查右侧的对象是否定义反向的特殊方法
  • 所有双目运算符都有对应的反向特殊方法
    • __add__的反向特殊方法为__radd__
    • __sub__的反向特殊方法为__rsub__
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Left:
    def __init__(self, v):
        self.v = v

class Right:
    def __init__(self, v):
        self.v = v

    def __radd__(self, o):
        return self.v + o.v

    def __rsub__(self, o):
        return o.v -  self.v
1
2
l = Left(2)
r = Right(1)
1
l + r
1
3
1
r + l
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-31-dee0167d7375> in <module>
----> 1 r + l


TypeError: unsupported operand type(s) for +: 'Right' and 'Left'

6.1.3 对象描述

  • print函数打印至输出终端
  • __str__
  • str(obj)
  • 在交互式环境中显示对象
  • __repr__
  • repr(obj)
1
pv = PointwiseVector([1, 2, 3])
1
print(pv) 
1
<__main__.PointwiseVector object at 0x10ee99c10>
1
pv
1
<__main__.PointwiseVector at 0x10ee99c10>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class PointwiseVector:
    def __init__(self, value):
        self.v = value

    # 略去其他方法的定义

    def __str__(self):
        print('__str__方法被调用')
        return str(self.v)

    def __repr__(self):
        print('__repr__方法被调用')
        return str(self.v)
1
pv = PointwiseVector([1, 2, 3])
1
print(pv)
1
2
__str__方法被调用
[1, 2, 3]
1
str(pv)
1
2
3
4
5
6
7
__str__方法被调用





'[1, 2, 3]'
1
pv
1
2
3
4
5
6
7
__repr__方法被调用





[1, 2, 3]
1
repr(pv)
1
2
3
4
5
6
7
__repr__方法被调用





'[1, 2, 3]'

6.1.4对象成员访问控制

  • __开头的属性具有一定的隐藏性,但并非真正的私有化
  • 特殊方法
  • __getattr__:当试图访问一个对象中不存在的属性时触发该方法;
  • __getattribute__:当试图访问对象属性(包括不存在的属性)时;
  • __setattr__:当试图为对象绑定属性时触发该方法;
  • __delattr__:当试图销毁对象属性时触发该方法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class TestAttrAccess:
    def __getattr__(self, name):
        print(f"属性 {name} 不存在!")
        return None

    def __setattr__(self, name, value):
        print("设置了一个数值")
        self.__dict__[name] = value

    def __delattr__(self, name):
        print("删除了一个数值")
        del self.__dict__[name]
1
taa = TestAttrAccess()
1
taa.x
1
属性 x 不存在!
1
2
taa.x = 10
taa.x
1
2
3
4
5
6
7
设置了一个数值





10
1
2
del taa.x
taa.x
1
2
删除了一个数值
属性 x 不存在!
  • __getattribute__
  • 只要试图访问对象的属性就会被触发
  • 类中定义了__getattribute__方法时,__getattr__方法就会失效
1
2
3
4
5
6
class TestAttrAccess:
    def __getattr__(self, name):
        print(f"属性 {name} 不存在!")

    def __getattribute__(self, name):
        print(f'属性{name}被访问')
1
2
taa = TestAttrAccess()
taa.x
1
属性x被访问
  • 无限递归调用风险
  • __getattr__中试图访问对象中不存在的属性时
  • __getattribute__中试图访问对象中的任何属性时
  • 解决办法
  • 通过访问父类属性
  • 使用一个代理对象

6.1.5 描述器

  • 描述器是实现了__get____set____delete__三个方法中的一个或多个的类的实例
  • 描述器一般不会单独使用,最为常见的情况是将描述器作为其他类的属性

  • 描述的工作原理

  • 默认情况下,在访问一个对象的属性时,解释器会依次查找该对象的__dict__属性、 所属类的__dict__属性、父类的__dict__属性......,直到找到所要访问的属性
  • 当找到的属性是一个描述器时,并不会直接返回该描述器,而是会调 用描述器中的__get____set____delete__方法

  • __get__(self, obj, type):当读取描述器中的值时触发,其中obj是描述器所属 的类的实例,type是描述器所属的类

  • __set__(self, obj, value):当向描述器写入值时触发
  • __delete__(self, obj):当销毁描述器时触发

  • 定义了 __set____delete__方法的对象称为数据描述器,仅定义了__get__方法 的描述器称为非数据描述器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Name:
    def __init__(self, family_name, given_name):
        self.family_name = family_name
        self.given_name = given_name

    def __get__(self, obj, type):
        return f'{self.given_name} {self.family_name}'

    def __set__(self, obj, value):
        self.family_name = value[0]
        self.given_name = value[1]

class Person:
    name = Name('Rossum', 'Guido')
1
2
p = Person()
p.name
1
'Guido Rossum'
1
2
p.name = ('Gates', 'Bill')
p.name
1
'Bill Gates'

6.1.6 可调用对象

  • 可调用对象
  • 函数
  • labmda表达式
  • 类方法
  • __call__
  • 如果一个类中定义了特殊函数__call__,那么它的实例也是可调用对象
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from functools import singledispatchmethod

class Averager:
    @singledispatchmethod
    def read(self, data):
        n = len(data)
        self.value = sum(data)/n

    @read.register(dict)
    def _(self, data):
        data = data.values()
        n = len(data)
        self.value = sum(data)/n

    @read.register(str)
    def _(self, data):
        data = [float(i) for i in data.split(',')]
        n = len(data)
        self.value = sum(data)/n

    def __call__(self, data):   # 可调用对象
        self.read(data)
        return self.value
1
2
3
4
5
6
7
avg = Averager()
avg([1, 2, 3, 4])

avg('1, 2, 3, 4')


Averager()('1, 2, 3, 4')
1
2.5
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 例:数据归一化

class TestCall:
    def __init__(self, xmax, xmin):
        self.xmax, self.xmin = xmax, xmin

    def __call__(self, x):
        return (x - self.xmin)/(self.xmax - self.xmin)

TestCall(180, 150)(170)
1
0.6666666666666666

6.1.7 容器

  • 容器
  • 能够持有其他对象
  • 具有读取、保存、删除元素、返回其中保存的元素的数量的方法
  • 容器特殊方法
  • __getitem__:使用[]读取保存在容器中的元素时触发;
  • __setitem__:修改容器中的元素时触发;
  • __delitem__:删除容器中的元素时触发;
  • __len__ :使用len函数获取容器中的元素数量时触发;
  • __contains__:使用innot in判断元素是否包含在容器中;
  • __reversed__(self):当容器中元素有序时,使用reversed函数反转元素的顺序时触发;
 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
class MyList:
    def __init__(self, values=None):
        if values is None:
            self.values = []
        else:
            self.values = list(values)

    def __getitem__(self, key):
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __len__(self):
        return len(self.values)

    def __delitem__(self, key):
        del self.values[key]

    def __reversed__(self):
        return reversed(self.values) 

    def add(self, value):
        self.values.append(value)

    def __str__(self):
        return str(self.values)
1
2
ml = MyList()
ml
1
<__main__.MyList at 0x7f9c207e5c70>
1
2
3
ml.add(0)
ml.add(1)
ml
1
<__main__.MyList at 0x7f9c207e5c70>
1
2
del ml[1]
len(ml)
1
1
1
ml
1
<__main__.MyList at 0x7f9c207e5c70>
1
0 in ml
1
True

6.1.8 迭代器与可迭代对象

迭代器

  • 迭代器协议
  • __iter__:返回一个迭代器对象,由iter函数触发
  • __next__:返回迭代器中的下一个元素,由next函数触发
  • StopIteration异常
  • 迭代器在迭代的过程中,遇到StopIteration异常表示迭代过程结束
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Fibonacci:
    def __init__(self):
        self.v1 = 0
        self.v2 = 1

    def __iter__(self):
        return self

    def __next__(self):
        value = self.v2
        if value >= 100:
            raise StopIteration
        self.v1, self.v2 = self.v2, self.v1 + self.v2
        return value
1
2
fb = Fibonacci()
next(fb)
1
1
1
2
fb = Fibonacci()
print(list(fb))
1
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

可迭代对象

  • 能够被迭代的对象称为可迭代对象
  • 可用于for循环、各种推导式、元组拆包、序列解包、参数分配等操作
  • 迭代器是可迭代对象的一种
1
2
3
4
5
6
class TestIter:
    def __init__(self):
        self.values = [1, 2, 3, 4, 5]

    def __getitem__(self, key):
        return self.values[key]
1
2
3
ti = TestIter()
for n in ti:
    print(n)
1
2
3
4
5
1
2
3
4
5
  • ti确实不是迭代器,它能够被迭代的原因是实现了__getitem__方法
  • 运行过程
  • Python解释器会自动调用iter(obj)
  • iter函数首先检查obj中是否包含__iter__方法
    • 如果是,则调用它并获取一个迭代器
    • 如果obj中没有__iter__方法,则进一步检查它是否包含__getitem__方法
    • 如果是,则尝试按整数索引获取obj中的元素,并基于这些元素创建出一个可迭代对象
    • 如果obj中不包含__getitem__方法,返回错误"TypeError: 'obj' object is not iterable"
  • 迭代器与可迭代对象的重要区别
  • 迭代器只能使用一次,经过一次迭代next函数已经遇到StopIteration异常,迭代器不会自动重置
  • 可迭代对象则可以重复被迭代,每一次迭代都会重新创建一个新的可迭代对象。

6.2 生成器

6.2.1 生成器的创建

生成器函数

  • yield
  • 在函数中返回一个数值
  • yield的值不是通过函数调用返回的,而是返回到迭代过程之中
  • 使用了yield返回值的函数称为生成器函数
  • 一个函数中一旦使用了yield关键字,它就不再是一个普通的函数
  • 即便中其中包含了return语句,在调用的时候也不会返回值,而是返回一个生成器对象
  • 生成器函数可被看作是生成器工厂
1
2
3
def gen_fun():
    yield 0
    return 1
1
2
g = gen_fun()
type(g)
1
generator
  • 生成器是一种特殊的迭代器,可利用next函数获取yield返回的数值
1
2
3
g = gen_fun()
next(g)
next(g)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-12-12844bda11d7> in <module>
      1 g = gen_fun()
      2 next(g)
----> 3 next(g)


StopIteration: 1
  • yield语句
  • 函数中可以多个yield语句或者多次执行yield语句,每次执行yield语句都会返回一个值
  • yield语句被执行并返回值后,生成器就被阻塞掉,不会自动继续运行,直到下一次调用next函数来激活
  • 当生成器函数中没有yield语句可以被再次执行时,执行next会抛出StopIteration异常
1
2
3
4
def gen_fun(n):
    for i in range(n):
        print(f'返回{i}')
        yield i
1
2
g = gen_fun(2)
next(g)
1
2
3
4
5
6
7
返回0





0
1
next(g)
1
2
3
4
5
6
7
返回1





1
1
next(g)
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-13-e734f8aca5ac> in <module>
----> 1 next(g)


StopIteration:
  • 生成器可以像其他可迭代对象一样使用
1
2
3
g = gen_fun(5)
for i in g:
   print(i)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
返回0
0
返回1
1
返回2
2
返回3
3
返回4
4
  • 惰性计算
  • 生成器中并不持有全部迭代元素,每次next执行临时生成一个元素并返回
  • 原因
    • 每次调用yield语句返回值后都会被阻塞

生成器推导式

  • 生成器推导式与生成器函数的作用相同,都是返回一个生成器,只不过提供了一种更加便捷的语法
  • 生成器函数 vs 生成器推导式
  • 当生成器要实现的功能比较复杂时最好使用生成器函数
  • 当功能仅通过一个表达式就能够实现时选择生成器推导式更合适
1
2
g = (i**2 for i in [1, 2])
g
1
<generator object <genexpr> at 0x10a4ff7b0>
1
next(g)
1
1
1
next(g)
1
4
1
next(g)
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-18-e734f8aca5ac> in <module>
----> 1 next(g)


StopIteration:

yield from

  • 为生成器函数依次返回可迭代对象中的元素,从而替代循环语句,使得代码更加简洁有效
  • 包含了复杂的异常处理的工作,可用于协程之中实现复杂的功能
1
2
3
def gen_fun(iters):
    for it in iters:
        yield from it
1
g = gen_fun([[1, 2, 3], ['a', 'b', 'c']])
1
list(g)
1
[1, 2, 3, 'a', 'b', 'c']

6.2.2 生成器与迭代器

  • __iter__作为一个生成器函数来使用
1
2
3
4
5
6
7
8
class GenIter:
    def __init__(self, values):
        self.values = values

    def __iter__(self):
        # for v in self.values:
        #     yield v
        yield from self.values
1
2
3
gi = GenIter([1, 2, 3])
for n in gi:
   print(n)
1
2
3
1
2
3

6.3 类装饰器

6.3.1 修饰方法的装饰器

  • 与函数装饰器相同,但需要处理方法特殊参数selfcls
1
2
3
4
5
6
7
8
9
def add_property(method):
    def wrapper(self):
        print("添加一个属性")
        self.new_property = 0
        result = method(self)
        del self.new_property
        print("删除属性")
        return result
    return wrapper
1
2
3
4
class TestMethodDec:
    @add_property
    def method(self):
        print(self.new_property)
1
2
tm = TestMethodDec()
tm.method()
1
2
3
添加一个属性
0
删除属性
  • 带参数的方法装饰器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def add_property(prop_name, value):
    def decorator(method):
        def wrapper(self, *args, **kwargs):
            print(f"添加属性{prop_name}")
            self.__dict__[prop_name] = value
            result = method(self, *args, **kwargs)
            del self.__dict__[prop_name]
            print(f"删除属性{prop_name}")
            return result
        return wrapper
    return decorator
1
2
3
4
class TestMethodDec:
    @add_property('new_property', 100)
    def method(self):
        print(self.new_property)
1
2
tm = TestMethodDec()
tm.method()
1
2
3
添加属性new_property
100
删除属性new_property
  • 在不对类或实例进行操作时,可定义即能用于函数又能用于方法的装饰器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import time

def run_time(fun):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = fun(*args, **kwargs)
        end = time.time()
        print(f'函数{fun.__name__}的执行时间为{(end-start):.4}秒')
        return result
    return wrapper

6.3.2 修饰类的装饰器

  • 原理
  • 在修饰类的装饰器中定义一个类,替换掉被修饰的类
  • 为了保留被修饰类的原有功能,通常需要实例化一个被修饰类的对象,将该对象作为新类的属性,通过属性代理访问的方式来实现被修饰类的功能
1
2
3
4
5
6
7
8
def class_dec(class_deced):
    class Inner:
        def __init__(self, *args, **kwargs):
            self.class_obj = class_deced(*args, **kwargs)
        def method(self):
            print("执行装饰器中类的方法")
            return self.class_obj.method()
    return Inner
1
2
3
4
@class_dec
class TestClassDec:
    def method(self):
        print("执行被修饰器的方法")
1
2
tm = TestClassDec()
tm.method()
1
2
执行装饰器中类的方法
执行被修饰器的方法
  • 更加灵活的方式
  • getattr(obj, prop_name)函数的作用是返回obj的名为prop_name的的属性
1
2
3
4
5
6
7
def class_dec(class_deced):
    class Inner:
        def __init__(self, *args, **kwargs):
            self.class_obj = class_deced(*args, **kwargs)
        def __getattr__(self, prop_name):
            return getattr(self.class_obj, prop_name)
    return Inner

6.3.3 基于类的装饰器*

  • 利用类来实现装饰器
  • 修饰函数
  • 修饰方法
  • 修饰类

修饰函数类装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import time

class RunTime():
    def __init__(self, fun):
        self.fun = fun

    def __call__(self):
        start = time.time()
        result = self.fun()
        end = time.time()
        print(f'函数{self.fun.__name__}的执行时间为{(end-start):.4}秒')
        return result
1
2
3
@RunTime
def test_fun():
    time.sleep(1)
1
test_fun
1
<__main__.RunTime at 0x10eb444c0>
1
test_fun()
1
函数test_fun的执行时间为1.005秒
  • 保留元信息
  • 利用wraps函数,或者功能相似的update_wrapper函数来实现
1
2
3
4
5
6
7
8
9
from functools import update_wrapper

class ClassDec():
    def __init__(self, fun):
        self.fun = fun
        update_wrapper(self, fun)

    def __call__(self):
        return self.fun()
1
2
3
@ClassDec
def test_fun():
    pass
1
2
test_fun.__name__
test_fun
1
<__main__.ClassDec at 0x10fa546a0>

修饰方法的类装饰器

  • 在装饰器类的__call__方法处理特殊参数
1
2
3
4
class TestDec:
    @ClassDec
    def method(self):
        pass
1
2
td = TestDec()
td.method()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-17-9e662c8506d2> in <module>
      1 td = TestDec()
----> 2 td.method()


<ipython-input-12-1a658fa139b3> in __call__(self)
      7 
      8     def __call__(self):
----> 9         return self.fun()


TypeError: method() missing 1 required positional argument: 'self'
  • __call__中调用被修饰方法时,传入self即可,即self.fun(self)
  • 该装饰器无法再用于函数之上。
  • 在装饰器中同时实现__call__方法和__get__方法使其即能修饰方法又能修饰函数
  • types.MethodType的作用是将self作为方法绑定至obj实例之上
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import types

class ClassDec():
    def __init__(self, fun):
        self.fun = fun

    def __call__(self, *args, **kwargs):
        print('执行__call__')
        return self.fun(*args, **kwargs)

    def __get__(self, obj, cls):
        print('执行__get__')
        print(obj)
        print(cls)
        return types.MethodType(self, obj)
1
2
3
4
5
6
7
8
@ClassDec
def fun():
    print('执行被修饰函数')

class TestDec:
    @ClassDec
    def method(self):
        print('执行被修饰方法')
1
fun()
1
2
执行__call__
执行被修饰函数
1
2
td = TestDec()
td.method()
1
2
3
4
5
执行__get__
<__main__.TestDec object at 0x10ebf4160>
<class '__main__.TestDec'>
执行__call__
执行被修饰方法
  • 实现原理
  • 实现__get__方法之后,装饰器成为一个描述器
  • TestDec类中被修饰的method方法被替换为一个同名的描述器
  • 在访问td对象的method方法时,会进一步调用描述器的__get__方法

修饰类的类装饰器

  • 装饰器__call__方法中创建一个被修饰类的实例并返回
1
2
3
4
5
6
7
class ClassDec:
    def __init__(self, dec_cls):
        self.dec_cls = dec_cls

    def __call__(self, *args, **kwargs):
        print(f'实例化一个{self.dec_cls}对象')
        return self.dec_cls(*args, **kwargs)
1
2
3
4
@ClassDec
class TestDec:
    def method(self):
        print('执行方法')
1
2
td = TestDec()
td
1
2
3
4
5
6
7
实例化一个<class '__main__.TestDec'>对象





<__main__.TestDec at 0x10fa54ca0>

6.4 抽象基类*

6.4.1 抽象基类的概念

  • 抽象类与抽象方法
  • 抽象类的主要作用有
  • 提高软件的灵活性
  • 提高代码规范程序
  • 便于软件的设计
  • 提供一类实例共性方法的统一实现
  • Python中的抽象基类
  • 通常不需要自定义抽象基类,大部分情况下内置的抽象基类就能够满足需要

6.4.2 抽象基类的应用

  • 方法
  • 派生抽象基类
  • 注册虚拟子类

派生抽象基类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from collections import abc

class TestAbc1(abc.MutableSequence):
    def __init__(self, values):
        self._values = list(values)

    def __len__(self):
        return len(self._values)

    def __getitem__(self, index):
        return self._values[index]

    def __setitem__(self, index, value):
        self._values[index] = value

    def __delitem__(self, index):
        del self._values[index]

    def insert(self, index, value):
        self._values.insert(index, value)
1
2
class TestAbc2(abc.MutableSequence):
    pass
1
2
ta1 = TestAbc1([1, 2, 3])
len(ta1)
1
3
  • 抽象基类与鸭子类型
  • 抽象方法强制实现
  • 能够使用isinsanceissubclass
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class TestAbc3:
    def __init__(self, values):
        self._values = list(values)

    def __len__(self):
        return len(self._values)

    def __getitem__(self, index):
        return self._values[index]

    def __setitem__(self, index, value):
        self._values[index] = value

    def __delitem__(self, index):
        del self._values[index]

    def insert(self, index, value):
        self._values.insert(index, value)
1
isinstance(ta1, abc.MutableSequence)
1
True
1
2
ta3 = TestAbc3([1, 2, 3])
isinstance(ta3, abc.MutableSequence)
1
False
  • 某些情况下即便没有继承抽象基类,使用isinstance也会返回True
1
2
3
4
5
6
7
8
from collections import abc

class TestABC:
    def __init__(self):
        self.num = 0

    def __len__(self):
        return self.num
1
2
ta = TestABC()
isinstance(ta, abc.Sized)
1
True
  • 原因
  • abc.Sized抽象基类中实现了一个特殊方法__subclasshook__
1
2
3
4
5
6
@classmethod
def __subclasshook__(cls, C):
    if cls is Sized:
        if any("__len__" in B.__dict__ for B in C.mro__):
            return True
    return NotImplemented

注册虚拟子类

  • 抽象基类的register方法或register装饰器
  • 特点
  • 注册虚拟子类时,Python不会检查抽象方法是否被实现
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from collections import abc

@abc.MutableSequence.register                # 使用装饰器注册虚拟子类
class TestAbc3:
    def __init__(self, values):
        self._values = list(values)
    def __len__(self):
        return len(self._values)
    def __getitem__(self, index):
        return self._values[index]
    def __setitem__(self, index, value):
        self._values[index] = value
    def __delitem__(self, index):
        del self._values[index]
    def insert(self, index, value):
        self._values.insert(index, value)
# abc.MutableSequence.register(TestAbc3)      # 使用函数注册虚拟子类
1
2
tb3 = TestAbc3([1, 2, 3])
isinstance(tb3, abc.MutableSequence)
1
True

常用内置抽象基类

  • collections.abc模块
抽象基类 继承自 抽象方法 Mixin 方法
Container __contains__
Hashable __hash__
Iterable __iter__
Iterator Iterable __next__ __iter__
Reversible Iterable __reversed__
Generator Iterator send, throw close, __iter__, __next__
Sized __len__
Callable __call__
Collection Sized, Iterable, Container __contains__, __iter__, __len__
Sequence Reversible,Collection __getitem__, __len__ __contains__, __iter__, __reversed__, index, count
MutableSequence Sequence __getitem__, __setitem__, __delitem__, __len__, insert 继承自 Sequence 的方法,以及 append, reverse, extend, pop, remove__iadd__
ByteString Sequence __getitem__, __len__ 继承自 Sequence 的方法
Set Collection __contains__, __iter__,__len__ __le__, __lt__, __eq__, __ne__, __gt__, __ge__, __and__, __or__,__sub__, __xor__, isdisjoint
MutableSet Set __contains__, __iter__, __len__, add, discard 继承自 Set 的方法以及 clear, pop, remove, __ior__, __iand__, __ixor__, __isub__
Mapping Collection __getitem__, __iter__, __len__ __contains__, keys, items, values, get, __eq__, and __ne__
MutableMapping Mapping __getitem__, __setitem__, __delitem__, __iter__, __len__ 继承自 Mapping 的方法以及 pop, popitem, clear, update,和 setdefault
MappingView Sized __len__
ItemsView MappingView , Set __contains__, __iter__
KeysView MappingView , Set __contains__, __iter__
ValuesView MappingView , Collection __contains__, __iter__
Awaitable __await__
Coroutine Awaitable send, throw close
AsyncIterable __aiter__
AsyncIterator AsyncIterable __anext__ __aiter__
AsyncGenerator AsyncIterator asend, athrow aclose, __aiter__, __anext__

6.4.3 自定义抽象基类

  • 自定义抽象基类需要继承abc.ABC
  • 使用abc.abstractmethod装饰器将一个方法声明为抽象方法
  • abstractmethod装饰器也可以和其他装饰器叠加使用,如staticmethodclassmethod,不过abstractmethod必须是最内层装饰器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from collections import abc

class Moto(abc.ABC):
    @abc.abstractmethod
    def run(self, speed):
        """行驶"""

    @abc.abstractmethod
    def refueling(self, litre):
        """加油"""

6.5 元类*

6.5.1 Python类的特征

类的一等对象特征

  • Python中的类
  • 可以被赋值给变量
  • 作为参数传递给函数
  • 可以作为函数的返回值
  • 能够在程序运行期间动态创建
1
2
3
4
5
6
7
class TestClass:
    pass

def get_class():
    class TestClass:
        pass
    return TestClass
1
2
3
Test = TestClass       # 赋值给变量
t = Test()
t
1
<__main__.TestClass at 0x1119ec670>
1
2
3
lst = []
lst.append(TestClass)  # 放入容器之中
lst[0]()
1
<__main__.TestClass at 0x111aa10a0>
1
get_class()()   
1
<__main__.get_class.<locals>.TestClass at 0x1119ec6d0>

类的类型

  • 即然Python中的类也是对象,那么它们与其他的对象类似,也需要有一个“类”来描述和创建
  • 元类
  • 用于创建类的“类”称为元类
  • 类是元类的实例,元类是比类更高一级的抽象
1
2
tc = TestClass()
type(tc)
1
__main__.TestClass
1
tc.__class__
1
__main__.TestClass
1
type(TestClass)
1
type
1
TestClass.__class__
1
type
  • 不只是自定义的类,如果没有指定元类,默认情况下Python中所有类的类型都是type
1
list.__class__
1
type
1
tuple.__class__
1
type
1
set.__class__
1
type
1
dict.__class__
1
type

类的动态创建

  • get_class函数每一次调用它都会动态地创建出一个新的类
  • 使用type函数能够更加灵活地动态创建类,它需要三个参数
  • name:字符串,用于指定类的名字;
  • bases:元组,用于指定基类,当为空元组时表示使用默认基类object
  • dict:字典,用于指定类的属性和方法,空字典表示没有属性或方法。
1
2
3
4
def m1(self):
    print("执行m1方法")
test_class = type('TestClass', (), {'x': 1, 'm1': m1})
test_class()
1
<__main__.TestClass at 0x111aa1910>
1
test_class().m1()
1
执行m1方法
  • 静态方法与类方法
  • 只需要像普通的类定义中那样使用classmethodstaticmethod装饰器修饰函数属性即可
  • python中type的作用
  • 作为函数来判断数据的类型
  • 是所有元类的默认基类,所有的元类都必须派生自type
  • 可以用于动态创建类

6.5.2 元类的定义与使用

自定义元类

  • Python中所有的元类都必须是type的派生类
  • 在类定义的时候,如果指定父类为type或者基它元类,那么这个类就是一个元类
  • 元类的定义与普通的类基本一致,可以使用一些特殊方法对元类进行定制
  • __new__方法
  • 四个参数 clsnamebasesdict
1
2
3
4
5
6
7
8
9
class LowerAttr(type):
    def __new__(cls, name, bases, attrs):
        new_attrs = {}
        for attr in attrs:
            if attr.startswith('__') and attr.endswith('__'):
                new_attrs[attr] = attrs[attr]
            else:
                new_attrs[attr.lower()] = attrs[attr]
        return super().__new__(cls, name, bases, new_attrs)

使用元类

1
2
3
class Test(metaclass=LowerAttr):
    def METHOD1(self):
        print('METHOD1') 
1
2
t = Test()
t.METHOD1()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-21-b4d216c52d52> in <module>
      1 t = Test()
----> 2 t.METHOD1()


AttributeError: 'Test' object has no attribute 'METHOD1'
1
t.method1()
1
METHOD1

类的创建过程

  • 类创建过程
  • Python解释器解析类的代码
  • 将类的名称、基类元组、属性字典传递给元类的__new__方法
  • __new__方法根据传入的参数调用type来创建类
  • 元类的使用
  • 当前类指定了元类,则用它来创建类
  • 如果当前类没有指定元类则进一步遍历检查基类中是否指定元类
  • 如果基类中也没有指定元类,则使用默认的type来创建类

6.5.3 元类的应用实例

应用场景

  • 元类是Python中最为复杂的内容之一,而且它的部分功能也可以使用装饰器或者类的定制来实现
  • 在绝大多数的Python编程任务中不需要使用元类
  • 元类最常用的应用场景是构建应用程序的API或编程框架

利用元类实现ORM

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Field:
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return f'{self.__class__.__name__}:{self.name}'


class StrField(Field):
    def __init__(self, name):
        super().__init__(name, 'varchar(50)')


class IntField(Field):
    def __init__(self, name):
        super().__init__(name, 'int')


class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return type.__new__(cls, name, bases, attrs)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                mappings[k] = v
        for k in mappings:
            attrs.pop(k)
        attrs['__mappings__'] = mappings
        attrs['__table__'] = name
        return type.__new__(cls, name, bases, attrs)


class Model(dict, metaclass=ModelMetaclass):
    def __getattr__(self, key):
        return self.get(key, None)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = f'insert into {self.__table__} ({",".join(fields)}) values ({",".join(params)})'
        print(sql, tuple(args))


class User(Model):
    # 定义类的属性到列的映射:
    id = IntField('id')
    name = StrField('username')
    password = StrField('password')
1
2
3
4
5
6
7
# 创建一个实例:
u = User(id=1, name='张三', password='123456')
# 保存到数据库:
u.save()

u = User(id=2, name='李四', password='123456')
u.save()
1
2
insert into User (id,username,password) values (?,?,?) (1, '张三', '123456')
insert into User (id,username,password) values (?,?,?) (2, '李四', '123456')

6.6 对象序列化*

  • 序列化
  • 对象序列化(Serialization)也称为对象持久化,是指将内存中的对象转换为数据流, 能够存储在文件之中或者通过网络进行传输
  • 反序列化
  • 对象反序列化是指将二进制字节 数据流恢复重建为 Python 对象

6.6.1 pickle

  • 对象序列化和反序列化最常用的工具。常用函数包括:
  • dump(obj, file, protocol=None):将对象obj序列化为二进制数据,并写入打开的文件对象file之中
  • load(file):从打开的文件对象file中加载序列化对象,反序列化重建对象
  • dumps(obj, protocol=None):将对象obj序列化为二进制数据
  • loads(data):对序列化之后的二进制数据进行反序列化操作,重建对象

  • 绝大多数Python数据类型都可以被序列化和反序列化,包括

  • None、 布尔类型(TrueFalse)、字符串(str)、字节串(byte)、字节数组(bytearray)
  • 全局函数和类
  • __dict__属性或__getstate__方法返回值可以被可被序列化的对象
  • 元素都为可序列化对象的列表、元组、集合和字典

一般数据类型的序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pickle

# 待序列化数据
data = {
    'list': [1, 2.0, 3, 4+5j],
    'tuple': ("字符串数据", b"byte string"),
    'set': {None, True, False}
}

# 序列化
with open('data.pickle', 'wb') as f:
    pickle.dump(data, f)

# 反序列化
with open('data.pickle', 'rb') as f:
    data = pickle.load(f)

函数、类和实例的序列化

  • 在序列化时,只有函数名和类名被序列化,而函数体及类体(包括类数据)不会被序列化
  • 在对函数和类进行反序列化的时候,它们的定义必须是可见的

  • 函数和类的序列化

1
2
3
4
5
6
7
import pickle

def test_fun():
    return 'Hello Pickle'

class TestClass:
    pass

fun_pkl = pickle.dumps(test_fun) class_pkl = pickle.dumps(TestClass) fun = pickle.loads(fun_pkl) fun()

1
2
3
4
5
test_class = pickle.loads(class_pkl)  # 类反序列化
obj = test_class()
del test_fun                          # 删除函数定义
del TestClass                         # 函数类定义
fun = pickle.loads(fun_pkl)           # 再次反序列化函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-4-58bb2403b666> in <module>
      3 del test_fun                          # 删除函数定义
      4 del TestClass                         # 函数类定义
----> 5 fun = pickle.loads(fun_pkl)           # 再次反序列化函数


AttributeError: Can't get attribute 'test_fun' on <module '__main__'>
1
test_class = pickle.loads(class_pkl)  # 再次反序列化类
1
2
3
4
5
6
7
8
9
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-5-d8daf5b93233> in <module>
----> 1 test_class = pickle.loads(class_pkl)  # 再次反序列化类


AttributeError: Can't get attribute 'TestClass' on <module '__main__'>

实例的序列化

  • 实例在序列化的时候,只会保存实例中的属性数据,实例所属的类同样不会被序列化
  • 在反序列化时,类也必须也是可见的
  • 反序列化时不会调用__init__方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pickle

class TestClass:
    attr_cls = 1
    def __init__(self):
        self.attr_obj = 2

obj = TestClass()
obj.attr_obj = 3
obj_pkl = pickle.dumps(obj)     # 实例序列化

del obj
obj = pickle.loads(obj_pkl)     # 实例反序列化

print(obj.attr_cls)
print(obj.attr_obj)

6.6.2 copyreg

  • copyreg的作用是注册能够在序列化和反序列化过程中被调用的函数,用于控制序列化和反序列化的过程
  • 在实际应用中往往会遇到软件升级的情况。升级之后,加载数据时可能会遇到旧版软件的反序列化数据,可能会出现不兼容的情况。copyreg模块通过为目标类型注册约函数和重构函数来控制反序列化过程,从而实现版本兼容
 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
import pickle
import copyreg

class User:                                     # 旧版User类
    def __init__(self, name, age):
        self.name = name
        self.age = age

def unpickle_fun(obj_data):                     # 重建被序列化的对象
    print('reconstructing')
    return User(**obj_data)

def pickle_fun(obj):                            # 在序列化之前调用
    print('before pickling')
    return unpickle_fun, (obj.__dict__,)

copyreg.pickle(User, pickle_fun, unpickle_fun)  # 注册归约函数和重构函数

user = User('张三', 20)
user_pkl = pickle.dumps(user)                   # 序列化旧版User的对象

del User

class User:                                     # 新版User类
    def __init__(self, name, age, email='common@test.email'):
        self.name = name
        self.age = age
        self.email = email

user_recover = pickle.loads(user_pkl)           # 重构为新版User对象
print(user_recover.email)

6.6.3 shelve

  • shelve模块是对pickle模块的封装,提供了更加便捷的序列化和反序列化功能
  • shelve模块中的open函数会打开序列化数据文件并返回一个特殊的字典,该字典中包含了保存在文件中的序列化数据
  • writeback参数用于控制是否可写入新的数据至序列化文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import shelve

class Test:
    pass

d1 = [1, 2.0, 3, 4+5j]
d2 = ("字符串数据", b"byte string")
d3 = {None, True, False}
d4 = Test()

# 序列化,以可写入方式打开文件
with shelve.open('shelve.data') as shelve_file:
    shelve_file['list'] = d1
    shelve_file['tuple'] = d2
    shelve_file['set'] = d3
    shelve_file['obj'] = d4

# 反序列化,以不可写入方式打开文件
with shelve.open('shelve.data', writeback=False) as shelve_file:
    for key, value in shelve_file.items():
        print(f'{key}\t{value}')
1
2
3
4
list:   [1, 2.0, 3, (4+5j)]
tuple:  ('字符串数据', b'byte string')
set:    {False, True, None}
obj:    <__main__.Test object at 0x7fa5725ff3a0>