- 译者序
- 前言
- 第1章 问答环节
- 第2章 Python 如何运行程序
- 第3章 如何运行程序
- 第4章 介绍 Python 对象类型
- 第5章 数字
- 第6章 动态类型简介
- 第7章 字符串
- 第8章 列表与字典
- 第9章 元组、文件及其他
- 第10章 Python 语句简介
- 第11章 赋值、表达式和打印
- 第12章 if 测试和语法规则
- 第13章 while 和 for 循环
- 第14章 迭代器和解析,第一部分
- 第15章 文档
- 第16章 函数基础
- 第17章 作用域
- 第18章 参数
- 第19章 函数的高级话题
- 第20章 迭代和解析,第二部分
- 第21章 模块:宏伟蓝图
- 第22章 模块代码编写基础
- 第23章 模块包
- 第24章 高级模块话题
- 第25章 OOP:宏伟蓝图
- 第27章 更多实例
- 第28章 类代码编写细节
- 第29章 运算符重载
- 第30章 类的设计
- 第31章 类的高级主题
- 第32章 异常基础
- 第34章 异常对象
- 第35章 异常的设计
- 第36章 Unicode 和字节字符串
- 字符串基础知识
- Python 的字符串类型
- 文本和二进制文件
- Python 3.0 中的字符串应用
- 转换
- 编码 Unicode 字符串
- 编码非ASCII文本
- 编码和解码非ASCII文本
- 其他 Unicode 编码技术
- 转换编码
- 在 Python 2.6 中编码 Unicode 字符串
- 源文件字符集编码声明
- 使用 Python 3.0 Bytes 对象
- 序列操作
- 创建 bytes 对象的其他方式
- 混合字符串类型
- 使用 Python 3.0(和 Python 2.6)bytearray 对象
- 使用文本文件和二进制文件
- Python 3.0 中的文本和二进制模式
- 类型和内容错误匹配
- 使用 Unicode 文件
- 在 Python 3.0 中处理 BOM
- Python 2.6 中的 Unicode 文件
- Python 3.0 中其他字符串工具的变化
- Struct二进制数据模块
- pickle对象序列化模块
- XML解析工具
- 本章小结
- 本章习题
- 习题解答
- 第37章 管理属性
- 第38章 装饰器
- 第39章 元类
- 附录A 安装和配置
- 附录B 各部分练习题的解答
- 作者介绍
- 封面介绍
世界上最简单的 Python 类
因为没有写任何方法,所以我们需要无操作的pass语句(第13章讨论过)。以交互模式执行此语句,建立这个类后,就可以完全在最初的class语句外,通过赋值变量名给这个类增加属性:
通过赋值语句创建这些属性后,就可以用一般的语法将它们取出。这样用时,类差不多就像C的struct或者Pascal的record:这种对象就是有字段附加在它的上边(我们也可以用字典的键做类似的事情,但是需要额外的字符)。
注意:其实该类还没有实例,也能这样用。类本身也是对象,也是没有实例。事实上,类只是独立完备的命名空间,只要有类的引用值,就可以在任何时刻设定或修改其属性。不过,当建立两个实例时,看看会发生什么事情:
这些实例最初完全是空的命名空间对象。不过,因为它们知道创建它们的类,所以会因继承并获取附加在类上的属性:
其实,这些实例本身没有属性。它们只是从类对象那里取出name属性。不过,如果把一个属性赋值给一个实例,就会在该对象内创建(或修改)该属性,而不会因属性的引用而启动继承搜索,因为属性赋值运算只会影响属性赋值所在的对象。在这里,x得到自己的name,但y依然继承附加在它的类上的name:
事实上,当进行下一章的深入探索时,命名空间对象的属性通常都是以字典的形式实现的,而类继承树(一般而言)只是连接至其他字典的字典而已。如果知道在哪里去搜索,的确会看到这一点。
例如,__dict__属性是针对大多数基于类的对象的命名空间字典(一些类也可能在__slots__中定义了属性,这是一个高级而少用的功能,我们将在第30章和第31章学习)。如下的代码在Python 3.0中运行;名称和__X__内部名称集合所出现的顺序可能随着版本的不同而有所不同,但是,我们赋值的名称全部给出:
在这里,类的字典显示出我们进行赋值了的name和age属性,x有自己的name,而y依然是空的。不过,每个实例都连接至其类以便于继承,如果你想查看的话,这个连接叫做__class__:
类也有一个__bases__属性,它是其超类的元组:
这两个属性是Python在内存中类树常量的表示方式。
揭开其奥秘的重点就是,Python的类模型相当动态。类和实例只是命名空间对象,属性是通过赋值语句动态建立。恰巧这些赋值语句往往在class语句内。只要能引用树中任何一个对象的任意地方,都可以发生。
即使是方法(通常是在类中通过def创建)也可以完全独立地在任意类对象的外部创建。例如,下列在任意类之外定义了一个简单函数,并带有一个参数。
这里与类完全没有什么关系——这是一个简单函数,在此时就能予以调用,只要我们传进一个带有name属性的对象(变量名self并没有使这变得特别)。
不过,如果我们把这个简单函数的赋值成类的属性,就会变成方法,可以由任何实例调用(并且通过类名称本身,只要我们手动传入一个实例)[1]。
在通常情况下,类是由class语句填充的,而实例的属性则是通过在方法函数内对self属性进行赋值运算而创建的。不过,重点在于并不是必须如此。Python中的OOP其实就是在已连接命名空间对象内寻找属性而已。
类与字典的关系
尽管前面小节中简单类是用来说明类模型的基础知识,它们所使用的技术也可以用于实际的工作。例如,第8章介绍了如何使用字典来记录我们程序中实体的属性。它证实了类也可以充当这一角色,它们打包像字典这样的信息,但是,也可以以方法的形式绑定处理逻辑。为了便于参考,这里给出了我们在本书前面所使用过的基于字典的记录的示例:
这段代码模拟了像其他语言中的记录这样的工具。正如我们所看到的,这里也有多种方式来用类做同样的事情。可能最简单的就是这种,利用键来记录属性:
这段代码的语法比其字典等价形式要少很多。它使用了一个空的class语句来产生一个空的命名空间对象。一旦我们产生了空类,我们随着时间用赋值类属性来填充它,这和以前一样。
这是有效的,但是,对于我们将需要的每一条不同的记录,都需要一条新的class语句。更通俗地说,我们可以产生一个空类的实例来表示每条不同的记录:
这里,我们从相同的类产生了两条记录。实例开始的时候为空,就像类一样。然后,我们通过对属性赋值来填充记录。然而这一次,有两个分开的对象,由此有两个不同的name属性。实际上,同一个类的实例甚至不一定必须具有相同的一组属性名称;在这个示例中,其中一个就有唯一的age名称。实例实际上是不同的名称空间,因此,每个实例都有一个不同的属性字典。尽管它们通常由类方法一致地填充,但它们比我们所预期的更加灵活。
最后,我们可能编写一个更完整的类来实现记录及其处理:
这一方案也产生多个实例,但是,这次类不是空的:我们已经添加了逻辑(方法)在构建的时候来初始化实例并把属性收集到一个元组中。这里,构造函数在实例上强制了一些一致性,通过总是设置name和job属性。此外,类的方法和实例属性创建了一个包,它组合了数据和逻辑。
我们可以添加计算薪酬、罗列名称等逻辑。最后,我们可能把该类连接到一个更大的层级中,以通过类的自动属性搜索来继承已有的一组方法,或者甚至可能把类的实例存储到一个文件中,该文件带有Python对象pickle化以使其持久。实际上,在下一章中,我们将用一个更加实际的运行实例来展示类基础知识的引用,从而实现类和记录的类比。
最后,尽管类型像字典一样是灵活的,但类允许我们以内置类型和简单函数不能直接支持的方式为对象添加行为。尽管我们也可以把函数存储到字典中,但再也没有比类更加自然的地方,可以使用它们来处理隐含的实例了。
[1]实际上,这正是self参数必须在Python方法中明确列出的原因之一:因为方法可以独立于类之外,创建为一个简单函数。因此,必须让隐含的实例参数明确化才行。否则,Python无法猜测简单函数是否最终会变成类的方法。不过,让self参数明确化的主要原因是为了让变量名的意义更为明确:没有通过self而引用的变量名是简单变量,而通过self引用的变量名则显然是实例的属性。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论