首页 > 代码库 > 7.2类和类型

7.2类和类型

现在读者可能对什么是类有了答题的感觉———或者已经有些不耐烦听我对他进行更多的介绍了。在开始介绍之前,先来认识一下什么是类,已经它和类型又有什么不同(或相同)

类到底是什么

从前面的部分中,类这个词已经多次出现,可以将他或多或少地视为种类或者类型的同义词。从很多方面说,这就是类--一种对象。所有的对象都属于某一个类,称为类的实例(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的特性中,屏蔽了类范围内的变量。这根函数的局部和全局变量的行为十分类似。

7.2类和类型