>>> 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
,就不需要创建方法 setName
和
getName
了。
关键是别人可能不知道对象内部发生的情况。
例如,ClosedObject可能在对象修改其名称时向管理员发送电子邮件。
这种功能可能包含在方法 set_name
中。但如果直接设置 c.name
,结果将如何呢? 什么都不会发生——根本不会发送电子邮件。
为避免这类问题,可将属性定义为私有。
私有属性不能从对象外部访问,而只能通过存取器方法(如 get_name
和
set_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
的一个属性中,这个属性遮住了类级变量。