详细解读Python中的__init__()方法


Posted in Python onMay 02, 2015

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

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

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

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

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

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

class X:
  pass

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

>>> X.__class__
<class 'type'>
>>> X.__class__.__base__
<class 'object'>

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

就像在每个方法中看到的那样,我们也看看从object继承的默认行为。在某些情况下,超类特殊方法的行为是我们所想要的。在其他情况下,我们需要覆盖这个特殊方法。
基类对象的init()方法

对象生命周期的基础是创建、初始化和销毁。我们将创建和销毁的高级特殊方法推迟到后面的章节中,目前只关注初始化。

所有类的超类object,有一个默认包含pass的__init__()实现,我们不需要去实现__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的程序中提供合适的诊断信息来展示如何实现不变性。

Python 相关文章推荐
python实现猜数字游戏(无重复数字)示例分享
Mar 29 Python
Python使用email模块对邮件进行编码和解码的实例教程
Jul 01 Python
python矩阵转换为一维数组的实例
Jun 05 Python
对Python 内建函数和保留字详解
Oct 15 Python
解决安装pycharm后不能执行python脚本的问题
Jan 19 Python
Python Django基础二之URL路由系统
Jul 18 Python
Python递归函数 二分查找算法实现解析
Aug 12 Python
用Pelican搭建一个极简静态博客系统过程解析
Aug 22 Python
Python列表原理与用法详解【创建、元素增加、删除、访问、计数、切片、遍历等】
Oct 30 Python
Python的pygame安装教程详解
Feb 10 Python
python等待10秒执行下一命令的方法
Jul 19 Python
Python爬虫框架之Scrapy中Spider的用法
Jun 28 Python
举例讲解Python的Tornado框架实现数据可视化的教程
May 02 #Python
Python的Bottle框架中返回静态文件和JSON对象的方法
Apr 30 #Python
使用Python编写提取日志中的中文的脚本的方法
Apr 30 #Python
简单的连接MySQL与Python的Bottle框架的方法
Apr 30 #Python
Python的Bottle框架中实现最基本的get和post的方法的教程
Apr 30 #Python
Python中使用Beautiful Soup库的超详细教程
Apr 30 #Python
Python中正则表达式的详细教程
Apr 30 #Python
You might like
晶体管单管来复再生式收音机
2021/03/02 无线电
PHP 程序授权验证开发思路
2009/07/09 PHP
php csv操作类代码
2009/12/14 PHP
在Mac OS上自行编译安装Apache服务器和PHP解释器
2015/12/24 PHP
老生常谈PHP中的数据结构:DS扩展
2017/07/17 PHP
Laravel 自带的Auth验证登录方法
2019/09/30 PHP
PHP7移除的扩展和SAPI
2021/03/09 PHP
jquery ajax提交表单数据的两种实现方法
2010/04/29 Javascript
JS实现金额转换(将输入的阿拉伯数字)转换成中文的实现代码
2013/09/30 Javascript
单元选择合并变色示例代码
2014/05/26 Javascript
PHP守护进程实例
2015/03/06 Javascript
jquery validate和jquery form 插件组合实现验证表单后AJAX提交
2015/08/26 Javascript
JS动态创建元素的两种方法
2016/04/20 Javascript
bootstrap手风琴制作方法详解
2017/01/11 Javascript
JavaScript关联数组用法分析【概念、定义、遍历】
2017/03/15 Javascript
Mongoose实现虚拟字段查询的方法详解
2017/08/15 Javascript
webpack本地开发环境无法用IP访问的解决方法
2018/03/20 Javascript
Node 升级到最新稳定版的方法分享
2018/05/17 Javascript
使用taro开发微信小程序遇到的坑总结
2019/04/08 Javascript
node学习笔记之读写文件与开启第一个web服务器操作示例
2019/05/29 Javascript
vue轮播组件实现$children和$parent 附带好用的gif录制工具
2019/09/26 Javascript
react的hooks的用法详解
2020/10/12 Javascript
Python爬虫之xlml解析库(全面了解)
2017/08/08 Python
python中numpy的矩阵、多维数组的用法
2018/02/05 Python
解决Spyder中图片显示太小的问题
2018/04/27 Python
python smtplib模块自动收发邮件功能(一)
2018/05/22 Python
python取余运算符知识点详解
2019/06/27 Python
pymysql的简单封装代码实例
2020/01/08 Python
pycharm设置python文件模板信息过程图解
2020/03/10 Python
解决Python中导入自己写的类,被划红线,但不影响执行的问题
2020/07/13 Python
金山毒霸系列的笔试题
2013/04/13 面试题
事业单位请假制度
2014/01/13 职场文书
2015社区健康教育工作总结
2015/05/20 职场文书
2019年让高校“心动”的自荐信
2019/03/25 职场文书
交通安全宣传标语(100条)
2019/08/22 职场文书
我们认为中短波广播场强仪的最佳组合
2022/04/05 无线电