>>> from env_helper import info; info()
页面更新时间: 2023-04-18 20:54:40
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-7-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

9.2.

类,用来描述具有相同的属性和方法的对象的集合。

例如,如果你在窗外看到一只鸟,这只鸟就是“鸟类”的一个实例。 鸟类是一个非常通用(抽 象)的类,它有多个子类:你看到的那只鸟可能属于子类“云雀”。 你可将“鸟类”视为由所有 鸟组成的集合,而“云雀”是其一个子集。 一个类的对象为另一个类的对象的子集时,前者就是后者的子类。 因此“云雀”为“鸟类”的子类,而“鸟类”为“云雀”的超类。

通过这样的陈述,子类和超类就很容易理解。 但在面向对象编程中,子类关系与自然语言不能完全类比,因为类是由其支持的方法定义的。 类的所有实例都有该类的所有方法,因此子类的所有实例都有超类的所有方法。 有鉴于此,要定义子类,只需定义多出来的方法。

9.2.1. 创建自定义类

下面是一个简单的创建自定义类示例:

>>> class Person:
>>>     def set_name(self, name):
>>>         self.name = name
>>>
>>>     def get_name(self):
>>>         return self.name
>>>
>>>     def greet(self):
>>>         print("Hello, world! I'm {}.".format(self.name))

这个示例包含三个方法定义,它们类似于函数定义,但位于 class 语句内。 Person 是类的名称。 class 语句创建独立的命名空间,用于在其中定义函数。

注意在类的定义中的 self 关键词它指向对象本身,代表当前对象的地址。

9.2.2. 属性、函数和方法

实际上,方法和函数的区别表现在参数 self 上。关联的方法将其第一个参数关联到它所属的实例,因此无需提供这个参数。

>>> class Class:
>>>     def method(self):
>>>         print('I have a self!')
>>> def function():
>>>     print("I don't...")
>>>
>>> instance = Class()
>>> instance.method()
I have a self!

也可以将属性关联到一个普通函数,但这样就没有特殊的 self 参数了。

>>> instance.method = function
>>> instance.method()
I don't...

请注意,有没有参数 self 并不取决于是否以刚才使用的方式(如 instance.method )调用方法。

实际上,完全可以让另一个变量指向同一个方法。

>>> class Bird:
>>>     song = 'Squaawk!'
>>>     def sing(self):
>>>         print(self.song)
>>> bird = Bird()
>>> bird.sing()
Squaawk!
>>> birdsong = bird.sing

变量 birdsong 指向的是关联的方法 bird.sing 。

>>> birdsong()
Squaawk!

9.2.3. 关于对象内部的可见性

默认情况下,可从外部访问对象的属性。再来看一下前面讨论封装时使用的示例。

>>> class C:
>>>     name = 'Sir Lancelot'
>>>     def get_name(self):
>>>         print(self.name)
>>> c = C()
>>> c.name
'Sir Lancelot'
>>> c.get_name()
Sir Lancelot

这貌似没问题,但它似乎又这违反了封装原则。毕竟,如果能直接访问 ClosedObject 的属性 name ,就不需要创建方法 setNamegetName 了。

关键是别人可能不知道对象内部发生的情况。 例如,ClosedObject可能在对象修改其名称时向管理员发送电子邮件。 这种功能可能包含在方法 set_name 中。但如果直接设置 c.name ,结果将如何呢? 什么都不会发生——根本不会发送电子邮件。

为避免这类问题,可将属性定义为私有。 私有属性不能从对象外部访问,而只能通过存取器方法(如 get_nameset_name )来访问。

9.2.4. 在Python中定义私有属性

Python没有为私有属性提供直接的支持,而是要求程序员知道在什么情况下从外部修改属性是安全的。 毕竟,你必须在知道如何使用对象之后才能使用它。然而,通过玩点小花招,可获得类似于私有属性的效果。

要让方法或属性成为私有的,只需让其名称以两个下划线 __ 打头即可。

>>> class Secretive:
>>>     def __inaccessible(self):
>>>         print("Bet you can't see me ...")
>>>     def accessible(self):
>>>         self.__inaccessible()
>>>         print("The secret message i")

现在从外部不能访问 __inaccessible

>>> s = Secretive()
>>> s.__inaccessible()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In [19], line 1
----> 1 s.__inaccessible()


AttributeError: 'Secretive' object has no attribute '__inaccessible'

但在类的内部可以调用 __inaccessible

>>> s = Secretive()
>>> s.accessible()
Bet you can't see me ...
The secret message i

在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头加上一个下划线和类名。

>>> Secretive._Secretive__inaccessible
<function __main__.Secretive.__inaccessible(self)>

<unbound method Secretive. inaccessible>

只要知道这种幕后处理手法,就能从类外访问私有方法,然而不应这样做。

>>> s._Secretive__inaccessible()
Bet you can't see me ...

如果你不希望名称被修改,又想发出不要从外部修改属性或方法的信号,可用一个下划线打头。 这虽然只是一种约定,但也有些作用。 例如, from module import * 不会导入以一个下划线打头的名称。

9.2.5. 类的命名空间

下面两条语句大致等价:

>>> def foo(x):
>>>     return x * x
>>> foo = lambda x: x * x

它们都创建一个返回参数平方的函数,并将这个函数关联到变量 foo 。 可以在全局作用域内定义名称 foo ,也可以在函数或方法内定义。 定义类时情况亦如此:在 class 语句中定义的代码都是在一个特殊的命名空间内执行的,而类的所有成员都可访问这个命名空间。 类定义其实就是要执行的代码段。

请看下面的代码:

>>> class MemberCounter:
>>>     members = 0
>>>     def init(self):
>>>         MemberCounter.members += 1
>>>
>>> m1 = MemberCounter()
>>> m1.init()
>>> MemberCounter.members
1
>>> m2 = MemberCounter()

使用了 init 来初始化所有实例:

>>> m2.init()
>>> MemberCounter.members
2

每个实例都可访问这个类作用域内的变量,就像方法一样。

>>> m1.members, m2.members
(2, 2)

如果你在一个实例中给属性members赋值,结果将如何呢?

>>> m1.members = 'Two'
>>> m1.members
'Two'
>>> m2.members
2

新值被写入 m1 的一个属性中,这个属性遮住了类级变量。