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类被称为 基本类超类 。而TeacherStudent类被称为 导出类子类

现在,我们将学习一个例子程序。

实例:

#!/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方法的时候,我们把TeacherStudent的实例仅仅作为SchoolMember的实例。

另外,在这个例子中,我们调用了子类型的tell方法,而不是SchoolMember类的tell方法。可以这样来理解,Python总是首先查找对应类型的方法,在这个例子中就是如此。如果它不能在导出类中找到对应的方法,它才开始到基本类中逐个查找。基本类是在类定义的时候,在元组之中指明的。

一个术语的注释——如果在继承元组中列了一个以上的类,那么它就被称作 多重继承

多继承的类定义如下:

class DerivedClassName(Base1,Base2,Base3):

  <statement-1>

   ...

注:解析类属性的顺序是深度优先,从左到右,因此,如果在DerivedClassName(子类)中没有找到某个属性,就会先搜索Base1,然后递归的搜索其基类,以此类推。

 

posted on 2011-08-17 16:30  xunya  阅读(458)  评论(0编辑  收藏  举报