类,用来描述具有相同的属性和方法的对象的集合。
例如,如果在窗外看到一只鸟,这只鸟就是“鸟类”的一个实例。 鸟类是一个非常通用(抽象)的类,它有多个子类:看到的那只鸟可能属于子类“云雀”。 可将“鸟类”视为由所有鸟组成的集合,而“云雀”是其一个子集。 一个类的对象为另一个类的对象的子集时,前者就是后者的子类。 因此“云雀”为“鸟类”的子类,而“鸟类”为“云雀”的超类。
通过这样的陈述,子类和超类就很容易理解。 但在面向对象编程中,子类关系与自然语言不能完全类比,因为类是由其支持的方法定义的。 类的所有实例都有该类的所有方法,因此子类的所有实例都有超类的所有方法。 有鉴于此,要定义子类,只需定义多出来的方法。
下面是一个简单的创建自定义类示例:
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
关键词它指向对象本身,代表当前对象的地址。
实际上,方法和函数的区别表现在参数 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!
默认情况下,可从外部访问对象的属性。再来看一下前面讨论封装时使用的示例。
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
)来访问。
要让方法或属性成为私有的,只需让其名称以两个下划线 __
打头即可。
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() #此代码会报错
但在类的内部可以调用 __inaccessible
:
s = Secretive()
s.accessible()
Bet you can't see me ... The secret message i
在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头加上一个下划线和类名。
Secretive._Secretive__inaccessible
<function __main__.Secretive.__inaccessible(self)>
只要知道这种幕后处理手法,就能从类外访问私有方法,不应这样做。
s._Secretive__inaccessible()
Bet you can't see me ...
如果不希望名称被修改,又想发出不要从外部修改属性或方法的信号,可用一个下划线打头。
这虽然只是一种约定,但也有些作用。
例如, from module import *
不会导入以一个下划线打头的名称。
下面两条语句大致等价:
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
的一个属性中,这个属性遮住了类级变量。