Python 类的魔术方法(定制类)
今天一整个上午学习了一下Python中的双下划线方法(魔术方法)。这些方法的功能非常强大,也能使Python代码可读性更高,更加Pythonic。这篇文章包含了很多魔术方法,包括:
- __init__
- __str__, __repr__
- __iter__, __getitem__, __len__
-
__eq__, __lt__
-
__add__, __radd__
- __call__
- __enter__, __exit__
运行环境:Python3.6 + Jupyter notebook。
下面就是 Jupyter notebook 笔记。
Python 类的魔术方法(定制类)¶
__init__¶
class Account:
'A simple account class'
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
acc1 = Account('zxzhu')
acc1
注:构造函数使我们可以从类中创建实例。
__str__, __repr__¶
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
acc = Account('bob', 1000)
print(acc)
acc
直接显示变量调用的不是 __str__(),而是 __repr__(),前者是给用户看的,后者则是为调试服务的。
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
acc = Account('Bob', 1000)
acc
print(acc)
__iter__, __getitem__, __len__¶
我们先给 Account 类实现一个交易函数和一个查看交易余额的属性。
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
def add_transaction(self, value):
if not isinstance(value,int):
raise ValueError('please use int for amount')
self._transactions.append(value)
@property
def balance(self):
return self.amount + sum(self._transactions)
acc = Account('Bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
acc.balance
@property 是一个 decorator,具体细节查看 @property。
接下来,我们要实现以下功能:
- 查看交易次数
- 查看每次交易的金额
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
self.__i = -1 #迭代索引
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
def add_transaction(self, value):
if not isinstance(value,int):
raise ValueError('please use int for amount')
self._transactions.append(value)
@property
def balance(self):
return self.amount + sum(self._transactions)
def __len__(self):
return len(self._transactions)
def __iter__(self):
return self #实现迭代实例自身
def __next__(self):
self.__i += 1
if self.__i >= len(self._transactions):
raise StopIteration #迭代结束
return self._transactions[self.__i]
def __getitem__(self,n):
if isinstance(n, int): #传入索引
return self._transactions[n]
if isinstance(n, slice): # 传入切片对象
start = n.start
end = n.stop
step = n.step
return self._transactions[start:end:step]
acc = Account('Bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
for i in acc:
print(i)
len(acc)
acc[::-1]
acc[::2]
以上,我们就可以利用len() 函数来查看交易次数,利用切片或者迭代来查看每次交易金额。
__eq__, __lt__¶
接下来我们进行运算符重载,使不同的 Account 类相互之间可以进行比较,我们比较账户余额。
为了方便,我们实现 __eq__ 和 __lt__方法,然后利用 functools.total_ordering 这个 decorator 来完善其他的比较运算。
from functools import total_ordering
@total_ordering
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
self.__i = -1 #迭代索引
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
def add_transaction(self, value):
if not isinstance(value,int):
raise ValueError('please use int for amount')
self._transactions.append(value)
@property
def balance(self):
return self.amount + sum(self._transactions)
def __len__(self):
return len(self._transactions)
def __iter__(self):
return self #实现迭代实例自身
def __next__(self):
self.__i += 1
if self.__i >= len(self._transactions):
raise StopIteration #迭代结束
return self._transactions[self.__i]
def __getitem__(self,n):
if isinstance(n, int): #传入索引
return self._transactions[n]
if isinstance(n, slice): # 传入切片对象
start = n.start
end = n.stop
step = n.step
return self._transactions[start:end:step]
def __eq__(self,other):
return self.balance == other.balance
def __lt__(self,other):
return self.balance < other.balance
acc1 = Account('bob')
acc2 = Account('tim', 100)
print(acc1.balance)
print(acc2.balance)
acc1 > acc2
acc1 < acc2
__add__, __radd__¶
下面我们实现账户合并,具体实现:
from functools import total_ordering
@total_ordering
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
self.__i = -1 #迭代索引
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
def add_transaction(self, value):
if not isinstance(value,int):
raise ValueError('please use int for amount')
self._transactions.append(value)
@property
def balance(self):
return self.amount + sum(self._transactions)
def __len__(self):
return len(self._transactions)
def __iter__(self):
return self #实现迭代实例自身
def __next__(self):
self.__i += 1
if self.__i >= len(self._transactions):
raise StopIteration #迭代结束
return self._transactions[self.__i]
def __getitem__(self,n):
if isinstance(n, int): #传入索引
return self._transactions[n]
if isinstance(n, slice): # 传入切片对象
start = n.start
end = n.stop
step = n.step
return self._transactions[start:end:step]
def __eq__(self,other):
return self.balance == other.balance
def __lt__(self,other):
return self.balance < other.balance
def __add__(self,other): # 合并账户
owner = self.owner + '&' + other.owner
amount = self.amount + other.amount
new_acc = Account(owner,amount)
for transaction in self._transactions + other._transactions:
new_acc.add_transaction(transaction)
return new_acc
acc1 = Account('bob', 0)
acc2 = Account('tim', 100)
acc3 = Account('james', 200)
acc1 + acc2
acc2 + acc3
acc1 + acc2 相当于 acc1.__add__(acc2)
我们尝试一下:
sum([acc1, acc2, acc3])
报错:'int' 和 'Account' 两种不同类型不能相加。这是由于 sum() 从0开始执行:
0.__add__(acc1)
0的 __add__方法当然不可能和 acc1 相加,因此,Python会尝试调用:
acc1.__radd__(0)
所以,我们接下来要实现这个方法。
from functools import total_ordering
@total_ordering
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
self.__i = -1 #迭代索引
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
def add_transaction(self, value):
if not isinstance(value,int):
raise ValueError('please use int for amount')
self._transactions.append(value)
@property
def balance(self):
return self.amount + sum(self._transactions)
def __len__(self):
return len(self._transactions)
def __iter__(self):
return self #实现迭代实例自身
def __next__(self):
self.__i += 1
if self.__i >= len(self._transactions):
raise StopIteration #迭代结束
return self._transactions[self.__i]
def __getitem__(self,n):
if isinstance(n, int): #传入索引
return self._transactions[n]
if isinstance(n, slice): # 传入切片对象
start = n.start
end = n.stop
step = n.step
return self._transactions[start:end:step]
def __eq__(self,other):
return self.balance == other.balance
def __lt__(self,other):
return self.balance < other.balance
def __add__(self,other): # 合并账户
owner = self.owner + '&' + other.owner
amount = self.amount + other.amount
new_acc = Account(owner,amount)
for transaction in self._transactions + other._transactions:
new_acc.add_transaction(transaction)
return new_acc
def __radd__(self, other):
if other == 0:
return self
else:
return self.__add__(other)
acc1 = Account('bob', 0)
acc2 = Account('tim', 100)
acc3 = Account('james', 200)
sum([acc1, acc2, acc3])
关于 __add__ 和 __radd__ 具体使用可以看看这里。
__call__¶
接下来我们实现实例本身的调用,即像调用函数一样调用实例。
from functools import total_ordering
@total_ordering
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
self.__i = -1 #迭代索引
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
def add_transaction(self, value):
if not isinstance(value,int):
raise ValueError('please use int for amount')
self._transactions.append(value)
@property
def balance(self):
return self.amount + sum(self._transactions)
def __len__(self):
return len(self._transactions)
def __iter__(self):
return self #实现迭代实例自身
def __next__(self):
self.__i += 1
if self.__i >= len(self._transactions):
raise StopIteration #迭代结束
return self._transactions[self.__i]
def __getitem__(self,n):
if isinstance(n, int): #传入索引
return self._transactions[n]
if isinstance(n, slice): # 传入切片对象
start = n.start
end = n.stop
step = n.step
return self._transactions[start:end:step]
def __eq__(self,other):
return self.balance == other.balance
def __lt__(self,other):
return self.balance < other.balance
def __add__(self,other): # 合并账户
owner = self.owner + '&' + other.owner
amount = self.amount + other.amount
new_acc = Account(owner,amount)
for transaction in self._transactions + other._transactions:
new_acc.add_transaction(transaction)
return new_acc
def __radd__(self, other):
if other == 0:
return self
else:
return self.__add__(other)
def __call__(self):
print('Start amount: {}'.format(self.amount))
print('Transactions: ')
for transaction in self:
print(transaction,end=' ')
print('\nBalance: {}'.format(self.balance))
acc1 = Account('bob', 10)
acc1.add_transaction(20)
acc1.add_transaction(-10)
acc1.add_transaction(50)
acc1.add_transaction(-20)
acc1.add_transaction(30)
acc1()
__enter__, __exit__¶
最后我们实现上下文管理器。
写代码时,我们希望把一些操作放到一个代码块中,这样在代码块中执行时就可以保持在某种运行状态,而当离开该代码块时就执行另一个操作,结束当前状态;所以,简单来说,上下文管理器的目的就是规定对象的使用范围,如果超出范围就采取“处理”。这一功能是在Python2.5之后引进的,它的优势在于可以使得你的代码更具可读性,且不容易出错。
详细内容可以参考:
Python学习笔记(五)-- 上下文管理器(Context Manager)
from functools import total_ordering
@total_ordering
class Account:
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
self.__i = -1 #迭代索引
def __str__(self):
return '{} of {} with starting amount: {}'.format(self.__class__.__name__,
self.owner, self.amount)
def __repr__(self):
return 'Account({!r}, {!r})'.format(self.owner, self.amount)
def add_transaction(self, value):
if not isinstance(value,int):
raise ValueError('please use int for amount')
self._transactions.append(value)
@property
def balance(self):
return self.amount + sum(self._transactions)
def __len__(self):
return len(self._transactions)
def __iter__(self):
return self #实现迭代实例自身
def __next__(self):
self.__i += 1
if self.__i >= len(self._transactions):
raise StopIteration #迭代结束
return self._transactions[self.__i]
def __getitem__(self,n):
if isinstance(n, int): #传入索引
return self._transactions[n]
if isinstance(n, slice): # 传入切片对象
start = n.start
end = n.stop
step = n.step
return self._transactions[start:end:step]
def __eq__(self,other):
return self.balance == other.balance
def __lt__(self,other):
return self.balance < other.balance
def __add__(self,other): # 合并账户
owner = self.owner + '&' + other.owner
amount = self.amount + other.amount
new_acc = Account(owner,amount)
for transaction in self._transactions + other._transactions:
new_acc.add_transaction(transaction)
return new_acc
def __radd__(self, other):
if other == 0:
return self
else:
return self.__add__(other)
def __call__(self):
print('Start amount: {}'.format(self.amount))
print('Transactions: ')
for transaction in self:
print(transaction,end=' ')
print('\nBalance: {}'.format(self.balance))
def __enter__(self): #进入上下文管理器
print('ENTER WITH: making backup of transactions for rollback')
self._copy_transactions = self._transactions.copy() # 备份交易
return self #返回实例自身给 with 后的对象
def __exit__(self, exc_type, exc_value, exc_traceback): #退出上下文管理器
print('EXIT WITH:', end=' ')
if exc_type: #代码块抛出异常
self._transactions = self._copy_transactions #恢复交易前状态
print('rolling back to previous transactions')
print('transaction resulted in {} ({})'.format(exc_type.__name__, exc_value)) #给出异常信息
else:
print('transaction ok') #代码块未抛出异常,交易成功
acc4 = Account('sue', 10)
amount_to_add = 20 #进账20
print('\nBalance start: {}'.format(acc4.balance))
with acc4 as a: #进入上下文管理器
print('adding {} to account'.format(amount_to_add))
a.add_transaction(amount_to_add)
print('new balance would be {}'.format(a.balance))
if a.balance < 0: #交易金额不足,抛出异常
raise ValueError('sorry cnnot go in debt!')
print('\nBlance end: {}'.format(acc4.balance)) #交易结束
上面是交易成功的情况,下面展示余额不足交易失败的情况:
acc4 = Account('sue', 10)
amount_to_add = -40 #支出40
print('\nBalance start: {}'.format(acc4.balance))
try:
with acc4 as a: #进入上下文管理器
print('adding {} to account'.format(amount_to_add))
a.add_transaction(amount_to_add)
print('new balance would be {}'.format(a.balance))
if a.balance < 0: #交易金额不足,抛出异常
raise ValueError('sorry cannot go in debt!')
except ValueError:
pass
print('\nBlance end: {}'.format(acc4.balance)) #交易结束
余额不足时抛出异常,异常传入__exit__,我们就可以恢复到交易前状态。