美文网首页
Python Magic Method

Python Magic Method

作者: SkyDavid | 来源:发表于2016-02-28 17:58 被阅读0次

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,但在操作符覆盖上常常使用

相关文章

网友评论

      本文标题:Python Magic Method

      本文链接:https://www.haomeiwen.com/subject/ivawkttx.html