运算符重载的作用是让用户定义的对象使用中缀运算符(如 + 和 |)或一元运算符(如 - 和 ~)。说得宽泛一些,在 Python 中,函数调用(())、属性访问(.)和元素访问 / 切片([])也是运算符。
我们为 Vector 类简略实现了几个运算符。add 和 mul 方法是为了展示如何使用特殊方法重载运算符,不过有些小问题被我们忽视了。此外,我们定义的Vector2d.eq 方法认为 Vector(3, 4) == [3, 4] 是真的(True),这可能并不合理。
运算符重载基础
在某些圈子中,运算符重载的名声并不好。这个语言特性可能(已经)被滥用,让程序员困惑,导致缺陷和意料之外的性能瓶颈。但是,如果使用得当,API 会变得好用,代码会变得易于阅读。Python 施加了一些限制,做好了灵活性、可用性和安全性方面的平衡:
- 不能重载内置类型的运算符
- 不能新建运算符,只能重载现有的
- 某些运算符不能重载——is、and、or 和 not(不过位运算符
- &、| 和 ~ 可以)
前面的博文已经为 Vector 定义了一个中缀运算符,即 ==,这个运算符由__eq__ 方法支持。我们将改进 eq 方法的实现,更好地处理不是Vector 实例的操作数。然而,在运算符重载方面,众多比较运算符(==、!=、>、<、>=、<=)是特例,因此我们首先将在 Vector 中重载四个算术运算符:一元运算符 - 和 +,以及中缀运算符 + 和 *。
构造函数与表达式: _ _init_ _, _ _sub_ _
class Number(obj):
def __init__(self, value):
self.data = value
def __sub__(self, value):
return Number(self.data - value)
x = Number(10)
y = x - 5
Y.data
常见运算符重载方法
method | overload | call |
---|---|---|
init | 构造函数 | 对象创建: X = Class(args) |
del | 析构函数 | X对象收回 |
add | 云算法+ | 如果没有_iadd_, X+Y, X+=Y |
or | 运算符| | 如果没有_ior_,X |
repr_, str | 打印,转换 | print(X),repr(X),str(X) |
call | 函数调用 | X(*args, **kwargs) |
getattr | 点号运算 | X.undefined |
setattr | 属性赋值语句 | X.any=value |
delattr | 属性删除 | del X.any |
getattribute | 属性获取 | X.any |
getitem | 索引运算 | X[key],X[i:j] |
setitem | 索引赋值语句 | X[key],X[i:j]=sequence |
delitem | 索引和分片删除 | del X[key],del X[i:j] |
len | 长度 | len(X),如果没有__bool__,真值测试 |
bool | 布尔测试 | bool(X) |
lt, gt, le, ge, eq, ne | 特定的比较 | X<Y,X>Y,X<=Y,X>=Y, X==Y,X!=Y 注释:(lt: less than, gt: greater than, le: less equal, ge: greater equal,eq: equal, ne: not equal ) |
radd | 右侧加法 | other+X |
iadd | 实地(增强的)加法 | X+=Y(or else add) |
iter, next | 迭代环境 | I=iter(X), next() |
contains | 成员关系测试 | item in X(任何可迭代) |
index | 整数值 | hex(X), bin(X), oct(X) |
enter, exit | 环境管理器 | with obj as var: |
get, set,delete | 描述符属性 | X.attr,X.attr=value, del X.attr |
new | 创建 | 在__init__之前创建对象 |
索引和分片: _ _getitem_ _, _ _setitem_ _
如果在类中定义的话,则对于实例的索引运算,会自动调用__getitem__。当实例X出现X[i]这样的索引运算时,Python会自动调用__getitem__方法
class Indexer(obj):
def __getitem__(self, index):
return in index ** 2
x = Indexer()
x[2]
拦截分片
class Indexer(obj):
data = [4, 5, 6, 7, 8, 9]
def __getitem__(self, index):
print("get item", index)
return self.data[index]
x = Index()
print(x[1:]
# ("get item", slice(1, None, None))
# [5, 6, 7, 8, 9]
print(x[2:4])
# ("get item", slice(2, 4, None))
# [6, 7]
print(x[2:9])
# ("get item", slice(2, 9, None))
# [6, 7, 8, 9]
print(x[::2])
# ("get item", slice(None, None, 2))
# [4, 6, 8]
索引迭代: _ _getitem_ _
class Stepper(obj):
def __int__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
x = Stepper("hello world")
for item in x:
print(item)
迭代器对象: iter, next
尽管上一节__getitem__是有效的,但它是迭代退而求其次的方法。Python所有的迭代环境会有优先尝试__iter__的方法,再尝试__getitem__。
从技术角度上讲,迭代环境是通过iter去尝试寻找__iter__方法来实现,而这种方法返回一个迭代器对象。如果已经提供了,python会重复调用迭代器对象的next()方法,直到发生StopIteration异常。如果没有找到__iter__,python会使用__getitem__机制。
class MyIterator(obj):
def __init__(self, wrappaed):
self.wrapped = wrapped
self.offset = 0
def next(self):
if self.offset >= len(self.wrapped):
raise StopIteration
item = self.wrapper[self.offset]
self.offset += 1
return item
class MyClass(obj):
def __init__(self, data)
self.data = data
def __iter__(self):
return MyIterator(self.data)
x = MyClass("hello")
I = iter(x)
while True:
try:
next(I)
except Exception as e:
print(e)
__getattr__和__setattr__捕捉属性的的引用
__getattr__拦截属性.运算符
class Empty(obj):
def __getattr__(self, attrname):
if attrname == "age":
return 27
else:
raise AttributeError, attrname
x = Empty()
x.age # 27
x.name
'''
Tracebaack(most recent call last):
File "<pyshell#101>", line1, in<module>
x.name
File "<pyshell#97>", line6, in __getattr__
raise AttributeError, attrname
AttributeError, attrname
'''
#
class Empty(obj):
def __getattr__(self, attr):
return self.__dict__[attr]
def __setattr__(self, attr, value):
self.__dict__[attr] = value
x = Empty()
x.age = 100
x.age # 100
__repr__和__str__会返回字符串表达形式
class Number(obj):
def __init__(self, data):
self.data = data
def __add__(self, data):
return Number(self.data + data)
def __radd__(self, data):
return Number(self.data + data)
def __sub__(self, data):
return Number(self.data + data)
def __str__(self):
return "Value:%d" % self.data
def __repr__(self):
return "Value:%d" % self.data
x = Number(100)
y = x - 300
print(x, y)
# Value:100, Value:-200
__radd__处理右侧加法
class Number(obj):
def __init__(self, data):
self.data = data
def __add__(self, data):
return Number(self.data + data)
def __radd__(self, data):
return Number(self.data + data)
def __sub__(self, data):
return Number(self.data + data)
def __str__(self):
return "Value:%d" % self.data
def __repr__(self):
return "Value:%d" % self.data
x = Number(100)
y = 100 + x
print(x, y)
# Value:100, Value:200
__call__拦截调用
当实例调用时,使用__call__方法
class Prod(obj):
def __init__(self, data):
self.data = data
def __call__(self, data):
return self.data * data
x =Prod(10)
x(2)
__del__是析构器
当实例创建时,就会调用__init__构造方法。当实例空间被收回时,析构函数__del__就会自动执行。
class Life(obj):
def __init__(self, name):
self.name = name
print("hello" + name)
def __del__(self):
print("goodbye" + self.name)
# 实例化对象
brian = Life("Brian")
# hello Brian
brian = "Brian"
# goodbye Brian