Python 的 Magic Method 就是以双下划线起始和结尾的方法。
为什么要有这些方法呢?我的理解是:
Python 中万物皆对象,所以如果 Python 完全遵循面向对象的原则,那么所有的方法调用应该都是 obj.method(args) 这种形式,但事实上 Python 中有许多的内置函数和操作符,这些内置函数和操作符调用的就是这些 magic method。所以说,magic method的存在是为了让自定义对象能够使用 Python 内置函数和操作符。
我认为 Pythonic 很大一部分指的就是这个。
A Guide to Python's Magic Methods
A Pythonic Card Deck
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class Deck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
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]
>>> deck = Deck()
>>> len(deck)
52
>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
Pythonic Vector
# -*- coding: utf8 -*-
from array import array
import reprlib
import math
import numbers
import operator
import functools
import itertools
class Vector:
"""
>>> Vector([1, 2])
Vector([1.0, 2.0])
>>> Vector([2.1, 3.0, 4.2])
Vector([2.1, 3.0, 4.2])
>>> Vector(range(10))
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
"""
typecode = 'd'
# 要求 components 是个能迭代的对象
def __init__(self, components):
self._components = array(self.typecode, components)
# 使 Vector 支持迭代
# Vector 能迭代的特性使得下面的一些 magic method 定义起来特别方便
def __iter__(self):
return iter(self._components)
def __repr__(self):
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return 'Vector({})'.format(components)
# tuple 中的参数要求能迭代
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return bytes([ord(self.typecode)]) + bytes(self._components)
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
def __bool__(self):
return bool(abs(self))
# 要使 Vector 是一个序列,必须支持 __len__, __getitem__ 方法
def __len__(self):
return len(self._components)
# 如果这样实现的话,如果我们用切片,会发现返回的是 array 而不是 Vector
# >>> v1 = Vector([3, 4, 5])
# array('d', [4.0, 5.0])
# def __getitem__(self, index):
# return self._components[index]
# 用下面这张方法实现能返回 Vector
# 注意切片时参数 index 是个切片对象
# v = v = Vector(range(7))
# >>> v[-1]
# 6.0
# >>> v[1:4]
# Vector([1.0, 2.0, 3.0])
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
msg = "{cls.__name__} indeces must be integers"
raise TypeError(msg.format(cls=cls))
# 由于我们想要 Vector immutable, 所以我们不定义 __setitem__
# 目前我们取得元素只能用 v[0] 这样的形式
# 我们想用 v.x 这样的形式取得元素
# >>> v = Vector(range(10))
# >>> v.x
# 0.0
# >>> v.k
# ... Vector object has no attribute k
shortcut_names = 'xyz'
def __getattr__(self, name):
cls = type(self)
if len(name) == 1:
pos = cls.shortcut_names.find(name)
if 0 <= pos < len(self):
return self[pos]
msg = "{cls.__name__} object has no attribute {name}"
raise AttributeError(msg.format(cls=cls, name=name))
# 由于 __getattr__ 的调用方式是:
# 先从对象的属性(包括父类属性)中寻找,找不到时才调用 __getattr__ 方法
# 所以如果我们动态绑定对象属性,则不会调用 __getattr__ 方法
# >>> v = Vector(range(10))
# >>> v.x
# 0.0
# >>> v.x = 10
# >>> v.x
# 10
# >>> v
# Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
# 我们当然可以用 __slots__ 限制对象属性,
# 但 __slots__ 的主要目的是节约内存而不是限制属性
# 在这里我们自定义 __setattr__
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcut_names:
msg = "readonly attribute {name}"
elif name.islower():
msg= "can't set attribute 'a' to 'z' in {cls.__name__}"
else:
msg = ''
if msg:
raise AttributeError(msg.format(cls=cls, name=name))
super().__setattr__(name, value)
# 要使 Vector hashable,必须定义 __eq__ 和 __hash__ 方法
# 这里 a == b 的调用顺序是:
# 先调用 a.__eq__(b)
# 如果返回 NotImplemented,则接着调用 b.__eq__(a)
# 如果仍然返回 NotImplemented,则使用两者的id比较值作为最后结果
def __eq__(self, other):
if isinstance(other, Vector):
return len(self) == len(other) and \
all(a == b for a, b in zip(self, other))
else:
return NotImplemented
def __hash__(self):
hashes = (hash(x) for x in self)
return functools.reduce(operator.xor, hashes, 0)
# **Operator Overloading**
# 定义 __neg__ 和 __pos__ 方法
# 一般这种一元操作符要求返回一个新对象
def __abs__(self):
return math.sqrt(sum(x * x for x in self))
def __neg__(self):
return Vector(-x for x in self)
def __pos__(self):
return Vector(self)
# __add__ 和 __iadd__
# 一般来说 __add__ 返回新对象,__iadd__ 修改本身
# 对一些 immutable 对象,虽然没有定义 __iadd__ 方法,但仍能使用 a += b
# 这是因为在这种情况下 a += b 是 a = a + b 的语法糖,调用的是 __add__
# >>> v = Vector([1, 2 ,3])
# >>> v + Vector([10, 10, 10])
# Vector([11.0, 12.0, 13.0])
# >>> v + (10, 10, 10)
# Vector([11.0, 12.0, 13.0])
def __add__(self, other):
try:
pairs = itertools.zip_longest(self, other, fillvalue=0.0)
return Vector(a+b for a, b in pairs)
else TypeError:
return NotImplemented
# 现在我们的 Vector 可以与其他可迭代对象相加,但反过来却不行
# Python 中另外有个所有操作符都有个反方法,比如 __radd__
def __radd__(self, other):
return self + other
def __mul__(self, scalar):
if isinstance(scalar, numbers.Real):
return Vector(x * scalar for x in self)
else:
return NotImplemented
def __rmul__(self, scalar):
return self * scalar
# 注意上面 __add__ 和 __mul__ 对传入参数类型的检测:
# __add__ 比较符合 duck typing,这种方式更加灵活
# __mul__ 对传入参数进行限制,这种方式更加安全
# 虽然用 isinstance 不符合 OOP,但在操作符覆盖上常常使用