在前面大套的历史背景和理论概念解说之后,我们来看看 Python 中对面向对象概念的具体实现,主要结合一些代码例子进行说明。
首先我们来看看类的定义和对应对象的创建:
class Dog:
kind = ''
def __init__(self, name):
self.name = name
def bark(self):
return 'woof-woof'
a = Dog('Fido')
b = Dog('Buddy')
上面这段代码逐行解释如下:
class
打头,代表类定义的开始,后面是类的名字,然后是一个冒号,表示下面缩进的代码段是 Dog
类的定义;在 Python 中类本身也是个对象,叫类对象(class object);if
else
for
while
def
都类似;kind
,这样定义的变量称为类变量(class variable),这种变量是整个类共有的,所有该类的对象实例共享;__init__()
的定义,因为这个函数定义在类里,所以通常我们称之为方法(method):self
,这是个特殊的变量,代表从这个类实例化出来的对象自己;注意这里是类的定义,还没有真正创建出对象,这个 self
相当于对未来产生对象的“提前引用”;类定义中的方法在被调用时解释器都会自动传递这个 self
进去;__init__()
用双下划线开头和结尾的方法,属于特殊方法(special method),是 Python 解释器特别定义和使用的;__init__()
是解释器在实例化一个对象之后自动调用的方法,用来对新创建的对象实例做初始化;self
之后带任意的输入参数,这些输入参数由方法自行使用;self.name
定义了一个实例变量(instance variable),前面说了 self
代表未来被实例化的对象自身,.
表示属于对象的,name
则是这个实例变量的名字,这个方法用传进来的参数 name
来初始化实例变量 self.name
,要注意这两个 name
是不一样的;bark()
的定义,这是我们自定义的方法,没有自定义的输入参数(例行的 self
参数不算),然后返回 dog 的叫声;a = Dog('Fido')
:Dog
当做一个函数来使用,Dog('Fido')
看上去就是个函数,它的意思是:创建一个 Dog
类的对象实例(instance object),并用以参数 'Fido'
调用类的 __init__()
方法;'Fido'
调用类的 __init__(self, name)
方法,第一个参数 self
解释器会自动放进去(就是刚刚创建好的对象实例),而 name
参数就是 'Fido'
,__init__()
方法运行完毕就会把这个对象的 self.name
实例变量设为 'Fido'
;a
变量。b
。创建出来的两个对象实例就可以使用了:
# 类变量可以通过 class object 来访问,并在所有实例变量之间共享
Dog.kind = 'canidae'
a.kind
'canidae'
b.kind
'canidae'
# 而实例变量是各个对象实例自己拥有,互不影响
a.name
'Fido'
b.name
'Buddy'
# 当然我们也可以使用我们定义的方法
a.bark()
'woof-woof'
如果前面的看上去有点复杂,可以先努力理解和记住下面的几个要点:
class MyClass:
的语法来定义类;MyClass.xxx
的语法访问;self.xxx
是实例变量(instance variable),是各个对象实例各自的属性(attribute),互不影响,对象实例创建后可以用 obj.xxx
的语法访问;__init__(self, param1, param2, ...)
,该方法会在对象实例创建后自动被调用,其参数中 self
是固定的,而后面如果有其他参数,需要在创建对象实例时传给类对象,类似这样:obj = MyClass(param1, param2, ...)
,这两个地方的参数表是对应上的;obj.method()
的语法调用;所有方法的第一个参数固定为 self
,其他参数的使用方法与前述 __init__
方法一样。有狗就有猫,我们现在可以依葫芦画瓢的定义一个猫类出来:
class Cat:
kind = 'felidae'
def __init__(self, name):
self.name = name
def mew(self):
return 'meow-meow'
c = Cat('Garfield')
Cat.kind
'felidae'
c.name
'Garfield'
c.mew()
'meow-meow'
和前面的狗类好像没有太大区别,因为从特征上基本一致:无论是猫还是狗,都有一个类变量 kind
(动物的分类),以及一个实例变量 name
(动物的名字),然后有个方法来发出叫声。这时候我们可以想到的是,是不是可以将这两个类的共性特征抽取出来,用一个公共的父类来实现呢?
首先我们定义一个比猫和狗都更加抽象的类,准备用作猫和狗的共通父类,这个类把上面说的共性特征都表达出来,想一下应该怎么写,然后看下面的代码:
class Animal:
kind = ''
def __init__(self, name):
self.name = name
def voice(self):
return '...'
我们可以看到,Animal
类具有类变量 kind
、实例变量 self.name
和一个方法 voice()
,但是没有给出具体的分类名和叫声是什么。
下面我们看看怎么继承这个父类来定义子类,在 Python 中要继承一个已存在的类,使用下面这样的语法:
class Dog(Animal):
kind = 'canidae'
def voice(self):
return 'woof-woof'
bark = voice
class
语句中类名后面可以紧跟一个括号,里面是要继承的父类,这样定义出来的子类就直接拥有了父类的一切,但可以修改,上面的定义就修改了类变量 kind
的值,给出了 voice()
方法的一个狗类实现(“汪汪汪”),并定义了一个自己独有的方法 bark
(作为 voice
方法的一个别名)。下面的猫类也类似:
class Cat(Animal):
kind = 'felidae'
def voice(self):
return 'meow-meow'
mew = voice
注意这两个子类都没写构造方法 __init__()
,因为父类的就好用,不需要改。现在我们可以使用这两个子类来实例化一些对象出来:
a = Animal('キュゥべえ')
c = Cat('Garfield')
d = Dog('Snoopy')
for animal in [a, c, d]:
print(animal.name, ':', animal.voice())
キュゥべえ : ... Garfield : meow-meow Snoopy : woof-woof
仔细看上面的代码,a
c
d
虽然是不同类的对象实例,但是因为它们都是 Animal
或其子类的对象实例,所以拥有 Animal
类所定义的标准接口(继承),从而可以在一个循环中一视同仁的处理,而且不同类的对象实例自动调用了各自类定义的 voice()
方法实现,产生各自正确的输出(多态)。
所以其实也不复杂吧?
和 C++、Java 等面向对象编程语言不一样,Python 并没有私有成员一说,从语法上讲,所有类定义中出现的类变量、对象变量、方法都是公开的。
但是 Python 同时给出了一个相对宽松的“弱约束”,就是作为一种约定俗成的代码风格,将类定义中所有下划线 _
开头的变量和方法作为“内部”变量和方法来看待,也就是说这些变量和方法属于类的内部实现的一部分,随时可能改变,外部最好不要依赖于这些变量和方法。
所以 Python 虽然没有提供强制受限的“私有成员”,但还是遵循了“接口与实现分离”的思维模式,我们在定义一个类的时候,如果有不希望外部直接使用的变量和方法,可以在其名字前加一个下划线 _
。
现在可以再读一遍前一章的概念,应该会有不同的感受。