《Python面向对象编程指南》——1.11 多策略的__init__()方法

1.11 多策略的__init__()方法

有些对象的创建来自多个来源。例如,我们也许需要克隆一个对象作为备忘录模式的一部分,或者冻结一个对象以使它可以用来作为字典的键或放入哈希集合;这也是set和fronezenset类的实现方式。

有很多全局的设计模式使用了多种方式来创建对象。其中一个为多策略初始化,__init__()函数的实现逻辑较为复杂,也会用到类层次结构中不同的(静态)构造函数。

它们是非常不同的实现方式,在接口的定义上就有根本区别。

之前的例子可以被高效的克隆是因为它们非常简单,在下一章中会进行展开描述。然而,为了更详细地说明更多关于对象克隆的基本技巧,我们会讨论一下如何把可变的Hand对象冻结成为不可变的Hand对象。

以下代码演示了两种创建Hand对象的例子。

class Hand3:
   def __init__( self, *args, **kw ):
     if len(args) == 1 and isinstance(args[0],Hand3):
       # Clone an existing hand; often a bad idea
       other= args[0]
       self.dealer_card= other.dealer_card
       self.cards= other.cards
     else:
       # Build a fresh, new hand.
       dealer_card, *cards = args
       self.dealer_card= dealer_card
       self.cards= list(cards)

第1种方式,Hand3实例从已有的Hand3对象创建。第2种方式,Hand3对象的创建基于Card实例。

一个fronzenset对象的创建可以基于已有的实例,或基于已存在集合对象。下一章会具体介绍创建不可变对象。基于已有的Hand,创建一个Hand对象,可用于创建一个Hand对象的备忘录模式,例如下面这段实现。

h = Hand( deck.pop(), deck.pop(), deck.pop() )
memento= Hand( h )

我们使用mem ento变量来保存Hand对象。可以用来比较当前对象和之前被处理的对象,我们也可以冻结它用于集合或映射。

1.11.1 更复杂的初始化方式

为了将多策略应用于初始化,通常要被迫放弃显式命名的参数。这样的设计虽然获得了灵活性,却使得参数名不够透明,意图不够明显,需要针对不同的使用场景分别提供文档进行解释说明。

也可以扩展初始化的实现来分离Hand对象。要分离的Hand对象只需修改构造函数。以下代码段演示了如何分离一个Hand对象。

class Hand4:
   def __init__( self, *args, **kw ):
     if len(args) == 1 and isinstance(args[0],Hand4):
       # Clone an existing handl often a bad idea
       other= args[0]
       self.dealer_card= other.dealer_card
       self.cards= other.cards
     elif len(args) == 2 and isinstance(args[0],Hand4) and 'split'
in kw:
       # Split an existing hand
       other, card= args
       self.dealer_card= other.dealer_card
       self.cards= [other.cards[kw['split']], card]
     elif len(args) == 3:
       # Build a fresh, new hand.
       dealer_card, *cards = args
       self.dealer_card= dealer_card
       self.cards= list(cards)
     else:
       raise TypeError( "Invalid constructor args={0!r}
kw={1!r}".format(args, kw) )
   def __str__( self ):
     return ", ".join( map(str, self.cards) )

这个设计需要传入更多的纸牌对象来创建合适的、分离的Hand对象。当我们从一个Hand4对象中分离出另一个Hand4对象时,使用split参数作为索引从原Hand4对象中读取Card对象。以下代码演示了我们怎样分离出一个Hand对象。

d = Deck()
h = Hand4( d.pop(), d.pop(), d.pop() )
s1 = Hand4( h, d.pop(), split=0 )
s2 = Hand4( h, d.pop(), split=1 )

我们初始化了一个Hand4类的实例然后再分离出其他的Hand4实例,命名为s1和s2,然后将Card对象传入每个Hand对象。在21点的规则中,只有当手中两张牌大小相等的时候才可允许分牌。可以看到__init__()函数的逻辑已经非常复杂,优势在于,它可以基于已有集合同时创建多个像fronzenset这样的对象。然而也将需要更多的注释和文档来说明这些行为。

1.11.2 静态函数的初始化

当我们有多种方式来创建一个对象时,有时使用静态函数好过使用复杂的__init__()函数。

也可以考虑使用类函数作为初始化的另一种选择,然而将依赖的对象作为参数传入函数会更好。当冻结或分离一个Hand对象时,我们或许希望创建两个新的静态函数来完成任务。使用静态函数作为代理构造函数在语法上略有差别,但是在代码的组织上却有明显的优势。

以下是Hand类的实现,使用了静态函数来完成初始化,从已有的Hand实例创建两个新实例。

class Hand5:
   def __init__( self, dealer_card, *cards ):
     self.dealer_card= dealer_card
     self.cards = list(cards)
   @staticmethod
   def freeze( other ):
     hand= Hand5( other.dealer_card, *other.cards )
     return hand
   @staticmethod
   def split( other, card0, card1 ):
     hand0= Hand5( other.dealer_card, other.cards[0], card0 )
     hand1= Hand5( other.dealer_card, other.cards[1], card1 )
     return hand0, hand1
   def __str__( self ):
     return ", ".join( map(str, self.cards) )

使用一个函数完成了冻结和备忘录模式,用另一个函数将Hand5对象分离为两个子实例。

这样既可以增强可读性,也不必使用参数名称来解释接口意图。

以下代码段演示了我们如何把Hand5对象进行分离:

d = Deck()
h = Hand5( d.pop(), d.pop(), d.pop() )
s1, s2 = Hand5.split( h, d.pop(), d.pop() )

我们创建了一个Hand5类的h实例,把它分为另外两个Hand实例,名为s1和s2,然后分别为它们赋值。而使用__init__()函数实现同样的功能时,split()静态函数的实现版本简化了很多。然而它并没有遵守一个原则:使用已有的set对象来创建fronzenset对象。

时间: 2024-09-19 20:36:26

《Python面向对象编程指南》——1.11 多策略的__init__()方法的相关文章

《Python面向对象编程指南》——第1部分 用特殊方法实现Python风格的类 第1章 __init__()方法 1.1 隐式的基类——object

第1部分 用特殊方法实现Python风格的类 init()方法 与Python无缝集成--基本特殊方法 属性访问.特性和修饰符 抽象基类设计的一致性 可调用对象和上下文的使用 创建容器和集合 创建数值类型 装饰器和Mixins--横切方面 用特殊方法实现 Python风格的类 通过重写特殊方法来完成对Python内部机制的调用,在Python中是很普遍的.例如len()函数就可以重写一个类的__len__()方法. 这意味着对于像(len(x))这样的通用公共接口,任何类(例如,声明一个类叫ti

《Python面向对象编程指南》——2.6 比较运算符方法

2.6 比较运算符方法 Python有6个比较运算符.这些运算符分别对应一个特殊方法的实现.根据文档,运算符和特殊方法的对应关系如下所示. x < y调用x.__lt__(y). x <=y调用x.__le__(y). x == y调用x.__eq__(y). x != y调用x.__ne__(y). x > y调用x.__gt__(y). x >= y调用x.__ge__(y). 我们会在第7章"创建数值类型"中再探讨比较运算符. 对于实际上使用了哪个比较运算

《Python面向对象编程指南》——导读

前 言 本书主要介绍Python语言的高级特性,特别是如何编写高质量的Python程序.这通常意味着编写高性能且拥有良好可维护性的程序.同时,我们也会探究不同的设计方案并确定究竟是哪种方案提供了最佳性能.而对于一些正在寻找解决方案的问题,这也是一种很好的方式. 本书的大部分内容将介绍一种给定设计的不同替代方案.一些方案性能更好,另一些方案更加简单或者更加适合于特定领域的问题.最重要的是,找到最好的算法和最优的数据结构,以最少的开销换取最大的价值.时间就是金钱,高效的程序会为它们的用户创造更多的价

《Python面向对象编程指南》——1.5 通过工厂函数调用__init()__

1.5 通过工厂函数调用__init()__ 我们可以使用工厂函数来完成所有Card对象的创建,这比枚举52张牌的方式好很多.在Python中,实现工厂有两种途径. 定义一个函数,返回不同类的对象. 定义一个类,包含了创建对象的方法.这是完整的工厂设计模式,正如设计模式书中提到的.在类似Java这样的语言里,工厂类层次结构是必需的,因为语言本身不支持可以脱离类而单独存在的函数. 在Python里,类定义不是必需的.仅当特别复杂的情形,工厂类才是不错的选择.Python的优势之一是,对于只需要简单

《Python面向对象编程指南》——1.7 简单的组合对象

1.7 简单的组合对象 一个组合对象也可以称作容器.我们会从一个简单的组合对象开始介绍:一副牌.这是一个基本的集合对象.我们的确可以简单地使用一个list来代替一副牌(deck)对象. 在设计一个类之前,我们需要考虑这样的一个问题:简单地使用list是合适的做法吗? 可以使用random.shuffle()函数完成洗牌操作,使用deck.pop()来完成发牌操作. 一些程序员可能会过早定义新类,正如像使用内置类一样,违反了一些面向对象的设计原则.比如像下面的这个设计. d= [card6(r+1

《Python面向对象编程指南》——2.5 __bytes__()方法

2.5 __bytes__()方法 只有很少的情景需要我们把对象转换为字节.在第2部分"持久化和序列化"中,我们会详细探讨这个主题. 通常,应用程序会创建一个字符串,然后使用Python的IO类内置的编码方法将字符串转换为字节.对于大多数情况,这种方法就足够了.只有当我们自定义一种新的字符串时,我们会需要定义这个字符串的编码方法. 依据不同的参数,bytes()函数的行为也不同. bytes(integer):返回一个不可变的字节对象,这个对象包含了给定数量的0x00值. bytes(

《Python面向对象编程指南》——2.8 __new__()方法和不可变对象

2.8 __new__()方法和不可变对象 __new__方法的一个用途是初始化不可变对象.__new__()方法中允许创建未初始化的对象.这允许我们在__init__()方法被调用之前先设置对象的属性. 由于不可变类的__init__()方法很难重载,因此__new__方法提供了一种扩展这种类的方法. 下面是一个错误定义的类,我们定义了float的一个包含单位信息的版本. class Float_Fail( float ): def __init__( self, value, unit ):

《Python面向对象编程指南》——1.9 不带__init__()方法的无状态对象

1.9 不带__init__()方法的无状态对象 以下是一个不需要__init__()方法的类定义.对于策略模式的对象来说这是常见的设计.一个策略对象以插件的形式复合在主对象上来完成一种算法或逻辑.它或许依赖主对象中的数据,策略对象自身并不携带任何数据.通常策略类会和享元设计模式一起使用:在策略对象中避免内部存储.所有需要的值都从策略对象的方法参数传入.策略对象自身是无状态的,可以把它看作是一系列函数的集合. 这里定义了一个类给Player实例提供游戏模式的选择,以下这个策略包括了拿牌和下调投注

《Python面向对象编程指南》——2.10 总结

2.10 总结 我们已经介绍了许多基本的特殊方法,它们是我们在设计任何类时的基本特性.这些方法已经包含在每个类中,只是它们的默认行为不一定能满足我们的需求. 我们几乎总是需要重载__repr__().__str__().和__format__().这些方法的默认实现不是非常有用. 我们几乎不需要重载__bool__()方法,除非我们想自定义集合.这是第6章"创建容器和集合"的主题. 我们常常需要重载比较运算符和__hash__()方法.默认的实现只适合于比较简单不可变对象,但是不适用于