基础知识
以下是对重载背后的关键概念的复习:
- 运算符重载让类拦截常规的python操作;
- 类可重载所有python表达式运算符;
- 类也可重载打印、函数调用、属性访问等内置运算;
- 重载使类实例的行为更加接近内置类型;
- 重载是通过在一个类中提供特殊名称的方法来实现的;
构造函数和表达式:__init __和__sub __
class Number():
def __init__(self, start):
self.data = start
def __sub__(self, other):
return Number(self.data - other)
X = Number(5)
Y = X - 2
print(Y.data) # output:3
常见的运算符重载方法
方法名 | 实现功能 | 触发调用的形式 |
---|---|---|
__init __ | 构造函数 | 对象创建:X = Class(args) |
__del __ | 析构函数 | X对象回收 |
__add __ | ‘+’运算符 | X + Y,X += Y |
__or __ | 按位或 | X / Y |
__repr __, __str __ | 打印、转换 | print(X)、repr(X)、str(X) |
__call __ | 函数调用 | X(*args, **kargs) |
__getattr __ | 属性访问 | X.undefined |
__setattr __ | 属性赋值 | X.any = value |
__delattr __ | 属性删除 | del X.any |
__getattribute __ | 属性访问 | X.any |
__getitem __ | 索引、分片、迭代 | X[key],X[I : j] |
__setitem __ | 索引赋值和分片赋值 | X[key] = value |
__delitem __ | 索引删除和分片删除 | del X[key] |
__len __ | 长度 | len(X) |
__bool __ | 布尔测试 | bool(X)、真值测试 |
__lt __,__gt __ | 比较 | X > Y、X < Y、X <= Y、X >= Y、X == Y、X != Y |
__le __,__ge __ | 比较 | X > Y、X < Y、X <= Y、X >= Y、X == Y、X != Y |
__eq __,__ne __ | 比较 | X > Y、X < Y、X <= Y、X >= Y、X == Y、X != Y |
__radd __ | 右侧“+”操作 | other + X |
__iadd __ | 原位置“+=”操作 | X += Y |
__iter __,__next __ | 迭代上下文 | I = iter(X),next(X);for循环 |
__contains __ | 成员关系测试 | item in X |
__index __ | 整数值转换 | hex(X)、bin(X)、oct(X) |
__enter __,__exit __ | 上下文管理器 | with obj as var: |
__get __,__set __ | 描述符属性 | X.attr、X.attr = value、del X.attr |
__delete __ | 描述符属性 | X.attr、X.attr = value、del X.attr |
__new __ | 创建 | 在__init __之前的对象创建 |
索引和分片:__getitem __和__setitem __
如果一个类定义(或继承)了__getitem __的话,该方法就会自动调用并进行实例的索引运算;
class Indexer():
def __getitem__(self, index):
return index ** 2
X = Indexer()
print(X[2]) # output: 4
拦截分片
__getitem __方法也可以在分片操作时背调用:
class Indexer():
data = [5, 6, 7, 8]
def __getitem__(self, index):
print('getitem:', index)
return self.data[index]
X = Indexer()
print(X[0]) # output:getitem:0
# 5
print(X[2, 4]) # output:getitem:silce(2, 4, None)
# [7, 8]
这里有一点需要解释,index返回值是‘silce(2, 4, None)’
L = [5, 6, 7, 8, 9]
print(L[2, 4]) # output:[7, 8]
print(L[1:]) # output:[6, 7, 8, 9]
print(L[:-1]) # output:[5, 6, 7, 8]
print(L[::2]) # output:[5, 7, 9]
尽管分片对象被绑定到一个分片对象并传递给列表的索引实现。实际上,你总是可以手动地传入一个分片对象,因为分片语法只不过就是用分片对象来索引的语法糖:
print(L[slice(2, 4)]) # output:[7, 8]
print(L[slice(1, None)]) # output:[6, 7, 8, 9]
print(L[slice(None, -1)]) # output:[5, 6, 7, 8]
print(L[slice(None, None, 2)]) # output:[5, 7, 9]
python 3.X中__index__不是索引
不能把python3.X中的索引拦截方法__index__方法混淆起来。__index__方法在使用时会为一个实例返回一个整数值,提供转化数字串的内置函数使用:
class C:
def __index__(self):
return 255
X = C()
print(hex(X)) # output:'Oxff'
print(bin(X)) # output:'Ob11111111'
print(otc(X)) # output:'O0377'
索引迭代:getitem
任何会响应索引运算的内置或用户定义的对象,都会响应for循环迭代:
class StepperIndex():
def __getitem__(self, i):
return self.data[i]
X = StepperIndex()
X.data = 'spam'
print(X[1]) # output:'p'
for i in X:
print(item, end=' ') # output:s p a m
可迭代对象:iter__和__next
用户定义的可迭代对象
class Squares():
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.value == self.stop:
raise StopIteration
self.value += 1
return self.value ** 2
for i in Squares(1, 5):
print(i, end=' ') # output:1, 4, 9, 16, 25
手动迭代对用户自定义的可迭代对象的效果,和内置类型是一样的:
X = Squares(1, 5)
I = iter(X)
next(I) # output:1
next(I) # output:4
next(I) # output:9
next(I) # output:16
next(I) # output:25
next(I) # output:StopIteration
单遍扫面与多遍扫描
我们需要注意的是__iter__被设计为只遍历一次,而不是遍历多次。类在它们的代码中显示地选择扫描行为;
X = Squares(1, 5)
print([n for n in X]) # output:[1, 4, 9, 16, 25]
print([n for n in X]) # output:[]
类vs生成器
要注意上面的例子如果使用生成器函数或生成器表达式编写,则可能变得更加简单一些,它们自动产生可迭代对象并在迭代过程之间保持局部变量状态;
类对于更复杂的迭代建模更胜一筹,尤其当它们能受益于类的一般优越性优势时;
单个对象上的多个迭代器
首先,我们关注一下多重的for循环:
S = 'abc'
for x in S:
for y in S:
print(x + y, end=' ' ) # output: aa ab ac ba bb bc ca cb cc