
python 黑帽编程 2.9 面向对象编程
我个人认为,计算机语言的发展,有两个方向,一个是从低到高的发展过程,在这个过程中,语言的思考和解决问题的方式是面向硬件的。硬件本质上处理的是信号,在此基础上,我们给硬件赋予了一定的“逻辑思维”能力,为了方便硬件帮我们做事,抽象出了指令的概念,进而出现了汇编语言,然后有了 Pascal 和 C 这样的标准的结构化语言。语言一路向上发展,都是根植于指令的,根植于指令就意味着流程和数据代表了一切,数据的变化成为我们表达和抽象这个世界的根本。不可否认,宇宙间的一切,都是在不停的变化中,然而命运有数,变化有法,越是着眼于变化,我们越难逃脱宿命。
在冯诺依曼的体系成为权威之前或者之后这些年中,计算机科学思考都衍生于对数学的思考。数学家认为数学语言能完美的描述这个世界,基于数学的编程语言一出来就是贵族,然而纯粹数学语言是没法用的,因为我们的机器还太低级,于是另一个编程语言的发展方向,是从数学语言向机器语言的自顶向下的发展。这其中纯粹的函数式语言是这方面的代表,比如 lisp ,微软的 F# 则是“混血”的代表。
从机器的角度理解世界,还是纯数学的方式理解世界,都不能代表人的视角和思维方式。在二者相向发展中,有了很多交汇。计算机当然离理解人的思维方式还差的很远,除非把人变成计算单元。科学家们在这个方向上努力良久,在编程语言领域 “ 面向对象 ” 的思想和方法被广泛接受。事物是不断变化的,人类在变化中寻找相对静止的时空来思考世界,来描述世界,文字、绘画都是语言,都需要在静止中呈现。生命尊重并表现自我,认同个体,于是世间有了物的概念。在静止中,如果还只是思考数据,那么就是混沌,观察个体才有意义,才有血肉。世间万物,物就是对象。
所谓面向对象,就是把你眼中能认为或抽象出的独立事务描述清楚,首先它是个整体,然后我们再肢解它,最后在把它重新放到变化中观察行为。抽象的越彻底,我们越能发现很多事务的共性,于是有了分类。在变化中,物与物必然会产出影响,于是有了关系。
视角发生了变化,描述事物和行为的方式必然有了变化,产生了新的表达方法,新的技巧,同时也有了新的问题和挑战,当然会产生新的解决问题的方法,这些就是面向对象的基本方法,设计模式,架构经验,等等。
扯了这么多闲篇,下面我们进入 Python 的面向对象世界,当然不会细致入微,简单概述为主。
2.9.1 类与对象在 python 中,我们使用 class 关键字来定义一类事物,类是一个抽象描述,并不是真正的存在,需要把它初始化才会产生一个真正的事物,我们称之为对象。在编程过程中,拥有行为和数据的是对象,而不是类。
下面我们声明一个简单的类:
#!/usr/bin/python
class Person:
pass # An empty block
p = Person()
print p
我们使用 class 语句后跟类名,创建了一个新的类。这后面跟着一个缩进的语句块形成类体。在这个例子中,我们使用了一个空白块,它由 pass 语句表示。
接下来,我们使用 类名后跟一对圆括号 来创建一个对象 / 实例。为了验证,我们简单地打印了这个变量的类型。它告诉我们我们已经在 __main__ 模块中有了一个 Person 类的实例。结果如下:

图 2
你可能已经注意到存储对象的计算机内存地址也打印了出来。这个地址在你的计算机上会是另外一个值,因为 Python 可以在任何空位存储对象。
2.9.2 对象的方法方法代表现实世界中的行为,简单示例如下:
class Person:
def sayHi(self):
print 'Hello, how are you?'
p = Person()
p.sayHi()
这里我们需要注意 sayHi 方法没有任何参数,但仍然在函数定义时有 self , self 代表对象本身,等价于 C++ 中的 self 指针和 Java 、 C# 中的 this 引用 。之后,我们通过对象名加点的方式来调用对象的方法。运行结果如下:

图 3
2.9.3 构造函数我们在使用类来构造一个对象的时候,通常在构造支出需要把对象本身的关键属性进行初始化。比如初始化一个人,如果我们认为年龄和名字是关键属性的话,我们在实例化这个人的时候,需要把相关属性的值传进来。上面我们知道出事化对象的时候是类名加括号的方式,实际上这个括号调用了一个内置的方法,我们称之为构造函数,正是该函数返回了被初始化的对象本身。
python 的构造函数名为 __init__ ,我们可以自定义传入参数的类型和个数。
#!/usr/bin/python
class Person:
def __init__(self, name):
self.name = name
def sayHi(self):
print 'Hello, my name is', self.name
p = Person('Swaroop')
p.sayHi()
这里,我们把 __init__ 方法定义为取一个参数 name (以及普通的参数 self )。在这个 __init__ 里,我们只是创建一个新的字段 name 。最重要的是,我们没有专门调用 __init__ 方法,只是在创建一个类的新实例的时候,把参数包括在圆括号内跟在类名后面,从而传递给 __init__ 方法。
现在,我们能够在我们的方法中使用 self.name 变量。这在 sayHi 方法中得到了验证。

图 4
2.9.4 变量变量代表属性和数据。在 python 中变量有两种,类变量和对象变量。类变量是全局的,对每个对象都共享,对象的变量是实例化的,每个对象之间互不影响。下面我们通过一段代码来对比:
#!/usr/bin/python
class Person:
'''Represents a person.'''
population = 0
def __init__(self, name):
'''Initializes the person's data.'''
self.name = name
print '(Initializing %s)' % self.name
# When this person is created, he/she
# adds to the population
Person.population += 1
def __del__(self):
'''I am dying.'''
print '%s says bye.' % self.name
Person.population -= 1
if Person.population == 0:
print 'I am the last one.'
else:
print 'There are still %d people left.' % Person.population
def sayHi(self):
'''Greeting by the person.
Really, that's all it does.'''
print 'Hi, my name is %s.' % self.name
def howMany(self):
'''Prints the current population.'''
if Person.population == 1:
print 'I am the only person here.'
else:
print 'We have %d persons here.' % Person.population
swaroop = Person('Swaroop')
swaroop.sayHi()
swaroop.howMany()
kalam = Person('Abdul Kalam')
kalam.sayHi()
kalam.howMany()
swaroop.sayHi()
swaroop.howMany()
这个例子略微有点长,但是它有助于说明类与对象的变量的本质。这里, population 属于 Person 类,因此是一个类的变量。 name 变量属于对象(它使用 self 赋值)因此是对象的变量。
观察可以发现 __init__ 方法用一个名字来初始化 Person 实例。在这个方法中,我们让 population 增加 1 ,这是因为我们增加了一个人。同样可以发现, self.name 的值根据每个对象指定,这表明了它作为对象的变量的本质。
在这个程序中,我们还看到 docstring 对于类和方法同样有用。我们可以在运行时使用 Person.__doc__ 和 Person.sayHi.__doc__ 来分别访问类与方法的文档字符串。
就如同 __init__ 方法一样,还有一个特殊的方法 __del__ ,它在对象消逝的时候被调用。对象消逝即对象不再被使用,它所占用的内存将返回给系统作它用。在这个方法里面,我们只是简单地把 Person.population 减 1 。
当对象不再被使用时, __del__ 方法运行,但是很难保证这个方法究竟在 什么时候 运行。如果你想要指明它的运行,你就得使用 del 语句,就如同我们在以前的例子中使用的那样。
这里需要注意的是:
l Python 中所有的类成员(包括数据成员)默认都是公有的。
l 如果你使用的数据成员名称以 双下划线前缀 比如 __privatevar , Python 的名称管