今天一整個上午學習了一下Python中的雙下劃線方法(魔術方法)。這些方法的功能很是強大,也能使Python代碼可讀性更高,更加Pythonic。這篇文章包含了不少魔術方法,包括:javascript
__eq__, __lt__css
__add__, __radd__html
運行環境:Python3.6 + Jupyter notebook。html5
下面就是 Jupyter notebook 筆記。java
class Account:
'A simple account class'
def __init__(self, owner, amount=0):
self.owner = owner
self.amount = amount
self._transactions = []
acc1 = Account('zxzhu')
acc1
注:構造函數使咱們能夠從類中建立實例。python
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__(),前者是給用戶看的,後者則是爲調試服務的。jquery
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)
咱們先給 Account 類實現一個交易函數和一個查看交易餘額的屬性。linux
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。android
接下來,咱們要實現如下功能:css3
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() 函數來查看交易次數,利用切片或者迭代來查看每次交易金額。
接下來咱們進行運算符重載,使不一樣的 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
下面咱們實現帳戶合併,具體實現:
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__ 具體使用能夠看看這裏。
接下來咱們實現實例自己的調用,即像調用函數同樣調用實例。
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()
最後咱們實現上下文管理器。
寫代碼時,咱們但願把一些操做放到一個代碼塊中,這樣在代碼塊中執行時就能夠保持在某種運行狀態,而當離開該代碼塊時就執行另外一個操做,結束當前狀態;因此,簡單來講,上下文管理器的目的就是規定對象的使用範圍,若是超出範圍就採起「處理」。這一功能是在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__,咱們就能夠恢復到交易前狀態。