您现在的位置: 万盛学电脑网 >> 程序编程 >> 网络编程 >> 编程语言综合 >> 正文

详细解析Python中

作者:佚名    责任编辑:admin    更新时间:2022-06-22

   这篇文章主要介绍了详细解析Python中__init__()方法的高级应用,包括在映射和elif序列等地方的更为复杂的用法,需要的朋友可以参考下

  通过工厂函数对 __init__() 加以利用

  我们可以通过工厂函数来构建一副完整的扑克牌。这会比枚举所有52张扑克牌要好得多,在Python中,我们有如下两种常见的工厂方法:

  定义一个函数,该函数会创建所需类的对象。

  定义一个类,该类有创建对象的方法。这是一个完整的工厂设计模式,正如设计模式书所描述的那样。在诸如Java这样的语言中,工厂类层次结构是必须的,因为该语言不支持独立的函数。

  在Python中,类并不是必须的。只是当有相关的工厂非常复杂的时候才会显现出优势。Python的优势就是当一个简单的函数可以做的更好的时候我们决不强迫使用类层次结构。

  虽然这是一本关于面向对象编程的书,但函数真是一个好东西。这在Python中是常见的也是最地道的。

  如果需要的话,我们总是可以将一个函数重写为适当的可调用对象。我们可以将一个可调用对象重构到我们的工厂类层次结构中。我们将在第五章《使用可调用对象和上下文》中学习可调用对象。

  一般,类定义的优点是通过继承实现代码重用。工厂类的函数就是包装一些目标类层次结构和复杂对象的构造。如果我们有一个工厂类,当扩展目标类层次结构的时候,我们可以添加子类到工厂类中。这给我们提供了多态性工厂类;不同的工厂类定义具有相同的方法签名,可以交替使用。

  这类水平的多态性对于静态编译语言如Java或C++非常有用。编译器可以解决类和方法生成代码的细节。

  如果选择的工厂定义不能重用任何代码,则在Python中类层次结构不会有任何帮助。我们可以简单的使用具有相同签名的函数。

  以下是我们各种Card子类的工厂函数:

  ?

1 2 3 4 5 6 7 8 9 10 11 def card(rank, suit): if rank == 1: return AceCard('A', suit) elif 2 <= rank < 11: return NumberCard(str(rank), suit)   elif 11 <= rank < 14: name = {11: 'J', 12: 'Q', 13: 'K' }[rank] return FaceCard(name, suit) else: raise Exception("Rank out of range")

  这个函数通过数值类型的rank和suit对象构建Card类。我们现在可以非常简单的构建牌了。我们已经封装构造问题到一个单一的工厂函数中,允许应用程序在不知道精确的类层次结构和多态设计是如何工作的情况下进行构建。

  下面是一个如何通过这个工厂函数构建一副牌的示例:

  ?

1 deck = [card(rank, suit) for rank in range(1,14) for suit in (Club, Diamond, Heart, Spade)]

  它枚举了所有的牌值和花色来创建完整的52张牌。

  1. 错误的工厂设计和模糊的else子句

  注意card()函数里面的if语句结构。我们没有使用“包罗万象”的else子句来做任何处理;我们只是抛出异常。使用“包罗万象”的else子句会引出一个小小的辩论。

  一方面,从属于else子句的条件不能不言而喻,因为它可能隐藏着微妙的设计错误。另一方面,一些else子句确实是显而易见的。

  重要的是要避免含糊的else子句。

  考虑下面工厂函数定义的变体:

  ?

1 2 3 4 5 6 7 8 def card2(rank, suit): if rank == 1: return AceCard('A', suit) elif 2 <= rank < 11: return NumberCard(str(rank), suit) else: name = {11: 'J', 12: 'Q', 13: 'K'}[rank] return FaceCard(name, suit)

  以下是当我们尝试创建整副牌将会发生的事情:

  ?

1 deck2 = [card2(rank, suit) for rank in range(13) for suit in (Club, Diamond, Heart, Spade)]

  它起作用了吗?如果if条件更复杂了呢?

  一些程序员扫视的时候可以理解这个if语句。其他人将难以确定是否所有情况都正确执行了。

  对于高级Python编程,我们不应该把它留给读者去演绎条件是否适用于else子句。对于菜鸟条件应该是显而易见的,至少也应该是显示的。

  何时使用“包罗万象”的else

  尽量的少使用。使用它只有当条件是显而易见的时候。当有疑问时,显式的并抛出异常。

  避免含糊的else子句。

  2. 简单一致的使用elif序列

  我们的工厂函数card()是两种常见工厂设计模式的混合物:

  if-elif序列

  映射

  为了简单起见,最好是专注于这些技术的一个而不是两个。

  我们总是可以用映射来代替elif条件。(是的,总是。但相反是不正确的;改变elif条件为映射将是具有挑战性的。)

  以下是没有映射的Card工厂:

  ?

1 2 3 4 5 6 7 8 9 10 11 12 13 def card3(rank, suit): if rank == 1: return AceCard('A', suit) elif 2 <= rank < 11: return NumberCard(str(rank), suit) elif rank == 11: return FaceCard('J', suit) elif rank == 12: return FaceCard('Q', suit) elif rank == 13: return FaceCard('K', suit) else: raise Exception("Rank out of range")

  我们重写了card()工厂函数。映射已经转化为额外的elif子句。这个函数有个优点就是它比之前的版本更加一致。

  3. 简单的使用映射和类对象

  在一些示例中,我们可以使用映射来代替一连串的elif条件。很可能发现条件太复杂,这个时候或许只有使用一连串的elif条件来表达才是明智的选择。对于简单示例,无论如何,映射可以做的更好且可读性更强。

  因为class是最好的对象,我们可以很容易的映射rank参数到已经构造好的类中。

  以下是仅使用映射的Card工厂:

  ?

1 2 3 def card4(rank, suit): class_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard) return class_(rank, suit)

  我们已经映射rank对象到类中。然后,我们传递rank值和suit值到类来创建最终的Card实例。

  最好我们使用defaultdict类。无论如何,对于微不足道的静态映射不会比这更简单了。看起来像下面代码片段那样:

  defaultdict(lambda: NumberCard, {1: AceCard, 11: FaceCard, 12: FaceCard, 12: FaceCard})

  注意:defaultdict类默认必须是零参数的函数。我们已经使用了lambda创建必要的函数来封装常量。这个函数,无论如何,都有一些缺陷。对于我们之前版本中缺少1到A和13到K的转换。当我们试图增加这些特性时,一定会出现问题的。

  我们需要修改映射来提供可以和字符串版本的rank对象一样的Card子类。对于这两部分的映射我们还可以做什么?有四种常见解决方案:

  可以做两个并行的映射。我们不建议这样,但是会强调展示不可取的地方。

  可以映射个二元组。这个同样也会有一些缺点。

  可以映射到partial()函数。partial()函数是functools模块的一个特性。

  可以考虑修改我们的类定义,这种映射更容易。可以在下一节将__init__()置入子类定义中看到。

  我们来看看每一个具体的例子。

  3.1. 两个并行映射

  以下是两个并行映射解决方案的关键代码:

  ?

1 2 3 class_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard) rank_str= {1:'A', 11:'J', 12:'Q', 13:'