弟兄们,那些离间你们,叫你们跌倒,背乎所学之道的人,我劝你们要留意躲避他们。因为这样的人不服侍我们的主基督,只服侍自己的肚腹,用花言巧语诱惑那些老实人的心。(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()
,首先就执行初始化函数,初始化函数有两个参数self
和name
,其中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),这句话道破了类和实例的关系。所谓工厂,就是可以用同一个模子做出很多具体的产品。类就是那个模子,实例就是具体的产品。
这就是通过类建立实例,并且通过实例来调用其属性和方法的过程。
如果你认为有必要打赏我,请通过支付宝:[email protected],不胜感激。