Python作者在其著作《Effective Python》中说过,很多使用Python的人,或多或少会写出带着“方言”的Python代码。Python有很多神奇的特性,也有其特殊的架构,本系列就是学习如何编写Pythonic的Python代码。

Pythonic风格的纸牌

在Python中,取长度信息通常使用len(obj),而非obj.len(),这与其他语言不同,但其实非常规范。其背后实际调用则是obj.__len__

还有,在使用类似Obj[key]的方法时,其背后就是调用__getitem__ 方法。调用的其实是obj.__getitem__(key)

下面,就写一个纸牌类

  • collections.namedtuple用来构建一个不需要方法的类,比如数据库条目
import collections


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

class FrenchDeck:
    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 = FrenchDeck()
>>> len(deck)
52
>>> deck[2]
Card(rank='4', suit='spades')

如果想从中随机选取一张纸牌,不需要重新编写函数,使用random.choice就可以:

>>> from random import choice
>>> choice(deck)
Card(rank='6', suit='hearts')
  • 使用Pythonic风格的编程,可以让你免去记忆.size()还是.length(),还能更好地利用标准库函数

不仅如此,由于__getitem__[]操作交给了self._cards,这让它支持切片操作!

>>> deck[:5]
 [Card(rank='2', suit='spades'),
 Card(rank='3', suit='spades'),
 Card(rank='4', suit='spades'),
 Card(rank='5', suit='spades'),
 Card(rank='6', suit='spades')]

自定义操作符:

Python还可以自定义操作符

下面建一个向量对象:

class Vector:
    
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    #用于print时输出,也可以用str(vec)方式调用,其实可以代替__str__
    #__repr用于调试,而str则是为了给终端用户看
    def __repr__(self):
        return 'Vector(%r, %r)' % (self.x, self.y)
    #abs(vec)
    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)
>>> v1 = Vector(1, 2)
>>> v2 = Vector(3, 4)
>>> v1 + v2
Vector(4, 6)

>>> v1 * 3
Vector(3, 6)

>>> abs(v1)
2.23606797749979

>>> print(v1)
Vector(1, 2)

>>> not v1
False

>>> v3 = Vector(0, 0)
>>> not v3
True

下面这段话摘自《Effective Python》:

如果 x 是一个内置类型的实例,那么 len(x) 的速度会非常快。背后的原因是 CPython 会直接从一个 C 结构体里读取对象的长度,完全不会调用任何方法。获取一个集合中元素的数量是一个很常见的操作,在str、list、memoryview 等类型上,这个操作必须高效。

换句话说,len 之所以不是一个普通方法,是为了让 Python 自带的数据结构可以走后门,abs 也是同理。但是多亏了它是特殊方法,我们也可以把 len 用于自定义数据类型。这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点,也印证了“Python 之禅”中的另外一句话:“不能让特例特殊到开始破坏既定规则。”

学习交流:
微信:ElijahMingLiu
Wechat

最后修改:2021 年 06 月 01 日 02 : 17 PM
如果觉得我的文章对你有用,请随意赞赏