习题 40: 模块, 类和对象
Python 是一门“面向对象编程语言”。这意味着在Python中有一个叫做类的概念,你能通过类用一种特殊的方式构建你的软件。使用类的概念,能给你的程序增添一致性,这样你可以用一种很轻松方便的方式调用他们。至少,这是面向对象的理论。
我现在要通过使用你已经知道的字典和模块等来教你开始学习面向对象编程、类和对象。我的问题是面向对象编程(OOP)只是普通的怪异。你要为之而奋斗,努力尝试理解我讲的内容,编写代码,在下一个练习中,我会更深入的讲解。
我们要开始了。
模块就像字典
你知道字典是如何被创建以及使用的,它用来将一个事物对应到另一个事物。意思就是说如果你有一个字典,字典中包括一个key "apple",那么你就可以实现:
mystuff = {'apple': "I AM APPLES!"}
print mystuff['apple']
记住“从X获取Y”的说法,然后想一想模块(module)。你之前已经写过一些模块,你应该知道它们:
- 包含一些函数和变量的python文件
- 你可以导入这个文件
- 你可以用
.
操作符访问这个模块的函数和变量
想象一下我有一个叫做mystuff.py
的模块,在这个模块中有一个叫做apple
的函数,下面是 mystuff.py
模块的代码:
# this goes in mystuff.py
def apple():
print "I AM APPLES!"
当我写完以上代码,我就可以通过import来调用mystuff
模块,并访问apple
函数:
import mystuff
mystuff.apple()
我还可以增加一个叫做tangerine
的变量:
def apple():
print "I AM APPLES!"
# this is just a variable
tangerine = "Living reflection of a dream"
可以用相同的方法来访问:
import mystuff
mystuff.apple()
print mystuff.tangerine
返回再看字典,你应该发现模块的使用跟字典是相似的,只不过语法不同,我们比较一下:
mystuff['apple'] # get apple from dict
mystuff.apple() # get apple from the module
mystuff.tangerine # same thing, it's just a variable
也就是说我们在Python中有一套通用的模式:
- 有一个key=value模式的容器
- 通过key从容器中获取数据
在字典中,key是一个字符串,写法为[key]
,在模块中,key是一个标识符,写法为.key
,在其余的地方,他们几乎是一样的。
类就像模块
你可以认为一个模块就是一个可以存放Python代码的特殊字典,这样你就可以通过.
操作符访问它。Python还有一个另外的结构提供相似的功能,就是类(class)。类的作用是组织一系列的函数和数据并将它们放在一个容器里,这样你可以通过.
操作符访问到它们。
如果我想创建一个类似mystuff
的类,我需要这样做:
class MyStuff(object):
def __init__(self):
self.tangerine = "And now a thousand years between"
def apple(self):
print "I AM CLASSY APPLES!"
这相比于模块复杂一些,对比之下肯定会有不同,但是你应该能返现这个类就像写一个包含apple()
方法的“迷你”mystuff
模块一样.让你困惑的地方可能是__init__()
这个方法以及使用self.tangerine
给tangerine
赋值。
这正是为什么要使用类而不是仅有模块的原因:你可以使用Mystuff
这个类,还可以用它来创建更多个Mystuff
,而他们之间也不会互相冲突。当你导入一个模块时,你的整个项目也就只有一个这个模块。
在你理解这些之前,你需要明白什么是“对象”,以及如何像使用Mystuff.py
模块一样使用Mystuff
这个类。
对象就像导入
如果一个类就像一个迷你模块,那么类也会有一个类似import
的概念,这个概念被称为实例化,这只是对创建一种更虚幻更聪明的叫法。当一个类被实例化,你就得到一个类的对象。
实例化类的方法就是像调用函数一样调用这个类:
thing = MyStuff()
thing.apple()
print thing.tangerine
第一行就是实例化的操作,这个操作多像是在调用一个函数啊。当然了,python在幕后帮你做了一系列的事情,我带你来看看具体的调用步骤:
- python 查找
MyStuff()
并确认它是你已经定义过的类- python创建一个空的对象,该对象拥有你在类中用
def
创建的所有函数- python看你是否创建了
__init__
函数,如果有,调用该方法初始化你新创建的空对象- 在
MyStuff
中,__init__
方法有一个额外的变量self
,这是python为我创建的一个空的对象,我可以在其上设置变量。- 然后,我给
self.tangerine
设置一首歌词,然后初始化这个对象- python可以使用这个新创建好的对象,并将其分配给我可以使用的变量
thing
。
这就是当你调用一个类的时候,python做的事情。记住,这并不是把类给你,而是把类作为蓝本来创建这种类型东西的副本。
我给了你一个类的简单工作原理,这样你就可以基于你所了解的模块,建立你对类的理解。事实上,在这一点上,类和对象与模块是有区别的:
- 类是用来创建迷你模块的蓝本或定义
- 实例化是如何创建这些小模块,并在同一时间将其导入。实例化仅仅是指通过类创建一个对象。
- 由此产生的迷你模块被称为对象,你可以将其分配给一个变量,让它开始运行
在这一点上,对象和模块的表现不同,这只是提供一种让你理解类和对象的方法。
从一个事物中获取事物
现在我提供给你3中方法从一个事物中获取另一个:
# dict style
mystuff['apples']
# module style
mystuff.apples()
print mystuff.tangerine
# class style
thing = MyStuff()
thing.apples()
print thing.tangerine
类的举例
你应该可以看到在这三个key=value的容器类型有一定的相似性,你也可能有一堆问题.记住这些问题,下一节练习会训练你关于“面向对象的词汇”。这节练习中,我只想让你输入这些代码并保证它能正常运行,这样能让你再继续下一节练习之前获得一些经验。
class Song(object):
def __init__(self, lyrics):
self.lyrics = lyrics
def sing_me_a_song(self):
for line in self.lyrics:
print line
happy_bday = Song(["Happy birthday to you",
"I don't want to get sued",
"So I'll stop right there"])
bulls_on_parade = Song(["They rally around tha family",
"With pockets full of shells"])
happy_bday.sing_me_a_song()
bulls_on_parade.sing_me_a_song()
What You Should See
$ python ex40.py
Happy birthday to you
I don't want to get sued
So I'll stop right there
They rally around tha family
With pockets full of shells
物以类聚
虽说将函数放到字典里是很有趣的一件事情,你应该也会想到“如果 Python 能自动为你做
这件事情该多好”。事实上也的确有,那就是 class
这个关键字。你可以使用
class
创建更棒的“函数字典”,比你在上节练习中做的强大多了。Class(类)有着
各种各样强大的功能和用法,但本书不会深入讲这些内容,在这里,你只要你学会把它们
当作高级的“函数字典”使用就可以了。
用到“class”的编程语言被称作“Object Oriented Programming(面向对象编程)”语言。 这是一种传统的编程方式,你需要做出“东西”来,然后你“告诉”这些东西去完成它们的工作。 类似的事情你其实已经做过不少了,只不过还没有意识到而已。记得你做过的这个吧:
stuff = ['Test', 'This', 'Out']
print ' '.join(stuff)
其实你这里已经使用了 class。stuff
这个变量其实是一个 list class
(列表类)。
而 ' '.join(stuff)
里调用函数 join
的字符串 ' '
(就是一个空格)也是
一个 class —— 它是一个 string class (字符串类)。到处都是 class!
还有一个概念是 object(物件),不过我们暂且不提。当你创建过几个 class 后就会学到了。
你怎样创建 class 呢?和你创建 ROOMS
的方法差不多,但其实更简单:
class TheThing(object):
def __init__(self):
self.number = 0
def some_function(self):
print "I got called."
def add_me_up(self, more):
self.number += more
return self.number
# two different things
a = TheThing()
b = TheThing()
a.some_function()
b.some_function()
print a.add_me_up(20)
print a.add_me_up(20)
print b.add_me_up(30)
print b.add_me_up(30)
print a.number
print b.number
warning:: 嗯,你开始看到 Python 的“疣子”了。Python 是一门比较旧的语言,其中包含很多 丑陋的设计决定。为了将这些丑陋设计掩盖过去,他们就做了一些新的丑陋设计, 然后告诉人们让他们习惯这些新的坏设计。
class TheThing(object)
就是其中 一个例子。这里我就不展开讲了,不过你也不必操心为什么你的 class 要在后面添一个(object)
,只要跟着这样做就可以了,否则将来总有一天别的 Python 程序员 会吼你让你这样做。后面我们再讲为什么。
你看到参数里的 self
了吧?你知道它是什么东西吗?对了,它就是 Python 创建的
额外的一个参数,有了它你才能实现 a.some_function()` 这种用法,这时它就会把
前者翻译成
some_function(a)执行下去。为什么用
self呢?因为你的函数并
不知道你的这个“实例”是来自叫
TheThing或者别的名字的 class。所以你只要使用
一个通用的名字
self`` 。这样你写出来的函数就会在任何情况下都能正常工作。
其实你可以使用 self
以外的别的字眼,不过如果你这样做的话,你就会成为所有
Python 程序员的众之矢的,所以还是随大流的好。只有变态才会在这里乱改,我教你的
没错。对以后会读到你的代码的人好点儿,因为你现在的代码10年以后所有的代码都会是一
团糟。
接下来,看到 __init__
函数了吗?这就是你为 Python class 设置内部变量的方式。
你可以使用 .
将它们设置到 self
上面。另外看到我们使用了 add_me_up()
为你创建的 self.number
加值。后面你可以看到我们怎样可以使用这种方法为数字
加值,然后打印出来。
Class 是很强大的东西,你应该好好读读相关的东西。尽可能多找些东西读并且多多实验。 你其实知道它们该怎么用,只要试试就知道了。其实我马上就要去练吉他了,所以我不会 让你写练习了。你将使用 class 写一个练习。
接下来我们将把习题 来自 Percal 25 号行星的哥顿人(Gothons)
的内容重写,不过这回我们将使用 class:
## Animal is-a object (yes, sort of confusing) look at the extra credit
class Animal(object):
pass
## ??
class Dog(Animal):
def __init__(self, name):
## ??
self.name = name
## ??
class Cat(Animal):
def __init__(self, name):
## ??
self.name = name
## ??
class Person(object):
def __init__(self, name):
## ??
self.name = name
## Person has-a pet of some kind
self.pet = None
## ??
class Employee(Person):
def __init__(self, name, salary):
## ?? hmm what is this strange magic?
super(Employee, self).__init__(name)
## ??
self.salary = salary
## ??
class Fish(object):
pass
## ??
class Salmon(Fish):
pass
## ??
class Halibut(Fish):
pass
## rover is-a Dog
rover = Dog("Rover")
## ??
satan = Cat("Satan")
## ??
mary = Person("Mary")
## ??
mary.pet = satan
## ??
frank = Employee("Frank", 120000)
## ??
frank.pet = rover
## ??
flipper = Fish()
## ??
crouse = Salmon()
## ??
harry = Halibut()
你应该看到的结果
这个版本的游戏和你的上一版(来自 Percal 25 号行星的哥顿人(Gothons)
)效果应该是一样的,其实有些代码都几乎一样。比较一下两版
代码,弄懂其中不同的地方,重点需要理解这些东西:
- 怎样创建一个
class Game(object)
并且放函数到里边去。 __init__
是一个特殊的初始方法,可以预设重要的变量在里边。- 为 class 添加函数的方法是将函数在 class 下再缩进一阶,class 的架构就是通过 缩进实现的,这点很重要。
- 你在函数里的内容又缩进了一阶。
- 注意冒号的用法。
- 理解
self
的概念,以及它在__init__
、play
、death
里是怎样使用的。 - 研究
play
里的getattr
的功能,这样你就能明白play
所做的事情。 其实你可以手动在 Python 命令行实验一下,从而弄懂它。 - 最后我们怎样创建了一个
Game
,然后通过play()
让所有的东西运行起来。
加分习题
- 研究一下
__dict__
是什么东西,应该怎样使用。 - 再为游戏添加一些房间,确认自己已经学会使用 class 。
- 创建一个新版本,里边使用两个 class,其中一个是
Map
,另一个是Engine
。 提示: 把play
放到Engine
里面。
Study Drills
- 用本节学到的内容多写几首歌,确定你明白你把一个字符串的列表作为歌词传递进去。
- 把歌词放进一个单独的变量,再把这个变量传递给类
- 看看你能不能让这个类做更多的事情,如果没什么想法也没关系,试试看,会有什么
- 在网上搜索一些“面向对象编程”的资料,尝试让你的大脑填满这些资料。如果这些资料对你没有用,也没关系,这些东西有一半对我来说也是没有意义的。
Common Student Questions
为什么我在类里创建__init__ 或其他函数的时候需要使用self |
---|
如果你不实用self ,像这种代码cheese = 'Frank' 就是不明确的. 代码并不清楚你指的是实例的cheese 属性,还是一个叫做cheese d的全局变量。如果你使用self.cheese='Frank' 代码就会十分清楚你指的就是实例中的属性self.cheese . |
Copyright (C) 2010 by
Author: Zed Shaw
Translator:Zander Wong