类
Python的类机制提供了面向对象编程的所有标准特性:
- 多重继承
- 子类重写基类的方法
- 方法可以通过相同的名称调用基类方法
- 对象可以包含任意数量与类型的数据
与模块相同,类同样具有Python的动态特性:运行时创建并且可以在创建后修改。
通常类成员都是公共成员,所有的成员函数都是虚函数。
在成员方法中引用对象成员没有简便的写法:成员方法声明时显式使用第一个参数代表对象,调用时这个参数被隐式提供。
类也是对象,可以引入与重命名。
内置类型可以作为基类被用于扩展。
大多数内置操作符可以被类实例重定义。
1 名称与对象
同一个对象可以绑定多个名称。
这一类似于别名的机制有时可以看作指针。
2 Python 范围与命名空间
命名空间是名称与对象的映射。
现在大多数命名空间作为Python字典实现。
一些命名空间示例:内置名称集合、模块的全局名称、方法声明中的本地名称。
不同命名空间中的名称没有关系。
严格地说,模块中名称的引用就是属性引用。
eg:modname.funcname中,modname是模块对象,funcname是模块对象的属性。
模块的属性与定义在模块中的全局名称共享相同的命名空间。
属性可以为只读,也可以为可写。
模块的属性是可写的。
可写属性可以被del语句删除。
不同时刻创建的命名空间具有不同的生命周期。
当Python解释器启动时,包含内置名称的命名空间将会被创建,并且不会被删除。
当模块定义被读取时,模块的全局命名空间将被创建。
通常情况下,模块的命名空间将会存活到解释器退出。
通过解释器的高级调用(无论是读取脚本文件或交互)执行的语句将会被认为是模块__main__的一部分,所以它们具有自己独有的全局命名空间。(内置名称实际上也存活于模块中,此模块被称为__builtin__)。
当一个函数被调用时,函数的本地命名空间将会被创建,并且当函数返回或提出一个没有被函数处理的异常时,此命名空间将会被删除。
递归的调用,每一层递归都有属于自己的本地命名空间。
范围是Python程序的文本区域,在范围中可以直接访问命名空间。
在执行期间,至少由三个内嵌范围可以被命名空间直接访问:
- 被首先搜索的最内层范围包含本地名称
- 从最内层封闭范围开始搜索的封闭函数的范围包括非局部,非全局的名称
- 倒数第二个范围包含当前模块的全局名称
- 被最后搜索的最外层范围是包含内置名称的命名空间
在最内存范围之外的命名空间中找到的变量是只读的,试图写入一个外部变量将会导致在内部范围创建一个新的本地变量。
通常情况下,本地范围引用当前函数的的本地名称。在函数的外部,本地范围引用与全局范围相同的命名空间,模块的命名空间。
类定义在本地范围中放置另一个命名空间。
范围由文本决定。
模块中定义的函数的全局范围是模块的命名空间。
实际上,对名称的搜索是在运行是动态完成的,然而,对名称的解析是静态的。
如果全局语句没有生效,对名称的赋值将会进入最内层范围。
赋值没有赋值数据,只是为对象绑定名称。
实际上,所有引入新名称的操作都使用本地范围。
特别是,import语句与函数定义将模块名或函数名绑定进本地范围。
3 类简介
3.1 类定义语法
# 定义类
class Ccc:
pass
类定义,与函数定义类似,必须在使用前被执行。
实际上,类定义中的语句通常为函数定义.
类中定义的函数通常有特定的形式,这由方法调用约定决定。
当进入一个类定义之后,将会创建一个新的命名空间,用作局部范围,因此,对本地变量的所有赋值操作都会进入这个新的命名空间。特别是,函数定义绑定新的函数的名称。
当离开一个类定义之后,一个类对象将会被创建。类对象将类定义创建的命名空间中的内容包装起来。原始本地范围将会被恢复,并且这个类对象将会与类定义首部中给定的类名称绑定。
3.2 类对象
类对象支持两种操作:属性引用与实例化。
# 类定义
class Ccc:
"a simple example"
i = 1321
def ff(self, arg):
print arg
def __init__(self, x, y):
self.x = x
self.y = y
# 引用属性
Ccc.i
# 引用方法对象
Ccc.ff
# 修改类属性
Ccc.i = 666
# 获取类的文档字符串
Ccc.__doc
# 实例化类
x = Ccc(123, 456)
# 访问实例对象属性
x.x
x.y
3.3 实例对象
实例对象唯一理解的操作是属性引用。
实例对象中存在两种有效属性,数据属性与方法。
数据属性不需要被声明,就像本地变量,它们会在第一次被赋值时存在。
方法是属于对象的函数。方法对象与类函数不同,方法对象不是一个函数对象。
# 类定义
class dd:
pass
# 实例化
x = dd()
# 添加属性
x.counter = 1
# 删除属性
del x.counter
3.4 方法对象
# 定义类
class dd:
def ff(self):
print "cccc"
# 实例化
x = dd()
# 调用方法
x.ff()
# 存储方法对象
gg = x.ff
gg()
当一个实例的非数据属性被引用时,实例的类将会被搜索。如果属性名称是一个函数对象的有效类属性,一个方法对象将会通过包装实例对象与方法对象被创建。当一个方法对象通过一个参数列表调用时,一个新的参数列表将会通过实例对象与参数列表创建出来,函数对象将会通过这个新的参数列表进行调用。
3.5 类变量与实例变量
通常来说,实例变量用于每个实例唯一的数据,类变量用于所有类实例共享的属性与方法。
# 类定义
class d:
kind = "dog"
def __init__(self, name):
self.name = name
self.ttt = []
tricks = []
def add_trick(self, trick):
self.tricks.append(trick)
def add_ttt(self, ttt):
self.ttt.append(ttt)
# 实例化
dd = d('Fido')
ee = d("Buddy")
# 访问属性
print dd.kind
print ee.kind
print dd.name
print ee.name
# 修改可变对象(一个实例修改,所有实例全部受到影响)
dd.add_trick('iii')
ee.add_trick('ppp')
# 修改可变对象(实力修改只会影响自身)
dd.add_ttt('iii')
ee.add_ttt('ppp')
4 注意事项
数据属性可以重写具有相同名称的方法属性,为了避免命名冲突,一般采用一些命名约定最小化冲突的可能。
数据属性可以被方法引用,换句话说,类不能被实现为纯抽象数据类型。实际上,Python不能强制实现数据隐藏,数据隐藏一般通过约定完成。(另一方面,C编写的Python可以完全隐藏实现细节并且控制对对象的访问,这可以用于基于C编写的Python扩展。)
客户端要小心使用数据属性,因为客户端可能会通过标记数据属性破坏方法维护的不变性。
在方法中引用数据属性与其他方法时没有缩写形式。
方法的第一个参数常常称为self。不过这只是一个约定,名称self对于Python来说并没有特别的含义。
类的函数对象定义了类对应实例的方法对象。将函数定义以文本形式包含在类定义中并不是必要的,将函数对象复制给类对象也可以。
def ff(self, x, y):
print 'ccc'
class C:
f = ff
def g(self):
print 'hhh'
h = g
方法可以通过self参数调用其他方法。
方法引用全局名称的方法与普通函数相同。关联方法的全局范围是包含方法定义的模块。
引入全局范围的函数与模块可以被方法使用,就像函数与类定义在其中一样。通常情况下,包含方法的类其本身就定义在全局范围内。
由于所有值都是对象,因此每一个值都有类型。它存储在object. __class__之中。