类Class

Learn from python2.7 official documentation

参考:python中使用多继承问题和super()内置函数的使用

Python的类提供了面向对象编程(Object Oriented Programming)的所有标准特性,同时也包含了Python语言的动态特性:在运行时创建,可以在创建后修改。

9.1 名称Name和对象Object

对象具有个性(individuality),可以将多个名称(在多个作用域中)绑定到同一对象,这在其他语言中称为别名(aliasing)。在处理不可变的基本类型(immutable basic types),如numberstringtuple时,可以安全地忽略这一点;但在别名涉及可变对象(mutable object),如listdictionary时,可能会产生不可预料的通常有利于程序的影响,因为别名在某些方面表现得像指针(pointers)。

9.2 作用域Scope和命名空间Namespace

命名空间是一个从名字到对象的映射,大部分命名空间都由Python字典实现。

几个命名空间的例子:存放内置函数的集合、模块中的全局名称、函数调用的局部名称。

命名空间是在不同的时刻创建的,并且具有不同的生存期。

注意:不同命名空间的名称之间没有关系。


跟在点号"."之后的名称称为属性(attribute),属性可以是只读的(read-only)或者可写的(writable)。可写的属性可以进行赋值操作modname.the_answer = 42(修改modname对象的the_answer属性值)及删除操作del modname.the_answer(从modname对象中移除the_answer属性)。


作用域是一个命名空间可以直接访问的Python程序的文本区域。直接访问意味着对名称的不合理引用(unqualified reference)将试图在命名空间内查找该名称。

虽然作用域是静态地被确定的,但是被动态地使用的。在执行的过程中,至少有三种嵌套的作用域可以直接访问:

  • 最先搜索的(be searched first),最内层的(innermost)作用域,包含局部名称(local name);
  • 从最相近的(nearest)封闭作用域(enclosing scope)开始搜索的,任何封闭函数(enclosing function)的作用域,包含非局部名称(non-local name)和非非全局名称(non-global name)。
  • 倒数第二的(next-to-last)作用域,包含当前模块的全局名称;
  • 最后搜索的(searched last),最外层的(outermost)作用域,是包含内置名称的命名空间。

如果一个名称被声明为全局名称(global name),则所有的引用和赋值,将直接转到包含模块全局名称的中间作用域(middle scope);否则,在innermost scope外的所有变量(variable)都是只读的(read-only),尝试写入这样的变量,只会在innermost scope中创建一个新的局部变量,而具有相同名称的外层变量保持不变。

通常,局部作用域引用当前函数内的局部名称,函数外的局部作用域引用和全局作用域一样的命名空间,即模块的命名空间。类的定义将在局部作用域中加入新的命名空间。


Python中,如果没有有效的global语句,对名称的分配总是进入最内层的作用域。分配名称不会复制数据,只会将名称绑定到对象;同样的,删除语句del x只是解除局部作用域引用的命名空间,与x之间的绑定。

事实上,采用新名称的所有操作都使用局部作用域,特别的,import语句和函数定义将模块和函数名称绑定到局部作用域。

global语句可用于显示全局作用域中的特殊变量。

9.3 初探类Class

类引入了一些新语法(syntax),三种新对象类型(object type)和一些新语义(semantic)。

9.3.1 类定义语法Class Definition Syntax

简单的Python类定义:

1
2
3
4
5
6
class ClassName:
<statement-1>
.
.
.
<statement-N>

9.3.2 类对象Class Object

当程序从结尾处正常离开类定义时,将会创建一个类对象,它作为类定义中创建的命名空间的封装器(wrapper),并绑定到类定义所给出的类名称上,如上文中的ClassName

类对象支持两种操作:属性引用(attribute reference)和实例化(instantiation)。

9.3.2.1 属性引用

类属性引用使用Python中属性引用的标准语法obj.name。有效的属性名称(attribute name)包括类对象被创建时存在于类命名空间中的所有名称,包括数据属性(data attribute)和方法(method)。如果类定义如下:

1
2
3
4
5
6
class MyClass:
"""A simple example class"""
i = 12345

def f(self):
return 'hello world'

那么有效的属性引用包括:MyClass.iMyClass.f,其分别返回一个整数和一个函数对象。

类属性也可以被赋值(assignment),因此可以通过赋值操作来更改MyClass.i的值。

__doc()__也是一个有效的属性,其返回所属类的文档字符串,如上文中的"A simple example class"

9.3.2.2 实例化

类的实例化使用Python中函数表示法,可把类对象视为,返回该类的一个新实例的不带参数的函数:

1
x = MyClass()

实例化操作会创建一个空对象,许多类喜欢创建带有特定初始状态的自定义实例,为此,类定义中可以包含一个名为__init()__的特殊方法,当类定义了__init()__方法时,实例化操作会自动为新创建的类实例调用__init()__方法。

除了第一个参数self是必须的;__init()__方法还可以有额外的参数,这种情况下,实例化时提供的参数将被传递给__init()__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass:
"""A simple example class"""
i = 12345

def f(self):
return 'hello world'

def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart

x = MyClass(3.0, -4.5)
print x.r, x.i
>>> 3.0 -4.5

9.3.3 实例对象

实例对象可以接收的唯一操作为属性引用,有效的属性名称包括数据属性和方法。


数据属性不需要声明,和局部变量一样,其在第一次被赋值时产生:

1
2
3
4
5
6
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print x.counter
del x.counter
>>> 16

x为上文中MyClass的实例,代码段打印数值16而不会保留任何追踪信息(新产生的数据属性counter在结尾被del语句清除)。


实例对象引用的方法是属于对象的函数,有效的方法名称依赖于实例对象所属的类,类定义中所有的函数对象属性,都是其实例的相应方法。

x.f即为有效的方法引用,因为MyClass.f是一个函数,但x.fMyClass.f并不相同,x.f是方法对象(method object),MyClass.f是函数对象(function object)。

9.3.4 方法对象Method Object

通常,方法在绑定后立即被调用,在MyClass的实例中,这将返回字符串'hello world'

1
x.f()

立即调用一个方法并不是必须的,x.f是一个方法对象,其可以被保存起来,以后再调用:

1
2
xf = x.f
print xf()

方法的特殊之处在于,调用时,对象作为函数的第一个参数self传递给方法,x.f()MyClass.f(x)完全等价。

当一个实例的非数据属性被引用时,将搜索实例所属的类,如果名称表示一个属于函数对象的有效类属性,Python将合并实例对象和函数对象到一个抽象对象中,这个抽象对象就是方法对象。调用方法对象时,如果附带参数列表,将会基于实例对象和参数列表来构建一个新的参数列表,并使用新的参数列表来调用相应的函数对象。

9.3.5 类和实例变量Instance Variable

一般的,实例变量用于每个实例的唯一数据,类变量用于类的所有实例共享的属性。

1
2
3
4
5
6
class Dog:

kind = 'canine' # 类变量

def __init__(self, name):
self.name = name # 实例变量

注意:将可变对象,如列表、字典,作为类变量时,所有类的实例将共享一个单独的变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Dog:

tricks = [] # 错误使用类变量

def __init__(self, name):
self.name = name

def add_trick(self, trick):
self.tricks.append(trick)

d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
d.tricks # 所有Dog的实例共享同一个tricks

>>> ['roll over', 'play dead']

正确的类设计应该使用实例变量:

1
2
3
4
5
6
7
8
class Dog:

def __init__(self, name):
self.name = name
self.tricks = [] # 为每个Dog的实例创建新的列表变量

def add_trick(self, trick):
self.tricks.append(trick)

9.4 补充说明

数据属性会覆盖掉具有相同名称的方法属性。


数据属性可以被方法以及一个对象的普通用户(“客户端”)所引用。换句话说,类不能用于实现纯抽象数据类型。 实际上,在 Python 中没有任何东西能强制隐藏数据 — 它是完全基于约定的。


在方法内部引用数据属性或其他方法并没有简便方式。

约定的,方法的第一个参数常常被命名为 self,这一名称在 Python 中没有特殊含义。


任何一个作为类属性的函数都为该类的实例定义了一个相应方法,函数定义可以在类定义之外,将函数对象赋值给一个局部变量是可行的:

1
2
3
4
5
6
7
8
9
10
11
# 在类定义外定义方法函数
def f1(self, x, y):
return min(x, x+y)

class C:
f = f1

def g(self):
return 'hello world'

h = g

fgh都是C类引用函数对象的属性,都是C实例的方法。


方法可以通过使用self参数调用其他方法:

1
2
3
4
5
6
7
8
9
10
class Bag:
def __init__(self):
self.data = []

def add(self, x):
self.data.append(x)

def addtwice(self, x):
self.add(x)
self.add(x)

可以通过object.__class__来获取对象所属的类。

9.5 继承Inheritance

派生类定义的基本语法:

1
2
3
4
5
6
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>

名称BaseClassName必须定义于包含派生类定义的作用域中,当基类定义在引用的模块中时,可以通过表达式表示基类位置:

1
class DerivedClassName(modname.BaseClassName):

派生类定义的执行过程中,当构造类对象时,基类会被记住,此信息将被用来解析属性引用:如果请求的属性在类中找不到,搜索将转往基类中进行查找;如果基类本身也派生自其他某个类,则此规则将被递归地应用。


派生类可能会重载其基类的方法。

对 C++ 程序员的提示:Python 中所有的方法实际上都是 virtual 方法。(暂时无法理解?)

在派生类中的重载方法,实际上可能想要扩展而非替换同名的基类方法。

使用BaseClassName.methodname(self, arguments)可以简单地直接调用基类方法,此方式仅当基类名称在全局作用域中存在时可用。


Python中关于继承机制的内置函数:

  • isinstance(obj, int):用于检查一个obj实例的类型,仅当obj.__class__int或某个派生自int的类时为True
  • issubclass(class, classinfo):用于检查类的继承,当classclassinfo的子类时为True;当class不是classinfo的子类,它们只有basestring()一个共同的祖先时为False

9.5.1 多重继承Multiple Inheritance

Python多重继承的派生类定义的基本语法:

1
2
3
4
5
6
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>

MRO即方法解析顺序(method resolution order),用于判断子类调用的属性来自于哪个父类。在Python2.3之前,MRO是基于深度优先算法的,自Python2.3开始使用C3算法,定义类时需要继承object,这样的类称为新式类,否则为旧式类。

9.6 私有变量Private Variables和类局部引用Class-local References

Python中不存在只能从对象内部访问的私有实例变量,约定的,以下划线开头的名称,如_span被视为API非公开的部分,无论其为函数、方法还是数据成员。

9.6.1 名称改写name mangling

为了避免,类似私有成员的名称与子类所定义的名称相冲突,这类情况的出现,Python提供名称改写(name mangling)机制,由至少两个前缀下划线,至多一个后缀下划线构成的标识符文本,如__spam,将被替换为_classname__spam,其中classname为去除了前缀下划线的当前类名称,这种改写不考虑标识符的位置,只要出现在类定义内部就会进行。

名称改写有助于让子类重载方法而不破坏类内方法调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)

def update(self, iterable):
for item in iterable:
self.items_list.append(item)

__update = update # 原始update()方法的私有拷贝

class MappingSubclass(Mapping):

def update(self, keys, values):
# 提供update()新的重载
# 而不会改变基类Mapping的__init__()
for item in zip(keys, values):
self.items_list.append(item)

上面的示例即使在 MappingSubclass 引入了一个__update标识符的情况下也不会出错,因为它会在Mapping类中被替换为_Mapping__update而在MappingSubclass类中被替换为_MappingSubclass__update

9.7 杂项说明

有时会需要使用类似于C"struct"这样的数据类型,将一些命名数据项捆绑在一起。这种情况适合定义一个空类:

1
2
3
4
5
6
7
8
9
class Employee:
pass

john = Employee() # 创建一个空的Employee员工记录

# 填充记录的字段
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

实例方法对象也具有属性。

文档原文:Instance method objects have attributes, too: m.im_self is the instance object with the method m(), and m.im_func is the function object corresponding to the method.

9.8 异常Exception是类

如果except子句中的类是异常的基类,那么该子句与异常兼容,此兼容不可逆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class B:
pass
class C(B):
pass
class D(C):
pass

for c in [B, C, D]:
try:
raise c()
except D:
print "D"
except C:
print "C"
except B:
print "B"
>>> B C D

如果将except子句顺序颠倒,将打印B B B

9.9 迭代器Iterator

Python中,大多数的容器对象都可以使用for语句进行迭代:

1
2
3
4
5
6
7
8
9
10
for element in [1, 2, 3]:
print element
for element in (1, 2, 3):
print element
for key in {'one':1, 'two':2}:
print key
for char in "123":
print char
for line in open("myfile.txt"):
print line

实际上,for语句在开始时,调用了容器对象的iter()函数,这个函数返回一个迭代器(iterator)对象。迭代器对象定义了next()方法,该方法一次去访问容易中的一个元素,并返回元素值,当没有更多的元素时,next()方法抛出StopIteration异常,来提示for语句终止循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
it.next()
StopIteration

将迭代器添加到类中,只需要在类中定义__iter()__方法,该方法返回带有next()方法的对象;如果同时在类中定义了next()方法,则__iter()__可以返回self

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Reverse:
"""实现用于逆向循环序列data的迭代器"""
def __init__(self, data):
self.data = data
self.index = len(data)

def __iter__(self):
return self

def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]

使用上文实现的迭代器:

1
2
3
4
5
6
7
8
9
10
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... print char
...
m
a
p
s

9.10 生成器Generator

生成器是用于创建迭代器的工具,它可以自动为创建的迭代器添加__iter()__next()方法。

生成器写法类似标准函数,返回数据时使用yield语句:

1
2
3
4
5
6
7
8
9
10
11
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]

>>> for char in reverse('golf'):
... print char
...
f
l
o
g

生成器的局部变量和执行状态会在每次调用之间自动保存,每次对生成器调用next()时,它会从上次离开位置恢复执行。

除了会自动创建方法和保存程序状态,当生成器终结时,它们还会自动引发StopIteration

9.11 生成器表达式

某些简单的生成器可以写成简洁的表达式代码,所用语法类似列表推导式,但外层为圆括号而非方括号。

这种表达式被设计用于,生成器将立即被外层函数所使用的情况。

生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product(点积)
260

>>> from math import pi, sin
>>> sine_table = dict((x, sin(x*pi/180)) for x in range(0, 91))

>>> unique_words = set(word for line in page for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1,-1,-1))
['f', 'l', 'o', 'g']