Python 類的魔術方法(定製類)

今天一整個上午學習了一下Python中的雙下劃線方法(魔術方法)。這些方法的功能很是強大,也能使Python代碼可讀性更高,更加Pythonic。這篇文章包含了不少魔術方法,包括:javascript

  • __init__
  • __str__, __repr__
  • __iter__, __getitem__, __len__
  • __eq__, __lt__css

  • __add__, __radd__html

  • __call__
  • __enter__, __exit__

運行環境:Python3.6 + Jupyter notebook。html5

下面就是 Jupyter notebook 筆記。java

 

Python 類的魔術方法(定製類)

 

__init__

In [3]:
class Account:
    'A simple account class'
    
    def __init__(self, owner, amount=0):        
        self.owner = owner
        self.amount = amount
        self._transactions = []
In [4]:
acc1 = Account('zxzhu')
acc1
Out[4]:
<__main__.Account at 0x1e4581f96a0>
 

注:構造函數使咱們能夠從類中建立實例。python

 

__str__, __repr__

In [5]:
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)
In [7]:
acc = Account('bob', 1000)
In [8]:
print(acc)
 
Account of bob with starting amount: 1000
In [9]:
acc
Out[9]:
<__main__.Account at 0x1e4581f9cf8>
 

直接顯示變量調用的不是 __str__(),而是 __repr__(),前者是給用戶看的,後者則是爲調試服務的。jquery

In [10]:
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)
In [11]:
acc = Account('Bob', 1000)
In [12]:
acc
Out[12]:
Account('Bob', 1000)
In [13]:
print(acc)
 
Account of Bob with starting amount: 1000
 

__iter__, __getitem__, __len__

 

咱們先給 Account 類實現一個交易函數和一個查看交易餘額的屬性。linux

In [88]:
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)
In [89]:
acc = Account('Bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
In [90]:
acc.balance
Out[90]:
80
 

@property 是一個 decorator,具體細節查看 @propertyandroid

 

接下來,咱們要實現如下功能:css3

  • 查看交易次數
  • 查看每次交易的金額
In [91]:
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]
In [92]:
acc = Account('Bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
In [93]:
for i in acc:
    print(i)
 
20
-10
50
-20
30
In [94]:
len(acc)
Out[94]:
5
In [95]:
acc[::-1]
Out[95]:
[30, -20, 50, -10, 20]
In [96]:
acc[::2]
Out[96]:
[20, 50, 30]
 

以上,咱們就能夠利用len() 函數來查看交易次數,利用切片或者迭代來查看每次交易金額。

 

__eq__, __lt__

 

接下來咱們進行運算符重載,使不一樣的 Account 類相互之間能夠進行比較,咱們比較帳戶餘額。

 

爲了方便,咱們實現 __eq__ 和 __lt__方法,而後利用 functools.total_ordering 這個 decorator 來完善其餘的比較運算。

In [97]:
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
In [98]:
acc1 = Account('bob')
acc2 = Account('tim', 100)
print(acc1.balance)
print(acc2.balance)
 
0
100
In [99]:
acc1 > acc2
Out[99]:
False
In [100]:
acc1 < acc2
Out[100]:
True
 

__add__, __radd__

 

下面咱們實現帳戶合併,具體實現:

In [101]:
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
In [102]:
acc1 = Account('bob', 0)
acc2 = Account('tim', 100)
acc3 = Account('james', 200)
In [103]:
acc1 + acc2
Out[103]:
Account('bob&tim', 100)
In [104]:
acc2 + acc3
Out[104]:
Account('tim&james', 300)
 

acc1 + acc2 至關於 acc1.__add__(acc2)

 

咱們嘗試一下:

In [105]:
sum([acc1, acc2, acc3])
 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-105-0fad3caba539> in <module>()
----> 1sum([acc1, acc2, acc3])

TypeError: unsupported operand type(s) for +: 'int' and 'Account'
 

報錯:'int' 和 'Account' 兩種不一樣類型不能相加。這是因爲 sum() 從0開始執行:

0.__add__(acc1)

0的 __add__方法固然不可能和 acc1 相加,所以,Python會嘗試調用:

acc1.__radd__(0)

因此,咱們接下來要實現這個方法。

In [106]:
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)
In [107]:
acc1 = Account('bob', 0)
acc2 = Account('tim', 100)
acc3 = Account('james', 200)
In [108]:
sum([acc1, acc2, acc3])
Out[108]:
Account('bob&tim&james', 300)
 

關於 __add__ 和 __radd__ 具體使用能夠看看這裏

 

__call__

 

接下來咱們實現實例自己的調用,即像調用函數同樣調用實例。

In [111]:
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))
In [112]:
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()
 
Start amount: 10
Transactions: 
20 -10 50 -20 30 
Balance: 80
 

__enter__, __exit__

 

最後咱們實現上下文管理器。

寫代碼時,咱們但願把一些操做放到一個代碼塊中,這樣在代碼塊中執行時就能夠保持在某種運行狀態,而當離開該代碼塊時就執行另外一個操做,結束當前狀態;因此,簡單來講,上下文管理器的目的就是規定對象的使用範圍,若是超出範圍就採起「處理」。這一功能是在Python2.5以後引進的,它的優點在於能夠使得你的代碼更具可讀性,且不容易出錯。

詳細內容能夠參考:

Python學習筆記(五)-- 上下文管理器(Context Manager)

what is a 「context manager」 in Python?

Python __exit__,__enter__函數with語句的組合應用

In [113]:
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')    #代碼塊未拋出異常,交易成功
In [114]:
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))    #交易結束
 
Balance start: 10
ENTER WITH: making backup of transactions for rollback
adding 20 to account
new balance would be 30
EXIT WITH: transaction ok

Blance end: 30
 

上面是交易成功的狀況,下面展現餘額不足交易失敗的狀況:

In [115]:
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))    #交易結束
 
Balance start: 10
ENTER WITH: making backup of transactions for rollback
adding -40 to account
new balance would be -30
EXIT WITH: rolling back to previous transactions
transaction resulted in ValueError (sorry cannot go in debt!)

Blance end: 10
 

餘額不足時拋出異常,異常傳入__exit__,咱們就能夠恢復到交易前狀態。

相關文章
相關標籤/搜索