弟兄们,那些离间你们,叫你们跌倒,背乎所学之道的人,我劝你们要留意躲避他们。因为这样的人不服侍我们的主基督,只服侍自己的肚腹,用花言巧语诱惑那些老实人的心。(ROMANS 16:17-18)

类(2)

现在开始不用伪代码了,用真正的python代码来理解类。当然,例子还是要用读者感兴趣的例子。

新式类和旧式类

Python是一个不断发展的高级语言(似乎别的语言也是不断发展的,甚至于自然语言也是),导致了Python 2和Python 3两个版本。

就是在Python 2中,还有“新式类”和“旧式类(也叫做经典类)”之分。这可真够分裂的。

新式类是Python 2.2引进的,在此后的版本中,我们一般用的都是新式类。

但是在Python 3中没有这种新旧的问题,它只是“类”。所以,如果你使用的是Python 3,并且也没有兴趣了解历史问题,可以跳过本节内容。不过,如果这样,如果有一天遇到老代码,你会后悔的。

书归正传,转到Python 2中,先写一个极简的旧式类。

>>> class AA:
...     pass

这就是一个旧式类。关于定义类的方法,下面会详细说明。读者姑且囫囵吞枣,认同我刚才建立的名为AA的类,为了简单,这个类内部什么也不做,就是用pass一带而过。但不管怎样,是一个类,而且是一个旧式类(它的另外一个名字是“经典类”,“旧”的时间长了就变成“经典”了)。

然后,将这个类实例化或者说建立一个实例(还记得上节中实例化吗?对,就是那个王美女干的事情):

>>> aa = AA()

不要忘记,实例化的时候,类的名称后面有一对括号,这跟调用函数的方法是一样的。

接下来做如下操作:

>>> type(AA)
<type 'classobj'>

AA()是调用类,但是AA指的就是那个类对象。

这一点非常类似于函数,比如函数foo(),而foo就是那个函数对象。在这里,“一切皆对象”应该再次浮现在你的脑海里。

type(AA)返回的是这个类对象的属性——classobj——AA是一个类对象。

>>> aa.__class__
<class __main__.AA at 0xb71f017c>

aa是一个实例,也是一个对象,每个对象都有__class__属性,用于显示它的类型。这里返回的结果是<class __main__.AA at 0xb71f017c>,从这个结果中可以读出的信息是,aa是类AA的实例。

>>> type(aa)
<type 'instance'>

解读一下上面含义:

type(aa)是要看实例aa的类型,显示的结果是instance,是告诉我们它是一个实例。

在这里是不是有点感觉不和谐呢?aa.__class__type(aa)都可以查看对象类型,但是它们居然显示不一样的结果。

看看我们已经熟悉的一个对象。

>>> a = 7
>>> a.__class__
<type 'int'>
>>> type(a)
<type 'int'>

对于整数7,毫无疑问,它是对象。用两种方式查看类型,返回的结果一样。为什么到类(严格讲是旧式类)这里,居然返回不一样呢?太不和谐了。

于是乎,就有了新式类,从Python2.2开始,变成这样了:

>>> class BB(object):
...     pass
... 

>>> bb = BB()

>>> bb.__class__
<class '__main__.BB'>
>>> type(bb)
<class '__main__.BB'>

终于把两者统一起来了,世界和谐了。

这就是新式类和旧式类的不同。

当然,不同点绝非仅仅于此,这里只不过提到一个现在能够理解的不同罢了。另外的不同还在于两者对于多重继承的查找和调用方法不同,旧式类是深度优先,新式类是广度优先。可以先不理解,后面会碰到的。

“喜新厌旧”是编程界的传统。所以,旧式类就不是我们讨论的内容了。

在本书此后的内容中,所有Python 2代码中的类,都是新式类。

如何定义新式类呢?

第一种定义方法,就是如同前面那样:

>>> class BB(object):
...     pass
...

跟旧式类的区别就在于类的名字后面跟上(object),这其实是一种名为“继承”的类的操作,当前的类BB是以类object为上级的(object被称为父类),即BB是继承自类object的新类。在python 3中,所有的类自然地都是类object的子类,就不用彰显出继承关系了。对了,这里说的有点让读者糊涂,因为冒出来了“继承”、“父类”、“子类”,不用着急,继续向下看。下面精彩,并且能解惑。

第二种定义方法,在类的前面写上这么一句:__metaclass__ == type,然后定义类的时候,就不需要在名字后面写(object)了。

>>> __metaclass__ = type
>>> class CC:
...     pass
... 
>>> cc = CC()
>>> cc.__class__
<class '__main__.CC'>
>>> type(cc)
<class '__main__.CC'>

两种方法,任你选用,没有优劣之分。但在本书中,如果在Python 2里面定义新式类,都会采用第一种定义方法。

创建类

前面我们创建了很简单的类,虽然没有什么太大的用途,但也是创建了类。为了更一般化地说明如何创建类,下面这个类,更具有通常类的结构。

Python2:

#!/usr/bin/env python
# coding=utf-8

class Person(object):
    """
    This is a sample of class.
    """

    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name

    def color(self, color):
        d = {}
        d[self.name] = color
        return d

Python 3:

class Person:
    """
    This is a sample of class.
    """

    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name

    def color(self, color):
        d = {}
        d[self.name] = color
        return d

这是一个具有“大众脸”的类,下面对它进行逐条解释。

Python 2的代码中,使用了class Person(object),代表着是新式类。而Python 3的代码中,不需要显示地继承object了。

class Person,这是在声明创建一个名为"Person"的类,其关键词是class——对照着学习,创建函数的关键词是def——新旧知识类比,更容易理解。类的名称一般用大写字母开头,这是惯例。如果名称是两个单词,那么两个单词的首字母都要大写,例如class HotPerson。当然,如果故意不遵循此惯例,也未尝不可,但是,会给别人阅读乃至于自己以后阅读带来麻烦,不要忘记“代码通常是给人看的,只是偶尔让机器执行”。既然大家都是靠右走的,你就别非要在路中间睡觉了。对于Python 2,因为是要继承object,所以要在后面有一个类似函数的参数列表那样的样式(object),Python 3则不需要,但是,不论是Python 2还是Python 3,要继承其它父类的时候,都要在后面写上(father_class_name)——当然,继承的问题是后文了。这一切结束之后,本行的最后就是冒号:

声明了类的名字(或者也包括继承的父类),然后就是类里面的代码块。秉承函数的做法,类里面的代码块,相对类定义类的那一行,也是要缩进四个空格——四个空格,尽可能不适用tab键,老老实实敲四个空格,否则后患无穷,绝非危言耸听。

再看类里面的代码,那些东西看起来并不陌生,你一眼就认出它们了——都是def这个关键词开头——就是已经学习过的函数。没错,它们就是函数。不过,仔细观察,会发现这些函数有点跟以往的函数不同,它们的参数中,都有self。这点差别,也是类中这种函数的特色,为了跟以往的函数有所却别,所以很多程序员喜欢把类里面的函数叫做“方法”——暂且不要纠结在名称上,虽然我看到过有人撰文专门分析了“方法”和“函数”的区别。但是,我倒是认为这不重要,重要的是类的中所谓“方法”和前面的函数,在数学角度看,丝毫没有区别。所以,你尽可以称之为函数。当然,听到有人说方法,也不要诧异和糊涂。它们本质是一样的。

需要再次提醒,函数的命名方法是以def发起,并且函数名称首字母不要用大写,可以使用aa_bb的样式,也可以使用aaBb的样式,一切看你的习惯了。

在上述代码示例中,函数(方法)的参数必须包括self参数,并且作为默认的第一个参数。这是需要注意的地方。至于它的用途,继续学习即可知道。

下面对类里面的每个方法(函数)做一个简要的阐述。

def __init__(self, name,这个方法是比较特殊的。当从命名方式上看,就不一般——用双下划线开头和结尾——这样的方法,在类里面还有很多,我们统称为“特殊方法”。对于__init__()这个特殊方法,通常还给它取了一个名字——构造函数——这是通常的叫法,但是我觉得这个名字不好,在本书中我把它叫做做初始化函数,因为从字面意义上看,它对应的含义是初始化,并且在Python中它的作用和其它语言比如Java中的构造函数还不完全一样,Python中的真正构造函数是__new__()

所谓初始化,就是让类有一个基本的面貌,而不是空空如也。做很多事情,都要初始化,让事情有一个具体的起点状态。比如你要喝水,必须先初始化杯子里面有水。在Python的类中,初始化就担负着类似的工作。在类实例化时首先就执行初始化函数。

此例子中的初始化函数的参数除了self,还有一个name,在这个类被实例化的同时,要传给它一个值。

在初始化函数里面,self.name = name的含义是要建立实例的一个属性,这个属性的名字也是name,这个属性的值等于参数name所传入的值。特别注意,这里的属性self.name和参数name是纯属巧合,你也可以设置成self.xxx = name,只不过这样写,总感觉不是很方便。

def get_name(self)def color(self, color)是类里面的另外两个方法,这两个方法的除了第一个参数必须是self之外,别的跟函数就没有什么区别了。只是需要关注的是两个方法中都用到了self.name,这个属性只能在类里面这样使用。

以上就将我们所建立的类进行了简要的分析。这也是建立一个类的基本方法。

实例

类是对象的定义,实例才是真实的物件。比如“人”是一个类,但是“人”终究不是具体的某个活体,只有“张三”、“李四”才是具体的物件,但他们具有“人”所定义的属性和方法。“张三”、“李四”就是“人”的实例。

承接前面的类,先写出调用该类的代码。

Python 2:

if __name__ == "__main__":
    girl = Person("canglaoshi")
    print girl.name 
    name = girl.get_name()
    print name
    her_color = girl.color("white")
    print her_color

Python 3:

if __name__ == "__main__":
    girl = Person("canglaoshi")
    print(girl.name)
    name = girl.get_name()
    print(name)
    her_color = girl.color("white")
    print(her_color)

girl = Person('canglaoshi')是利用上面的类创建实例。

创建实例,调用类Person(),首先就执行初始化函数,初始化函数有两个参数selfname,其中self是默认参数,不需要传值;name则需要给它传值,所以用Person('canglaoshi')的样式,就是为初始化函数中的name参数传值了,即name = 'canglaoshi'

girl就是一个实例,它有属性和方法。

先说属性。实例化过程中,首先要执行__init__(),并通过参数name,使得实例属性self.name = 'canglaoshi'。这里先稍微提一下self的作用,它实质上就是实例对象本身,当你用实例调用方法的时候,由解释器将那个实例传递给方法,所以不需要显示地为这个参数传值。那么self.name也顺理成章地是实例的属性了。所以print girl.name或者print(girl.name)的结果应该是canglaoshi

这就是初始化的功能。简而言之,通过初始化函数,确定了这个实例的“基本属性”(实例是什么样子的)。

girl.get_name()是通过实例来调用方法,也可以说建立了实例girl,这个实例就具有了get_name()方法。虽然在类里面,该方法的第一个参数是self,跟前面所述原因一样,通过实例调用该方法——实例方法——的时候,不需要显示地为self传递值,所以,在这里就不需要写任何参数。观察类中这个方法的代码可知,它的功能就是返回实例属性self.name的值,所以print name或者print(name)的结果是canglaoshi

girl.color("white")之所以要给参数传值,是因为def color(self, color)中有参数color。另外,这个方法里面也使用了self.name实例属性。最终该方法返回的是一个字典。所以print her_color或者print(her_color)的结果是{'canglaoshi': 'white'}

刚才以girl = Person("canglaoshi")的方式,建立了一个实例,仿照它,还可以建立更多的实例,比如boy = Person("zhangsan")等等。也就是一个类,可以建立多个实例。所以“类提供默认行为,是实例的工厂”(源自Learning Python),这句话道破了类和实例的关系。所谓工厂,就是可以用同一个模子做出很多具体的产品。类就是那个模子,实例就是具体的产品。

这就是通过类建立实例,并且通过实例来调用其属性和方法的过程。


总目录   |   上节:类(1)   |   下节:类(3)

如果你认为有必要打赏我,请通过支付宝:[email protected],不胜感激。

results matching ""

    No results matching ""