The Python Data Model
Tags: Python

介紹

這個章節主要介紹python中的一些magic methods，顧名思義就是擁有魔法一樣，不需要去重新定義或架構一個方法，直接使用即可。例如，假如有一個a = [1, 2, 3]，想知道它的長度，可以直接使用len(a)=3，這是因為type(a)是list，list這個物件的class裡面已經有定義 __len__這個function，所以只要使用len()就直接呼叫__len__這個function。這些magic method的命名方式都是用__x__。

若想知道某個class有哪些magic methods，可以這個程式碼查看，例如想知道int class的magic methods

dir(int)

可以得到下面的magic methods

['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

關於magic methods介紹可以參考：https://www.tutorialsteacher.com/python/magic-methods-in-python

建立一個 class A Pythonic Card Deck

以下是以撲克牌為例子：

class FrenchDeck: ranks = [str(n) for n in range(2, 11)] + list( 'JQKA')] # ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J',

'Q', 'K', 'A'] suits = 'spades diamonds clubs hearts'.split() # ['spades',

'diamonds', 'clubs', 'hearts'] def __init__(self):

self._cards = [Card(rank, suit) for suit in self.suits for

rank in self.ranks]

def __len__(self):

return len(self._cards) def __getitem__(self, position):

return self._cards[position]

接下來使用collections.namedtuple來定義一個class，collections.namedtuple的好處是可以為class命名，增加可讀性

import collections Card = collections.namedtuple('Card', ['rank', 'suit']) print(Card)

可以得到：

Card(rank=’7', suit=’diamonds’)

接下來實體化FrenchDeck()，透過len(deck)可以得到撲克牌的所有花色及大小

deck = FrenchDeck() print(len(deck))

長度為52

這邊可以使用len()是因為在class FrenchDeck有定義__len__，若是將__len__拿掉會得到：

TypeError: object of type 'FrenchDeck' has no len()

若是使用 index查看deck裡的element:

print(deck[0])

可以得到Card(rank=’2', suit=’spades’)，這也是因為class FrenchDeck有定義__getitem__

Card(rank=’2', suit=’spades’)

deck[:3]

[Card(rank=’2', suit=’spades’), Card(rank=’3', suit=’spades’), Card(rank=’4', suit=’spades’)]

for card in deck:

print(card)

Card(rank=’A’, suit=’hearts’) Card(rank=’K’, suit=’hearts’) Card(rank=’Q’, suit=’hearts’)

所以說當有一個字串’123’或是一個[1, 2, 3]可以使用len()或是index就是因為class string, class list有__len__及__getitem_，可以用dir(str), dir(list)查看。

How Special Methods Are Used

這些magic methods都有一個共通點，就是它是透過Python interpreter主動呼叫，而不是透過使用者自己呼叫，所以上面的例子我們只需要使用len()，而不需要deck.__len__()。但是其實我們在寫程式時，不太需要去直接去呼叫這些特殊的方法，有一個例外的方法就是__init__()，相信對於class有一點概念的大概都會知道，使用__init__()目的是為了做初始化的動作，可以看上面的class FrenchDeck，當實體化一個物件時，它會呼叫__init__()。

盡量避免隨意定義特殊方法，其實命名特殊發法也是一門學問，在這邊就不做介紹，詳細內容可以參考：https://aji.tw/python%E4%BD%A0%E5%88%B0%E5%BA%95%E6%98%AF%E5%9C%A8__%E5%BA%95%E7%B7%9A__%E4%BB%80%E9%BA%BC%E5%95%A6/

Emulating Numeric Types

首先定義一個class Vector

class Vector: def __init__(self, x=0, y=0): self.x = x self.y = y

def __repr__(self):

return 'Vector(%r, %r)' % (self.x, self.y) def __abs__(self):

return hypot(self.x, self.y)

def __bool__(self):

return bool(abs(self)) def __add__(self, other): x = self.x + other.x y = self.y + other.y return Vector(x, y) def __mul__(self, scalar): return Vector(self.x * scalar, self.y * scalar)

Figure 1–1. Example of two-dimensional vector addition; Vector(2, 4) + Vector(2, 1) re‐ sults in Vector(4, 5)

v = Vector(3, 4)

Vector(3, 4)

abs(v)

5.0，這邊可以跟前面相呼應，直接使用abs() Python interpreter就會去呼叫__abs__()這個special method

v1 = Vector(2, 4)

Vector(2, 4)

v2 = Vector(2, 1)

Vector(2, 1)

v3 = v1 + v2

Vector(4, 5)