注:原书做者 Steven F. Lott,原书名为 Mastering Object-oriented Pythonpython
__init__()
方法意义重大的缘由有两个。第一是在对象生命周期中初始化是最重要的一步;每一个对象必须正确初始化后才能正常工做。第二是__init__()
参数值能够有多种形式。程序员
由于有不少种方式为__init__()
提供参数值,因此对于对象建立有大量的使用案例,咱们能够看看其中的几个。咱们想尽量的弄清楚,所以咱们须要定义一个初始化来正确的描述问题域。设计模式
在咱们接触__init__()
方法以前,不管如何,咱们都须要简单粗略地看看Python中隐含的object
类的层次结构。函数
在这一章,咱们看看不一样形式的简单对象的初始化(例如:打牌)。在这以后,咱们还能够看看更复杂的对象,就像包含集合的hands
以及包含策略和状态的players
。ui
每个Python类都隐含了一个超类:object。它是一个很是简单的类定义,几乎不作任何事情。咱们能够建立object
的实例,可是咱们不能用它作太多,由于许多特殊的方法容易抛出异常。编码
当咱们自定义一个类,object
则为超类。下面是一个类定义示例,它使用新的名称简单的继承了object
:命令行
class X: pass
下面是和自定义类的一些交互:设计
>>> X.__class__ <class 'type'> >>> X.__class__.__base__ <class 'object'>
咱们能够看到该类是type
类的一个对象,且它的基类为object
。code
就像在每一个方法中看到的那样,咱们也看看从object
继承的默认行为。在某些状况下,超类的特殊方法是咱们想要的。而在其余状况下,咱们又须要覆盖这个特殊方法。对象
__init__()
方法对象生命周期的基础是它的建立、初始化和销毁。咱们将建立和销毁推迟到后面章节的高级特殊方法中讲,目前只关注初始化。
全部类的超类object
,有一个默认包含pass
的__init__()
方法,咱们不须要去实现它。若是不实现它,则在对象建立后就不会建立实例变量。在某些状况下,这种默认行为是能够接受的。
咱们老是给对象添加属性,该对象为基类object
的子类。思考下面的类,它须要两个实例变量但不初始化它们:
class Rectangle: def area(self): return self.length * self.width
Rectangle
类有一个使用两个属性来返回一个值的方法。这些属性没有初始化,是合法的Python代码。它能够明确地避免设置属性,虽然感受有点奇怪,可是合法。
下面是与Rectangle
类的交互:
>>> r = Rectangle() >>> r.length, r.width = 13, 8 >>> r.area() 104
显然这是合法的,但这也是容易混淆的根源,因此也是咱们须要避免的缘由。
不管如何,这个设计给予了很大的灵活性,这样有时候咱们不用在__init__()
方法中设置全部属性。至此咱们走的很顺利。一个可选属性其实就是一个子类,只是没有真正的正式声明为子类。咱们建立多态在某种程度上可能会引发混乱,以及if
语句的不恰当使用所形成的盘绕。虽然未初始化的属性多是有用的,但也颇有多是糟糕设计的前兆。
《Python之禅》中的建议:
"显式比隐式更好。"
一个__init__()
方法应该让实例变量显式。
很是差的多态
灵活和愚蠢就在一念之间。
当咱们以为须要像下面这样写的时候,咱们正从灵活的边缘走向愚蠢:
if 'x' in self.__dict__:
或者:
try: self.x except AttributeError:
是时候从新考虑API并添加一个通用的方法或属性。重构比添加if
语句更明智。
__init__()
咱们经过实现__init__()
方法来初始化对象。当一个对象被建立,Python首先建立一个空对象并为该新对象调用__init__()
方法。这个方法函数一般用来建立对象的实例变量并执行任何其余一次性处理。
下面是Card
类示例定义的层次结构。咱们将定义Card
超类和三个子类,这三个子类是Card
的变种。两个实例变量直接由参数值设置,并经过初始化方法计算:
class Card: def __init__(self, rank, suit): self.suit = suit self.rank = rank self.hard, self.soft = self._points() class NumberCard(Card): def _points(self): return int(self.rank), int(self.rank) class AceCard(Card): def _points(self): return 1, 11 class FaceCard(Card): def _points(self): return 10, 10
在这个示例中,咱们提取__init__()
方法到超类,这样在Card
超类中的通用初始化能够适用于三个子类NumberCard
、AceCard
和FaceCard
。
这是一种常见的多态设计。每个子类都提供一个惟一的_points()
方法实现。全部子类都有相同的签名:有相同的方法和属性。这三个子类的对象在一个应用程序中能够交替使用。
若是咱们为花色使用简单的字符,咱们能够建立Card
实例,以下所示:
cards = [AceCard('A', '♠'), NumberCard('2','♠'), NumberCard('3','♠'),]
咱们在列表中枚举出一些牌的类、牌值和花色。从长远来讲,咱们须要更智能的工厂函数来建立Card
实例,用这个方法枚举52张牌无聊且容易出错。在咱们接触工厂函数以前,咱们看一些其余问题。
__init__()
建立显而易见的常量能够给牌定义花色类。在二十一点中,花色可有可无,简单的字符串就能够。
咱们使用花色构造函数做为建立常量对象示例。在许多状况下,咱们应用中小部分对象能够经过常量集合来定义。小部分的静态对象多是实现策略模式或状态模式的一部分。
在某些状况下,咱们会有一个在初始化或配置文件中建立的常量对象池,或者咱们能够基于命令行参数建立常量对象。咱们会在第十六章《命令行处理》中获取初始化设计和启动设计的详细信息。
Python没有简单正式的机制来定义一个不可变对象,咱们将在第三章《属性访问、特性和描述符》中看看保证不可变性的相关技术。在本示例中,花色不可变是有道理的。
下面这个类,咱们将用于建立四个显而易见的常量:
class Suit: def __init__(self, name, symbol): self.name = name self.symbol = symbol
下面是经过这个类建立的常量:
Club, Diamond, Heart, Spade = Suit('Club','♣'), Suit('Diamond','♦'), Suit('Heart','♥'), Suit('Spade','♠')
如今咱们能够经过下面展现的代码片断建立cards
:
cards = [AceCard('A', Spade), NumberCard('2', Spade), NumberCard('3', Spade),]
这个小示例的方法对于单个字符花色的代码来讲并无多大改进。在更复杂的状况下,会经过这个方式建立一些策略或状态对象。从小的静态常量池中复用对象使得策略或状态设计模式效率更高。
咱们必须认可,在Python中这些对象并非技术上一成不变的,它是可变的。进行额外的编码使得这些对象真正不可变可能会有一些好处。
可有可无的不变性
不变性颇有吸引力但却容易带来麻烦。有时候神话般的“恶意程序员”在他们的应用程序中经过修改常量值进行调整。从设计上考虑,这是很是愚蠢的。这些神话般的、恶意的程序员不会中止这样作。在Python中没有更好的方法保证没有白痴的代码。恶意程序员访问到源码而且修改它仅仅是但愿尽量轻松地编写代码来修改一个常数。
在定义不可变对象的类的时候最好不要挣扎过久。在第三章《属性访问、特性和描述符》中,咱们将在有bug的程序中提供合适的诊断信息来展现如何实现不变性。