返回介绍

世界上最简单的 Python 类

发布于 2024-01-29 22:24:15 字数 4294 浏览 0 评论 0 收藏 0

我们在本章详细研究过class语句语法,不过类产生的基本的继承模型其实非常简单:所涉及的就是在连接的对象树中搜索属性。实际上,我们建立的类中可以什么东西都没有。下列语句建立一个类,其内完全没有附加的属性(空的命名空间对象)。

因为没有写任何方法,所以我们需要无操作的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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文