现在读者可能对什么是类有了答题的感觉———或者已经有些不耐烦听我对他进行更多的介绍了。在开始介绍之前,先来认识一下什么是类,已经它和类型又有什么不同(或相同)
类到底是什么
从前面的部分中,类这个词已经多次出现,可以将他或多或少地视为种类或者类型的同义词。从很多方面说,这就是类--一种对象。所有的对象都属于某一个类,称为类的实例(instance)。例如,现在请往窗外看,鸟就是鸟类的实例。鸟类是一个非常通用(抽象)的类,具有很多子类:看到的鸟可能属于子类“百灵鸟”。可以将“鸟类”想象成所有鸟的集合,而“百灵鸟类”是其中的一个子集。当一个对象所属的类是另外一个对象所属类的子集时,前者就被称为后者的子类(subclass),所以“百灵鸟类”是鸟类的子类。相反,“鸟类”是“百灵鸟类”的超类(supclass)。
注意 正日常交谈中,可能经常用复数来描述对象的类,比如birds或者larks。Python中,习惯上都使用单数名词,并且首字母大写,比如Bird和Lark。
这样一比喻,子类和超类就容易理解了。但是在面向对象程序设计中,子类是隐式地,因为一个类的定义取决于他所支持的方法。类的所有势力都会包含这些方法,所以所有子类的所有实例都有这些方法。定义子类只是个定义更多(也有可能是重载已经存在的)的方法的过程。
例如,鸟类Bird可能支持fly方法,而企鹅类Penguin(Bird的子类)可能会增加个EatFish方法。当创建Penguin类时,可能会想要重写(override)超类的方法,对于Penguin的实例来说,这个方法要么什么也不做,要么就产生异常,因为Penguin(企鹅)不会fly(飞)。
7.2.2创建自己的类
终于来了!可以创建自己的类了!先来看一个简单地类:
_metaclass_ = type # 确定使用新式类
class Person:
def setName(self ,name):
self.name = name
def getName(self):
return self.name
def greet(self):
print"Hello,world! I‘m%s." %self.name
这个例子包含3个方法定义,除了他们是写在class语句之外,一切都好像是函数定义。Person当然是类的名字。class语句会在函数定义的地方创建自己的命名空间,一切看起来都挺好,但是那个self参数看起来有点奇怪。它是对于对象自身的引用。那么它是什么对象?让我们创建一些实例看看:
>>>foo = Person()
>>>bar = Person()
>>>foo.set.Name(‘LuKe Skywalker‘)
>>>bar.set.Name(‘Anakin Skywalker‘)
>>>foo.greet()
>>>Hello.world!I‘m Lukwalker.
>>>bar.greet()
Hello.world! I‘m Anakin Skywalker.
好了,例子一目了然,应该能说明self的用处了。在调用foo的setName和greet函数时,foo自动将自己作为第一个参数传入函数中——因此形象的命名为self。对于这个变量,每个人可能都会有自己的叫法,但是因为他总是对象自身,所以习惯上叫做self。
显然这就是self的用处和存在的必要性。没有他的话,成员方法就没法访问他们要对其特性进行操作的对象本身了。
和之前一样,特性是可以在外部访问的:
>>>foo.name
‘Luke Skywalker‘
>>>bar.name = ‘Yoda‘
>>>bar.greet()
Hello.world!I‘m Yoda.
7.2.3特性、函数和方法
self参数事实上正是方法和函数的区别。方法(更专业一点可以绑定方法)将他们的第一个参数绑定到所属的实例上,因此你无需显示提供该参数。当然也可以将特性绑定到一个普通函数上,这样就不会有特殊的self参数了:
>>>class Class:
def method(self)
print ‘I have a self‘
>>>def function()
print"I don‘t..."
>>>instance = Class()
>>>instance.method()
I have a self!
>>>instance.method = function
>>>instance.method()
I don‘t...
注意,self参数并不依赖于调用方法的方式,前面我们使用的是instance.method(实例.方法)的形式,可以随意使用其他变量引用同一个方法:
>>>class Bird:
song = ‘Squaak!‘
def sing(self):
print self .song
>>>bird = Bird()
>>>bird.sing()
Squaak!
>>>birdsong = bird.sing
>>>birdsong()
Squaak!
尽管最后一个方法调用看起来与函数调用十分相似,但是变量birdsong引用绑定方法bird.sing上,也就意味着着还是会对self参数进行访问(也就是说,他仍旧绑定到类的相同实例上)
再论私有化
默认情况下,程序可以从外部访问一个对象的特性。再次使用前面讨论过的有关封装的例子:
>>>c.name
‘Sir Lancelot‘
>>>c.name = ‘Sir Gumby‘
>>>c.getName()
‘Sir Gumny‘
有些程序员觉得这样做是可以的,但是有些人(比如SmallTalk之父,SmallTalk的对象特性只允许同一个对象的方法访问)觉得这样就破坏了封装的原则。他们认为对象的状态对于外部应该是完全隐藏的(不可访问)。有人可能觉得奇怪为什么他们会站在如此极端的立场上。每个对象管理自己的特性还不够吗?为什么要对外部世界隐藏呢?毕竟如果能直接使用ClosedObject的name特性的话就不用使用setName和getName方法了。
关键在于其他程序员可能不知道(可能也不应该知道)你的对象内部的具体操作。例如,ClosedOject可能会在其他对象更改自己的名字的时候,给一些管理员发送邮件消息。这应该是setName方法的一部分。但是如果直接使用c.name设定名字会发生什么?什么都没发生,Email也没有发出去。为了避免这类事件的发生,应该使用私有(private)特性,这是外部对象无法访问,但是getName和setName等访问器(accessor)能够访问的特性。
python并不直接支持私有方式,而要靠程序员自己把握在外部进行特性修改的时机。毕竟在使用对象前应该知道如何使用。但是,可以用一些小技巧达到私有特性的效果。
为了让方法或者特性变为私有(从外部无法访问),只要在他的名字前面加上双划线即可:
class Secretive:
def__inaccessible(self):
print "Bet you can‘t see me..."
def accessible(self):
print " The secret message is:"
self.__inaccessible()
现在_inaccessible从外界是无法访问的,而在类内部还能使用(比如从acessible)访问:
>>> s= Secretive()
>>>s._inaccssible()
Traceback(most recent call list):
File "<pyshell#112>", Line 1, in ?
s.inaccessible()
AttributeError: Secretive instance has no attribute ‘_inaccessible‘
>>>s.accessible()
The seret message is:
Bet you can‘t see me...
尽管双下划綫有些奇怪,但是看起来像是其他语言中的标准私有方法。真正发生的事情才是不标准的。类的内部定义中,所有以双下划线的名字都被“翻译”成前面加上单划线和类名的形式。
Secretive._Secretive_inaccessible
<unbound method Secretive._inaccessible>
在了解了这些幕后的事情后,实际上还是能在类外访问这些私有方法,尽管不应该这么做:
>>> s._Secretive__inaccessible()
Bet you can‘t see me ...
简而言之,确保其他人不会访问对象的方法和特性是不可能的,但是这类“名称变化术”就是他们不应该访问这些函数或者特性的强有力信号。
如果不需要使用这种方法,但是又不想其他数据访问内部数据,那么可以使用单下划线。这不过是个习惯,但的确有实际效果。例如前面有下划线的名字都不会被带星号的import语句(from moduel import*)导入。
7.2.4类的命名空间
下面的两个语句(几乎)等价:
def foo(x): return x*x
foo = lambda x: x*x
两者都创建了返回参数平方的函数,而且都将变量foo绑定到函数上。变量foo可以在全局(模块)范围进行定义,也可以处于局部的函数或方法内。定义类时,同样的事情也会发生,所有位于class语句中的代码都在特殊的命名空间中执行——类命名空间(class namespace)。这个命名空间可由类内所有成员访问。并不是所有python程序员都知道类的定义其实就是执行代码块,这一点非常有用,比如,在类的定义区并不限定只能使用def语句:
>>>class C:
print ‘Class C being defined...‘
Class C being defined...
>>>
看起来有点傻,但是看看下面的:
class MemberCouner:
members = 0
def init(self)
MemberCounter.members+=1
>>> m1 = MemberCounter()
>>> m1.init()
>>> MemberCounter.members
1
>>> m2 = MemberCounter()
>>> m2.init()
>>>MemberCounter.members
2
上面的代码中,在类作用域中定义了一个可供成员(实例)访问的变量,用来计算类的成员数量。注意init用来初始化所有实例:
就像方法一样,类作用域内的变量也可以被所有实例访问:
>>> m1.members
2
>>>m2.members
2
那么在实例中重绑定members特性呢?
>>>m1.members = ‘Two‘
>>>m1.members
‘Two‘
>>>m2.members
2
新numbers值被写到了m1的特性中,屏蔽了类范围内的变量。这根函数的局部和全局变量的行为十分类似。