7.1、相关术语
7.1.1、Python Scopes and Name Spaces (作用域和命名空间)
命名空间是从命名到对象的映射。当前命名空间主要是通过Python字典实现的,通常不用关心具体实现方式(除非出于性能考虑)。
如:内置命名空间、模块中的全局命名、函数调用中的局部命名,某种意义上讲对象的属性集也是一个命名空间。不同的命名空间中的命名没有任何联系,例如两个不同模块可能都会定义一个名为"maximize"函数而不会发生混淆,因为用户必须以模块名为前缀来引用它们。
Python中任何一个"."之后的命名为属性。例如,表达式z.real中的real是对象z的一个属性,从模块中引用命名是引用属性:表达式modname.funcname中,modname是一个模块对象,funcname是它的属性。因此,模块的属性和模块中的全局命名有直接的映射关系:它们共享同一个命名空间。
有一个例外,模块对象有一个隐秘的只读对象,名为__dict__,它返回用于实现模块命名空间的字典,命名__dict__是一个属性而非全局命名。显然,使用它违反了命名空间实现的抽象原则,应该被严格限制于调试中。
属性可以是只读或可写的,后一种情况下,可以对属性赋值,如:'modname.a=20',可写的属性可以用del语句删除,如:del modname.a'会从modname对象中删除a属性。
不同的命名空间在不同的时刻创建,有不同的生存期。包含内置命名空间在python解释器启动时创建,会一直保留,不被删除,模块的全局命名空间在模块定义被读入时创建,通常模块命名空间也会保存到解释器退出,由解释器在最高层调用执行的语句,不管它是从脚本文件中读入还是来自交互式输入,都是__main__模块的一部分,所有它们也拥有自己的命名空间。(内置命名也同样被包含在一个模块中,它被称作__builtin__)
当函数被调用时创建一个局部命名空间,函数反正返回过抛出一个未在函数内处理的异常时删除,当然第一个递归调用拥有自己的命名空间。
作用域是Python程序中一个命名空间可以直接访问的正文区域,"直接访问"在这里有意思是查找命名时无需引用命名前缀。
尽管作用域是静态定义,在使用时他们都是动态的,每次执行时,至少有三个命名空间可以直接访问的作用域嵌套在一起:包含局部命名的作用域在最里面,首先被搜索;其次搜索的是中层的作用域,这里包含了同层的函数;最后被搜索的是外面的作用域,它包含内置命名。
如果一个命名声明为全局的,那么所有的赋值和引用都直接针对包含模块全局命名的中级作用域,另外,从外部访问到的所有内层作用域的变量都是只读的。(试图写这样的变量,只会在内部作用域创建一个新的局部变量,外部标示命名的那个变量不会改变)。
从字面意义上讲,局部作用域引用当前函数的命名,在函数之外,局部作用域与全局作用域引用同一命名空间:模块命名空间,类定义也是局部作用域中的另一个命名空间。
重要的是作用域决定于源程序的文本:一个定义于某模块中的函数的全局作用域是该模块的命名空间,而不是该函数的别名被定义或调用的位置,命名的实际搜索过程是动态的,在运行时确定(局部变量已经是静态确定了)
Python的一个特别之处在于其赋值操作总是在最里层的作用域,赋值不会复制数据。只是将命名绑定到对象,删除也是如此:"del x" 只是从局部作用域的命名空间中删除命名x,事实上,所有引入新命名的操作都作用于局部作用域,特别是import语句和函数将模块名或函数绑定于局部作用域(可以使用global语句将变量引入全局作用域)
注意:1、赋值的地点决定标识符所处的命名空间;2、函数定义产生新的命名空间;3、LGB规则:命名空间的搜索顺序时,最先从当前函数命名空间中搜索,其次是模块(.py文件)命名空间,最后是内置命名空间(__builtin__)。
实例:
#!/usr/bin/python
a=1
def func():
'''Test '''
print locals()
print globals()
func()
输出:
{}
{'a': 1, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'fun.py', '__package__': None, 'func': <function func at 0x7f60b0cbc488>, '__name__': '__main__', '__doc__': None}
locals()和globals()分别返回local name space和global name space。通常用字典来表示。字典是一个(key,name)的集合,key是name,value是name所指的对象。
7.2、类
面向对象的目的是代码重用,减少重复性的开发。重用机制包括封装、继承、多态。
类支持两种操作:属性引用和实例化。
实例:
#!/usr/bin/python
class test:
def __init__(self,x,y):
self.r=x
self.z=y
def max(self):
if self.r > self.z:
print 'max:',self.r
else:
print 'max:',self.z
x=test(2,4)
print x.r,x.z
x.max()
输出:
2 4
max: 4
以一创建了一个类对象test,__init__()、max()属于test类对象的方法,这此方法与函数的区别只是一个额外的self变量。__init__()方法在类的一个对象被建立时,马上运行。这个方法可以用来对你的对象做一些你希望的 初始化 。
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称,但是在调用这个方法的时候你不为这个参数赋值,Python会提供这个值。这个特别的变量指对象本身,按照惯例它的名称是self
。如有一个类称为MyClass
和这个类的一个实例MyObject
。当调用这个对象的方法MyObject.method(arg1, arg2)
的时候,这会由Python自动转为MyClass.method(MyObject, arg1, arg2)
——这就是self
的原理了。这也意味着如果有一个不需要参数的方法,你还是得给这个方法定义一个self
参数。
x=tset(2,4)创建了一个新的类实例将该对象赋给局部变量x(类的实例化)。
实例化操作(调用一个类对象)来创建一个空的对象,如果类定义了__init__()方法的话,类的实例化操作会自动为新创建的类实例调用__init__()方法初始化。
如上,x就是一个实例对象,实例对象的唯一可用的操作就是属性引用。
x.max()就是一个方法对象,方法对象也可以被存储起来以后调用
如:
xmax=x.max
xmax()
或:
xmax=x.max()
xmax
7.3、类的继承
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过 继承 机制。继承完全可以理解成类之间的 类型和子类型 关系。
假设你想要写一个程序来记录学校之中的教师和学生情况。他们有一些共同属性,比如姓名、年龄和地址。他们也有专有的属性,比如教师的薪水、课程和假期,学生的成绩和学费。
你可以为教师和学生建立两个独立的类来处理它们,但是这样做的话,如果要增加一个新的共有属性,就意味着要在这两个独立的类中都增加这个属性。这很快就会显得不实用。
一个比较好的方法是创建一个共同的类称为SchoolMember
然后让教师和学生的类 继承 这个共同的类。即它们都是这个类型(类)的子类型,然后我们再为这些子类型添加专有的属性。
使用这种方法有很多优点。如果我们增加/改变了SchoolMember
中的任何功能,它会自动地反映到子类型之中。例如,你要为教师和学生都增加一个新的身份证域,那么你只需简单地把它加到SchoolMember
类中。然而,在一个子类型之中做的改动不会影响到别的子类型。另外一个优点是你可以把教师和学生对象都作为SchoolMember
对象来使用,这在某些场合特别有用,比如统计学校成员的人数。一个子类型在任何需要父类型的场合可以被替换成父类型,即对象可以被视作是父类的实例,这种现象被称为多态现象。
另外,我们会发现在 重用 父类的代码的时候,我们无需在不同的类中重复它。而如果我们使用独立的类的话,我们就不得不这么做了。
在上述的场合中,SchoolMember
类被称为 基本类 或 超类 。而Teacher
和Student
类被称为 导出类 或 子类 。
现在,我们将学习一个例子程序。
实例:
#!/usr/bin/python
# Filename: inherit.py
class SchoolMember:
'''Represents any school member.'''
def __init__(self, name, age):
self.name = name
self.age = age
print '(Initialized SchoolMember: %s)' % self.name
def tell(self):
'''Tell my details.'''
print 'Name:"%s" Age:"%s"' % (self.name, self.age),
class Teacher(SchoolMember):
'''Represents a teacher.'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print '(Initialized Teacher: %s)' % self.name
def tell(self):
SchoolMember.tell(self)
print 'Salary: "%d"' % self.salary
class Student(SchoolMember):
'''Represents a student.'''
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print '(Initialized Student: %s)' % self.name
def tell(self):
SchoolMember.tell(self)
print 'Marks: "%d"' % self.marks
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 22, 75)
print # prints a blank line
members = [t, s]
for member in members:
member.tell() # works for both Teachers and Students
输出:
Initialized SchoolMember: Mrs. Shrividya)
(Initialized Teacher: Mrs. Shrividya)
(Initialized SchoolMember: Swaroop)
(Initialized Student: Swaroop)
Name:"Mrs. Shrividya" Age:"40" Salary: "30000"
Name:"Swaroop" Age:"22" Marks: "75"
为了使用继承,我们把基本类的名称作为一个元组跟在定义类时的类名称之后。然后,我们注意到基本类的__init__
方法专门使用self
变量调用,这样我们就可以初始化对象的基本类部分。这一点十分重要——Python不会自动调用基本类的constructor,你得亲自专门调用它。
我们还观察到我们在方法调用之前加上类名称前缀,然后把self
变量及其他参数传递给它。
注意,在我们使用SchoolMember
类的tell
方法的时候,我们把Teacher
和Student
的实例仅仅作为SchoolMember
的实例。
另外,在这个例子中,我们调用了子类型的tell
方法,而不是SchoolMember
类的tell
方法。可以这样来理解,Python总是首先查找对应类型的方法,在这个例子中就是如此。如果它不能在导出类中找到对应的方法,它才开始到基本类中逐个查找。基本类是在类定义的时候,在元组之中指明的。
一个术语的注释——如果在继承元组中列了一个以上的类,那么它就被称作 多重继承 。
多继承的类定义如下:
class DerivedClassName(Base1,Base2,Base3):
<statement-1>
...
注:解析类属性的顺序是深度优先,从左到右,因此,如果在DerivedClassName(子类)中没有找到某个属性,就会先搜索Base1,然后递归的搜索其基类,以此类推。