昨天和今天花了一点时间就把coursera上这门Using Databases with Python的week1刷完了. Week1讲的其实是Class, 所以顺便回顾一下Class. Week2要等下周了.

为什么会需要Class, 回归到最基本的认识, 为了效率, 为了偷懒, 不要重复写相同作用的代码. 一些terminology:

  • class: 相当于一个模板, template/bluprint
  • method/message: class里的function
  • field/attribute: class里的data
  • object/instance: 一个特定的实例, 也就是用class这个模板造出的一个实际物件

一个简单的栗子

class om2pyStudent:
	x = 0   # x也就是一个attribute

	def __init__(self):  # 这个称为constructor, 一般被用来设定变量
		print "I am constructed"

	def task(self):      # task就是一个method,也就是class里的function
		self.x = self.x + 1     # self就是指某一个class实例自己
		print "Complete task", self.x


bambooom = om2pyStudent()  # 定义了bambooom是一个om2pyStudent
bambooom.task()            # 重复了三次 task 这个method
bambooom.task()            # 每次都更新了bambooom里面的x的值
bambooom.task()

结果如下:

I am constructed
Complete task 1
Complete task 2
Complete task 3

第二个简单的栗子

class om2pyStudent:
	x = 0
	name = ""  # 定义了另一个attribute

	def __init__(self, name):  # 这次constructor里面多了一个参数name
		self.name = name      # 将参数name赋值给self.name, 也就是更新前面新定义的name
		print self.name, "constructed"

	def task(self):  # task就是一个method,也就是class里的function
		self.x = self.x + 1   # self就是指某一个class实例自己
		print self.name, "complete task", self.x


b = om2pyStudent("bambooom")  # 定义了b是一个om2pyStudent, 它的名字是bambooom
b.task()     # b做了一次task

j = om2pyStudent("aJiea")    # 定义了j是一个om2pyStudent, 它的名字是aJiea
j.task()     # j做了一次task

b.task()
b.task()     # b又做了两次task
j.task()     # j又做了一次task

结果如下:

bambooom constructed
bambooom complete task 1
aJiea constructed
aJiea complete task 1
bambooom complete task 2
bambooom complete task 3
aJiea complete task 2

虽然都是相同的在更新self.x, 但self指代不一样, 更新不一样

有关inheritance的栗子

从已有的class里创建新的class, 保持所有原有class的特性, 这样新的class就是一个child class, 原来的已有的class叫parent class. (还是为了方便重复使用)

class om2pyStudent:
	x = 0
	name = ""

	def __init__(self, name):
		self.name = name
		print self.name, "constructed"

	def task(self):
		self.x = self.x + 1
		print self.name, "complete task", self.x

class prdStudent(om2pyStudent):
# prdStudent是继承om2pyStudent的一个新的class, 有所有om2pyStudent的性质
	times = 0 # 在新的class可以定义新的attribute

	def c2t2(self): # 定义新的method
		self.times = self.times + 1
		self.task() # 仍然可以用继承过来的method
		print self.name, "came C2T2", self.times, "times"

b = om2pyStudent("bambooom")  # 定义了b是一个om2pyStudent
b.task()     # b做了一次task

j = prdStudent("aJiea")    # 定义了j是一个prdStudent, 它的名字是aJiea
j.task()     # j做了一次task
j.c2t2()     # j去了一次C2T2

结果如下:

bambooom constructed
bambooom complete task 1
aJiea constructed
aJiea complete task 1
aJiea came C2T2 1 times

一个简单的回顾 over.


但想起之前在这个网站上看过有关继承的一个有趣的python问题.

class Parent(object):
    x = 1

class Child1(Parent):
    pass

class Child2(Parent):
    pass

print Parent.x, Child1.x, Child2.x
Child1.x = 2
print Parent.x, Child1.x, Child2.x
Parent.x = 3
print Parent.x, Child1.x, Child2.x

这么一小段, 如果一开始看, 猜output大多会觉得是这样的

  1 1 1
  1 2 1
  3 2 1

但实际上的结果是这样的

  1 1 1
  1 2 1
  3 2 3

问题就出在最后一步, 为什么Parent.x变了之后, 会将Child2.x也变成了3, 但是同时Child1.x却没有变呢?

这个问题的关键是python内部是将class里的变量当做字典来处理, 并且此处涉及到搜索顺序. 当有多重继承时, 如果现在的class里没有定义的, 会从base class里去寻找. 搜索的顺序被称为Method Resolution Order (MRO), 不仅是method, attribute也同样适用. 有关MRO还可以看看Guido老爹写的旧文, 很有意思, 讲了MRO是如何从传统的逐层递进由左至右的算法演变到现在的算法C3, C3会拒绝出现矛盾顺序的class定义.

回归到这个栗子本身. 第一次print的时候, Child1Child2都没有自己的x, 所以根据MRO, 会向它们的base class也就是Parent去搜索. 所以得到第一排结果是1 1 1. 这个还是很好理解的.

然后Child1.x = 2Child1这个class添加了自己的变量x, 并赋值为2. 第二次print的时候, Child1.x就会从自己的class里获得刚刚新赋值的x(=2), 但Child2仍然没有自己的x, 还是从Parent获取.

最后Parent.x = 3, 是直接更改的Parent里的x, 赋值为3. 第三次print的时候, Child1.x先从自己的内部搜索, 发现有之前已经定义过的x(=2), 但Child2还是没有自己的x, 继续从Parent获取, 此时Parent.x就已经是更新变成了3了. 所以最后打印的结果是3 2 3

是不是觉得有点神奇呢? 反正我是这么觉得的.