• 【笔记】流畅的Python


    1. 一摞Python风格的纸牌

    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]
    

    collections.namedtuple : 用以构建只有少数属性但是没有方法的对象,比如数据库条目。

    >>> beer_card = Card('7', 'diamonds')
    >>> beer_card
    Card(rank='7', suit='diamonds')
    

    __len__() : 提供 len() 函数对该类的访问。

    __getitem__() : 用于按照某一规则得到相关属性,例如 random.choice()

    >>> from random import choice
    >>>choice(deck)
    Card(rank='3', suit='hearts')
    >>> choice(deck)
    Card(rank='K', suit='spades')
    >>>choice(deck)
    Card(rank='2', suit="clubs")
    

    现在已经可以体会到通过实现 魔法方法 来利用 Python数据模型的两个好处:

    • 作为你定义的类的用户,他们不必去记住标准操作的各式名称(“怎么得到元素的总数?是 .size() 还是 .length() 还是别的什么?”)

    • 可以更加方便地利用Python的标准库,比如 random.choice() 函数,从而不用重新发明轮子(通过 __getitem__() 获得)。

    而且好戏还在后面。

    因为 __getitem__ 方法把 [] 操作交给了 self._cards 列表,所以我们的 deck 类自动支持切片(slicing)操作。下面列出了查看一摞牌最上面 3 张和只看牌面是 A 的牌的操作。其中第二种操作的具体方法是,先抽出索引是 12 的那张牌,然后每隔 13 张牌拿 1 张:

    >>> deck[:3]
    [Card(rank='2', suit='spades'), Card(rank='3', suit='spades'),
    Card(rank='4', suit='spades')]
    >>> deck[12::13]
    [Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'),
    Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
    

    另外,仅仅实现了 __getitem__ 方法,这一摞牌就变成可迭代的了:

    >>> for card in deck:
    ...     print(card)
    Card(rank='2', suit='spades')
    Card(rank='3', suit='spades')
    Card(rank='4', suit='spades')
    ...
    

    反向迭代也没关系:

    >>> for card in reversed(deck): # doctest: +ELLIPSIS
    ...     print(card)
    Card(rank='A', suit='hearts')
    Card(rank='K', suit='hearts')
    Card(rank='Q', suit='hearts')
    ...
    

    2. 不要使用可变类型作为参数的默认值

    可选参数可以有默认值,这是 Python 函数定义的一个很棒的特性,这样我们的 API 在进化的同时能保证向后兼容。然而,我们应该避免使用可变的对象作为参数的默认值。

    下面在示例 8-12 中说明这个问题。我们以示例 8-8 中的 Bus 类为基础定义一个新类,HauntedBus,然后修改 init 方法。这一次,passengers 的默认值不是 None,而是 [],这样就不用像之前那样使用 if 判断了。这个“聪明的举动”会让我们陷入麻烦。

    class HauntedBus:
        """备受幽灵乘客折磨的校车"""
        def __init__(self, passengers=[]):
            self.passengers = passengers
    
        def pick(self, name):
            self.passengers.append(name)
    
        def drop(self, name):
            self.passengers.remove(name)
    

    HauntedBus 的诡异行为如示例 8-13 所示。

    >>> bus1 = HauntedBus(['Alice', 'Bill'])
    >>> bus1.passengers
    ['Alice', 'Bill']
    >>> bus1.pick('Charlie')
    >>> bus1.drop('Alice')
    >>> bus1.passengers ➊
    ['Bill', 'Charlie']
    >>> bus2 = HauntedBus() ➋
    >>> bus2.pick('Carrie')
    >>> bus2.passengers
    ['Carrie']
    >>> bus3 = HauntedBus() ➌
    >>> bus3.passengers ➍
    ['Carrie']
    >>> bus3.pick('Dave')
    >>> bus2.passengers ➎
    ['Carrie', 'Dave']
    >>> bus2.passengers is bus3.passengers ➏
    True
    >>> bus1.passengers ➐
    ['Bill', 'Charlie']
    

    ❶ 目前没什么问题,bus1 没有出现异常。

    ❷ 一开始,bus2 是空的,因此把默认的空列表赋值给 self.passengers。

    ❸ bus3 一开始也是空的,因此还是赋值默认的列表。

    ❹ 但是默认列表不为空!

    ❺ 登上 bus3 的 Dave 出现在 bus2 中。

    ❻ 问题是,bus2.passengers 和 bus3.passengers 指代同一个列表。

    ❼ 但 bus1.passengers 是不同的列表。

    问题在于,没有指定初始乘客的 HauntedBus 实例会共享同一个乘客列表。

    这种问题很难发现。如示例 8-13 所示,实例化 HauntedBus 时,如果传入乘客,会按预期运作。但是不为 HauntedBus 指定乘客的话,奇怪的事就发生了,这是因为self.passengers 变成了 passengers 参数默认值的别名。出现这个问题的根源是,默认值在定义函数时计算(通常在加载模块时),因此默认值变成了函数对象的属性。因此,如果默认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影响。

    可变默认值导致的这个问题说明了为什么通常使用 None 作为接收可变值的参数的默认值。在示例 8-8 中,init 方法检查 passengers 参数的值是不是 None,如果是就把一个新的空列表赋值给 self.passengers。

  • 相关阅读:
    毫秒级从百亿大表任意维度筛选数据,是怎么做到的...
    编译时异常和运行时异常的区别
    ajax同步与异步的区别
    jdk、jre、jvm三者联系
    java可变参数
    String 堆内存和栈内存
    构造方法
    为什么成员变量不用先初始化
    Javascript 创建对象方法的总结
    Java四种读取和创建XML文档的例子教程
  • 原文地址:https://www.cnblogs.com/brt2/p/13732603.html
Copyright © 2020-2023  润新知