资讯专栏INFORMATION COLUMN

[译] Python 学习 —— __init__() 方法 1

MobService / 1654人阅读

摘要:第一是在对象生命周期中初始化是最重要的一步每个对象必须正确初始化后才能正常工作。第二是参数值可以有多种形式。基类对象的方法对象生命周期的基础是它的创建初始化和销毁。在某些情况下,这种默认行为是可以接受的。

注:原书作者 Steven F. Lott,原书名为 Mastering Object-oriented Python

__init__()方法意义重大的原因有两个。第一是在对象生命周期中初始化是最重要的一步;每个对象必须正确初始化后才能正常工作。第二是__init__()参数值可以有多种形式。

因为有很多种方式为__init__()提供参数值,所以对于对象创建有大量的使用案例,我们可以看看其中的几个。我们想尽可能的弄清楚,因此我们需要定义一个初始化来正确的描述问题域。

在我们接触__init__()方法之前,无论如何,我们都需要简单粗略地看看Python中隐含的object类的层次结构。

在这一章,我们看看不同形式的简单对象的初始化(例如:打牌)。在这之后,我们还可以看看更复杂的对象,就像包含集合的hands以及包含策略和状态的players

隐式超类——object

每一个Python类都隐含了一个超类:object。它是一个非常简单的类定义,几乎不做任何事情。我们可以创建object的实例,但是我们不能用它做太多,因为许多特殊的方法容易抛出异常。

当我们自定义一个类,object则为超类。下面是一个类定义示例,它使用新的名称简单的继承了object

class X:
    pass

下面是和自定义类的一些交互:

>>> X.__class__

>>> X.__class__.__base__

我们可以看到该类是type类的一个对象,且它的基类为object

就像在每个方法中看到的那样,我们也看看从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超类中的通用初始化可以适用于三个子类NumberCardAceCardFaceCard

这是一种常见的多态设计。每一个子类都提供一个唯一的_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的程序中提供合适的诊断信息来展示如何实现不变性。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37510.html

相关文章

  • [] Python 学习 —— __init__() 方法 3

    摘要:简单复合对象复合对象也可被称为容器。它难以序列化对象并像这样初始化来重建。接口仍然会导致多种方法计算。还要注意一些不完全遵循点规则的方法功能。逐步增加项目的方法和一步加载所有项目的方法是一样的。另一个方法就是之前那样的类定义。 注:原书作者 Steven F. Lott,原书名为 Mastering Object-oriented Python 在各个子类中实现__init_...

    leanote 评论0 收藏0
  • [] Python 学习 —— __init__() 方法 2

    摘要:工厂类的函数就是包装一些目标类层次结构和复杂对象的构造。连贯的工厂类接口在某些情况下,我们设计的类在方法使用上定义好了顺序,按顺序求方法的值很像函数。这个工厂类可以像下面这样使用首先,我们创建一个工厂实例,然后我们使用那个实例创建实例。 注:原书作者 Steven F. Lott,原书名为 Mastering Object-oriented Python 通过工厂函数对 __init_...

    xiaotianyi 评论0 收藏0
  • [] Python 学习 —— __init__() 方法 4

    摘要:同时,有多个类级别的静态构造函数的方法。这个累赘,无论如何,是被传递到每个单独的对象构造函数表达式中。我们可能只有几个特定的担忧,提供额外关键字参数给构造函数。 注:原书作者 Steven F. Lott,原书名为 Mastering Object-oriented Python 没有__init__()的无状态对象 下面这个示例,是一个简化去掉了__init__()的类。这是一个常见...

    yvonne 评论0 收藏0
  • []PEP 380--子生成器的语法

    摘要:提议以下的新的生成器语法将被允许在生成器的内部使用其中表达式作用于可迭代对象,从迭代器中提取元素。子迭代器而非生成器的语义被选择成为生成器案例的合理泛化。建议如果关闭一个子迭代器时,引发了带返回值的异常,则将该值从调用中返回给委托生成器。 导语: PEP(Python增强提案)几乎是 Python 社区中最重要的文档,它们提供了公告信息、指导流程、新功能的设计及使用说明等内容。对于学习...

    fevin 评论0 收藏0

发表评论

0条评论

MobService

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<