函数
我们的程序很快就会变得越来越大,越来越复杂。需要一些方法把它们分成较小的部分进行组织,这样更易于编写,也更容易明白。
要把程序分解成较小的部分,主要有 3 种方法。
- 函数(function),就像是代码的积木,可以反复地使用。
- 利用对象(object),可以把程序中的各部分描述为自包含的单元。
- 模块(module),就是包含程序各部分的单独的文件。
在这一章中,我们将学习函数,后面两章会讨论对象和模块。学习完这些知识,我们就具备了所需要的全部基本工具,可以开始使用图形和声音并且创建游戏了。
积木
最简单地讲,函数就是可以完成某个工作的代码块。这是可以用来构建更大程序的一个小部分。可以把这个小部分与其他部分放在一起,就像用积木搭房子一样。
创建函数
创建或定义函数要使用 Python 的 def
关键字。然后可以利用函数名来使用或调用这个函数。下面先来看一个简单的例子。
def printMyAddress():
print('Warren Sande')
print('123 Main street')
print('Ottawa, Ontario, Canada')
print('K2M 2E9')
pass
printMyAddress() #调用(使用)函数
print('Done the function')
第 1 行中,我们使用 def
关键字定义了一个函数。在函数名后面有一对括号()
,然后是一个冒号:
。
这个图中包括以下步骤。
- 从这里开始。这是主程序的开始。
- 调用函数时,跳到函数中的第一行代码。
- 执行函数中的每一行代码。
- 函数完成时,从离开主程序的那个位置继续执行。
空函数
如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
pass
语句什么都不做,那有什么用呢?实际上 pass
可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个 pass
,让代码能运行起来。
调用函数
调用函数是指运行函数里的代码。如果我们定义了一个函数,但是从来不调用它,这些代码就永远也不会运行。调用函数时要使用函数名和一对括号。有时括号里还会有些东西,有时也可能什么也没有。
使用函数的主要原因是,一旦定义了函数,就可以通过调用反复地使用。所以如果我们想把地址打印 5 次,可以这样做:
printMyAddress()
printMyAddress()
printMyAddress()
printMyAddress()
printMyAddress()
Warren Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
Warren Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
Warren Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
Warren Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
Warren Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
传递参数(输入)
使用函数还有一个原因,每次函数运行时可以让它有不同的表现。来看括号做什么用:它用来传递参数(argument)!
假设你希望对你的所有家庭成员使用这个地址打印函数。所有人的地址都是一样的,但是每一次人名会有所不同。不能在函数中把人名硬编码写成 Warren Sande,你可以建立一个变量。调用函数时将这个变量传递到函数。
函数运行时,变量 myName
会填人调用函数时为它传入的任何参数。调用函数时,我们把参数放在括号里,通过这种方式将参数传入函数。
def printMyAddress(myName):
print(myName) #打印人名
print("123 Main Street")
print("Ottawa, Ontario,Canada")
print("K2M 2E9")
pass
printMyAddress("Carter Sande")
----------------out---------------------
Carter Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
每次可以用不同方式打印地址,比如:
printMyAddress("Carter Sande")
printMyAddress("Warren Sande")
printMyAddress("Kyra Sande")
printMyAddress("Patricia Sande")
----------------out---------------------
Carter Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
Warren Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
Kyra Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
Patricia Sande
123 Main Street
Ottawa, Ontario, Canada
K2M 2E9
我们向函数传入什么值,函数中就会使用什么值,并作为地址的人名部分打印出来。
要向街道上的每一个人发送信件,我们的地址打印函数需要两个参数:一个对应人名,另一个对应门牌号码:
def printMyAadress(someName, houseNum): #使用两个变,两个参数
print(SomeName)
print(houseNum)
print("Main Street")
print("Ottawa, Ontario, Canada")
print("K2M 2E9")
pass
printMyAddress("Carter Sande", "45")
printMyAddress("Jack Black", "64")
printMyAddress("Tom Green", "22")
printMyAddress("Todd White", "36")
使用多个参数时,要用逗号来分隔。
函数返回值
函数的另一个突出作用是:它们还可以向你发回一些东西。我们已经知道,可以向函数发送信息(参数),不过函数还可以向调用者发回信息。从函数返回的值称为结果(result)或返回值(return value)。
要让函数返回一个值,需要在函数中使用 Python 关键字 return
。下面给出一个例子:
def calculateTax(price, tax_rate):
taxTotal = price + (price * tax_rate)
return taxTotal
这会把值 taxTotal
发回到调用这个函数的程序部分。
不过发回这个值时,它会去哪里呢?返回值会回到调用这个函数的代码。看下面的例子:
totalPrice = calculateTax(7.99, 0.06)
print(totalPrice)
calculateTax
函数会返回一个值:8.4694
,这个值将赋给 totalPrice
。
使用表达式的任何地方都可以使用函数来返回值。可以把返回值赋给一个变量(就像前面一样),也可以在另一个表达式中使用,或者打印出来。
思考:返回多个值?
变量作用域
有些变量在函数之外,如 totalPrice
,还有一些变量在函数内部,如 total
。这些变量只是同一个东西的两个不同名字。
在我们的 calculateTax
例子中,totalPrice
和 total
是贴在同一个东西上的两个标签。对于函数而言,函数内的名字只是在函数运行时才会创建。在函数运行之前或者完成运行之后甚至根本不存在。Python 提供了内存管理(memory management),可以自动完成这个工作。Python 在函数运行时会创建新的名字在函数内使用,当函数完成时会把它们删除。最后这部分很重要:函数运行结束时,其中的所有名字都不再存在。
函数运行时,函数之外的名字被搁置一边,而没有用到。只有函数内部的名字会被用到。程序中使用(或者可以使用)变量的部分称为这个变量的作用域(scope)。
局部变量
在上面的例子中,变量 price
和 total
只在函数内使用。我们说 price
、total
和 tax_rate
的作用域是 calculateTax()
函数。这也称为这些变量是局部的(local)。price
、total
和 tax_rate
变量是 calculateTax()
函数中的局部变量。
def calculateTax(price, tax_rate):
total = price + (price * tax_rate)
return total
my_price = float(input("Enter a price :"))
totalPrice = calculateTax(my_price, 0.06)
print("price = " + str(my_price) + " Total price =" + str(totalPrice))
print(price) #尝试打印price
如果运行这个程序,会得到这样一个错误:在 calculateTax()
函数以外,变量 price
根本没有定义。它只是在函数运行时才存在。试图在这个函数之外打印 price
的值时(此时函数并没有运行),就会得到一个错误。
全局变量
与局部变量 price
对应,变量 my_price
和 totalPrice
在函数之外定义(程序主部分中)。我们使用全局变量(global)表示有更大作用域的变量。在这种情况下,更大是指程序的主部分,而不是函数内部。如下面的程序,完全可以在另一个位置使用变量 my_price
和 totalPrice
,它们仍然有之前给定的值。它们仍在合法的作用域中(in scope)。因为我们可以在程序的任何地方使用这些变量,所以把它们称作全局变量(global variable)。
def calculateTax(price, tax_rate):
total = price + (price * tax_rate)
print(my_price)
return total
my_price = float(input("Enter a price :"))
totalPrice = calculateTax(my_price, 0.06)
print("price = " + str(my_ price) + " Total price =" + str(totalPrice))
如果函数的任何部分试图改变这个变量,Python 会创建一个新的局部变量。所以如果你打算这样做:
my_ price = my_ price + 10
那么 my_price
将是 Python 在函数运行时创建的一个新的局部变量。
def calculateTax(price, tax_rate):
total = price + (price * tax_rate)
my_price = 10000
print('my_price (inside function) = %f' % my_price)
return total
my_price = float(input("Enter a price :"))
totalPrice = calculateTax(my_price, 0.06)
print("price = " + str(my_ price) + " Total price =" + str(totalPrice))
print('my_price (outside function) = %f' % my_price)
可以看到,现在有两个名为 my_price
的不同变量,分别有不同的值。一个是 calculateTax()
函数中的局部变量,我们将它设置为 10000
。另一个是主程序中定义的全局变量,用来获取用户的输入,它的值是 7.99
。
我们看到,如果试图从函数内改变一个全局变量的值,Python 会创建一个新的局部变量。这是为了防止函数无意地改变全局变量。不过,有些情况下确实要在函数中改变一个全局变量。这该怎么做呢?
可以用Python的一个关键字 global
来做到。可以这样来使用:
def calculateTax(price, tax_rate):
global my_price #告诉Python你想使用全局版本的my_ price
如果使用 global
关键字,Python 不会建立名为 my_price
的局部变量,而是会使用名为 my_price
的全局变量。另外,如果还没有名为 my_price
的全局变量,Python 就会创建一个。
你可能已经从一些例子中注意到,往往很难知道一个变量是局部的还是全局的,这让代码更加混乱,因为存在同名的不同变量。而且,只要有混乱,错误就会乘虚而入。所以对目前的状况来说,建议你对局部变量和全局变量使用不同的名字。这样就不会有混乱,也能把错误拒之门外。
lambda表达式
在 Python 里有两类函数:
- 用
def
关键词定义的正规函数 - 用
lambda
关键词定义的匿名函数,它没有函数名#************************** # lambda - 定义匿名函数的关键词。 # arguments - 函数参数,它们可以是位置参数、默认参数、关键字参数,和正规函数里的参数类型一样。 # : - 冒号,在函数参数和表达式中间要加个冒号。 # expression - 只是一个表达式,输入函数参数,输出一些值。 #************************** lambda arguments: expression
expression
中没有return
语句,因为lambda
不需要它来返回,表达式本身结果就是返回值。- 匿名函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
```python
def sqr(x):
return x ** 2
print(sqr)
y = [sqr(x) for x in range(10)] print(y)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
lbd_sqr = lambda x: x ** 2 print(lbd_sqr)
at 0x000000BABB6AC1E0>
y = [lbd_sqr(x) for x in range(10)] print(y)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
sumary = lambda arg1, arg2: arg1 + arg2 print(sumary(10, 20))
30
func = lambda *args: sum(args) print(func(1, 2, 3, 4, 5))
15
匿名函数常常应用于函数式编程的高阶函数 (high-order function)中,主要有两种形式:
1. 参数是函数 (filter, map)
1. 返回值是函数 (closure)
<a name="EAp8p"></a>
### filter
过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表, 可以使用 list() 来转换。
```python
odd = lambda x: x % 2 == 1
templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(templist))
# [1, 3, 5, 7, 9]
map
根据提供的函数对指定序列做映射。
m1 = map(lambda x: x ** 2, [1, 2, 3, 4, 5])
print(list(m1))
# [1, 4, 9, 16, 25]
m2 = map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
print(list(m2))
# [3, 7, 11, 15, 19]
高阶函数
def apply_to_list(fun, some_list):
return fun(some_list)
lst = [1, 2, 3, 4, 5]
print(apply_to_list(sum, lst))
# 15
print(apply_to_list(len, lst))
# 5
print(apply_to_list(lambda x: sum(x) / len(x), lst))
# 3.0
Revision
- 什么是函数。
- 什么是参数(argument或parameter)。
- 如何向函数传递一个参数。
- 如何向函数传递多个参数。
- 如何让函数向调用者返回一个值。
- 变量作用域是什么,什么是局部变量和全局变量。如何在函数中使用全局变量。
测试题
- 使用哪个关键字来创建函数?
- 如何调用函数?
- 如何向函数传递信息(参数)?
- 函数最多可以有多少个参数?
- 如何从函数返回信息?
- 函数运行结束后,函数中的局部变量会发生什么?
动手试一试
编写一个函数,用大写字母打印你的名字,就像下面这样,编写一个程序多次调用这个函数。
P P I T T T T T T T T T T P P I T T P P I T T P P I T T P I T T P I T T P I T T
建立一个函数,可以打印全世界任何人名、门牌号、街道、城市、州或省、邮政编码和国家。(提示:这需要7个参数。可以把它们作为单独的参数传入,也可以作为一个列表。)
- 编写一个函数计算零钱的总面值,包括五分币、二分币和一分币。函数应当返回这些硬币的总面值。然后编写一个程序调用这个函数。程序运行时应当得到类似下面的输出:
quarters: 3 dimes: 6 nickels: 7 pennies: 2 total is $1.72
对象
在前几章中,我们已经了解了可以使用不同方式组织数据和程序,以及把东西收集在一起。我们看到了列表可以收集变量(数据),函数可以把一些代码收集到能够反复使用的单元中。
对象(object)则让这种收集的思想更向前迈进一步。对象可以把函数和数据收集在一起。这个主意在编程中非常有用,而且在很多很多的程序中都已经用到。实际上,如果仔细分析 Python,几乎一切都是对象。按编程的术语来讲,我们说 Python 是面向对象的(object oriented)。这说明,Python 中可以使用对象(实际上这也相当容易)。并不是一定得创建自己的对象,不过这样可以让很多事情更容易一些。
对象是蓝图(blue print),制定‘游戏’规则,给默认值,描述现象。
真实世界中的对象
在 Python 中定义什么是对象也可以作为一个很好的起点。拿球来举个例子。可以操作一个球,比如捡球、抛球、踢球或者充气(对于某些球来说)。我们把这些操作称为动作(action)。还可以通过指出球的颜色、大小和重量来描述一个球。这些就是球的属性(attribute)。
真实世界的真实对象(物体)包括两个方面:
- 可以对它们做什么(动作)。
- 如何描述(属性或特性)。
真实世界中的对象具有独占性,一个对象我拥有了,你就没有,越稀有越有价值。想要拥有一个一模一样的对象,是要付出成本代价的(再生产一个)。但是计算机中的对象或者产品不具有独占性,相反的,具有很强的‘分享性’,复制一个对象的成本几乎为零,分析的越多越有价值。《大教堂与集市》
Python 中的对象
在 Python 中,一个对象的特征(或“你知道的事情”)也称为属性(attribute),这应该很好记。动作(或“能够对对象做的操作”)称为方法(method)。如果要建立一个球的 Python 版本或者模型(model),球就是一个对象,它要有属性和方法。
球的属性可能包括,这些都是关于球的描述。
ball.color
ball.size
ball.weight
球的方法可能包括,这些都是可以对球做的操作。
ball.kick()
ball.throw()
ball.inflate( )
属性
属性就是你所知道(或者可以得出)的关于球的所有方面。球的属性就是一些信息(数字、字符串等等)。它们就是变量,只不过是包含在对象中的变量。
print(ball.size) #可以显示
ball.color = 'green' #可以为它们赋值
myColor = ball.color #可以把它们赋给常规的、不是对象的变量
myBall.color = yourBall.color #可以把它们赋给其他对象的属性
方法
方法就是可以对对象做的操作,它们是一些代码块,可以调用这些代码块来完成某个工作。方法就是包含在对象中的函数。函数能做到的,方法都可以做到,包括传递参数和返回值。
对象 = 属性 + 方法
**
所以利用对象,可以把一个东西的属性和方法(你知道的事情和你可以做的事情)收集在一起。属性是信息,方法是动作。
点记法
在前面的球例子中,你可能已经注意到对象名与属性或方法名之间的点。这是 Python 使用对象属性和方法的一种记法:object.attribute
或 object.method()
。就这么简单。这称为点记法,很多编程语言中都使用了这种记法。
创建一个对象
Python中创建对象包括两步。第一步是定义对象看上去什么样,会做什么,也就是它的属性和方法。但是创建这个描述并不会真正创建一个对象。这有点像一个房子的蓝图。蓝图可以告诉你房子看上去怎么样,但是蓝图本身并不是一个房子。你不可能住在一个蓝图里。只能用它来建造真正的房子。实际上,可以使用蓝图盖很多的房子。
在Python中,对象的描述或蓝图称为一个类(class)。
class Ball: #这里告诉Python我们在建立一个类
def bounce(self):
if self.direction == "down":
self.airection = "up"
pass
pass
第二步是使用类来建立一个真正的对象。这个对象称为这个类的一个实例(instance)。
创建一个实例
前面提到过,类定义并不是一个对象。这只是蓝图。现在来盖真正的房子。
如果想创建Ball的一个实例,可以这样做:
myBall = Ball()
myBall2 = Ball()
myBall3 = Ball()
myBall4 = Ball()
#下面给它提供一些属性:
myBall.direction = "down"
myBall.color = "green"
myBal1.size = "small"
来看一个完整的例子:
class Ball:
def bounce(self):
if self.direction == "down":
self.direction = "up"
pass
pass
myBall = Ball()
myBall.direction = "down"
myBall.color = "red"
myBall.size = "small"
print("I just created a ball.")
print("My ball is ", myBall.size)
print("My ball is ", myBall.color)
print("My ball's direction is ", myBall.direction)
print("Now I'm going to bounce the ball")
print()
myBall.bounce()
print("Now the ball's direction is ", myBall.direction)
//=============================results=================================
I just created a ball.
My ball is small
My ball is red
My ball's direction is down
Now I'm going to bounce the ball
Now the ball's direction is up
初始化
创建球对象时,并没有在 size
、color
或 direction
中填人任何内容。必须在创建对象之后填充这些内容。不过有一种方法可以在创建对象时设置属性。这称为初始化对象。
创建类定义时,可以定义一个特定的方法,名为 __init__()
,只要创建这个类的一个新实例,就会运行这个方法。可以向 __init__()
方法传递参数,这样创建实例时就会把属性设置为你希望的值。
class Ball:
def __init__(self, color, size, direction):
self.color = color
self.size = size
self.direction = direction
def bounce(self):
if self.direction == "down":
self.direction = "up"
myBall = Ball("red", "small", "down")
print("I just created a ball.")
特殊成员(属性、方法)
str()
Python 中的对象有一些“魔法”方法,当然它们并不是真的有魔法!这些只是在你创建类时 Python 自动包含的一些方法。Python 程序员通常把它们叫做特殊方法(special method)。
所有“魔法”方法都在方法名称前后各使用两条下划线。
我们已经知道,__init__()
方法会在对象创建时完成初始化。每个对象都内置有一个 __init__()
方法。如果你在类定义中没有加入自己的 __init__()
方法,就会有这样一个内置方法接管,它的工作就是创建对象。
另一个特殊方法是 __str__()
,它会告诉 Python 打印(print)一个对象时具体显示什么内容。Python 会默认以下内容:
- 实例在哪里定义。
- 类名。
- 存储实例的内存位置。
不过,如果你希望 print
为对象显示其他的内容,可以定义自己的 __str__()
,这会覆盖内置的 __str__()
方法。
class Ball:
def __init__(self , color, size, airection):
self.color = color
self.size = size
self.direction = direction
def __str__(self):
msg = "Hi, I'm a %s %s ball" % (self.size, self.color)
return msg
myBall = Ball("red", "small", "down")
print(myBall)
//=============================results=================================
Hi, I'm small red ball!
self
你可能已经注意到,在类属性和方法定义中多处出现了 self
,比如:def bounce(self):
self
是什么意思?我们说过,可以使用蓝图盖很多个房子,还记得吧?使用一个类也可以创建多个对象实例,例如:创建 Ball
类的两个实例
cartersBall = Ball("red", "small","down")
warrensBall = Ball("green", "medium", "up")
调用其中一个实例的方法时,像这样: warrensBall.bounce()
方法必须知道是哪个实例调用了它。是 cartersBall
需要反弹吗?还是 warrensBall
? self
参数会告诉方法哪个对象调用它。这称为实例引用(instance reference)。
调用方法时,warrensBall.bounce()
的括号里没有参数,但是方法里却有一个 self
参数。既然我们并没有传入任何东西,这个 self
参数从哪里来的?这是 Python 处理对象的另外一个“魔法”。调用一个类方法时,究竟是哪个实例调用了这个方法?这个信息(也就是实例引用)会自动传递给方法。
这就像写成: Ball.bounce(warrensBall)
在这种情况下,我们告诉了 bounce()
方法哪个球要反弹。实际上,这个代码也能正常工作,因为写成 warrensBall.bounce()
时,Python 在后台确实也是这么做的。
单例
class Earth:
pass
a = Earth()
print(id(a))
# 260728291456
b = Earth()
print(id(b))
# 260728291624
#------------------------
class Earth:
__instance = None # 定义一个类属性做判断
def __new__(cls):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance
else:
return cls.__instance
pass
a = Earth()
print(id(a))
# 512320401648
b = Earth()
print(id(b))
# 512320401648
示例类-HotDog
热狗的属性
cooked_level
: 这是一个数字,通过这个属性我们可以知道热狗烤了多长时间。0 ~ 3
表示还是生的,超过3
表示半生不熟,超过5
表示已经烤好,超过8
表示已经烤成木炭了!我们的热狗开始时是生的。cooked_string
: 这是一个字符串,描述热狗的生熟程度。-
condiments
: 这是热狗上的配料列表,比如番茄酱、芥末酱等。
热狗的方法
cook()
: 把热狗烤一段时间。这会让热狗越来越熟。add_condiment()
: 给热狗加一些配料。__init__()
: 创建实例并设置默认属性。__str__()
: 让print
的结果看起来更好一-些。
class HotDog:
def __init__(self):
self.cooked_level = 0
self.cooked_string = "Raw"
self.condiments = []
pass
def cook(self, time):
self.cooked_level = self.cooked_level + time
if self.cooked_level > 8:
self.cooked_string = "Charcoal"
elif self.cooked_level > 5:
self.cooked_string = "Well-done"
elif self.cooked_level > 3:
self.cooked_string = "Medium"
else:
self.cooked_string = "Raw"
pass
def __str__(self):
msg = "hot dog"
if len(self.condiments) > 0:
msg = msg + " with "
for i in self.condiments:
msg = msg + i + ", "
msg = msg.strip(", ")
msg = self.cooked_string + " " + msg + "."
return msg
def adaCondiment(self,condiment):
self.condiments.append(condiment)
pass
myDog = HotDog()
print(myDog.cooked_level)
print(myDog.cooked_string)
print(myDog.condiments)
print("Now I'm going to cook the hot dog")
myDog.cook(4)
print(myDog.cooked_level)
print(myDog.cooked_string)
myDog = HotDog()
print(myDog)
print('Cooking hot dog for 4 minutes...")
myDog.cook(4)
print(myDog)
print("Cooking hot dog for 3 more minutes...")
myDog.cook(3)
print(myDog)
print("What happens if I cook it for 10 more minutes?")
myDog.cook(10)
print(myDog)
print("Now, I'm going to add some stuff on my hot dog")
myDog.addCondiment("ketchup")
myDog.addCondiment("mustard")
print(myDog)
//=============================results=================================
Raw hot dog.
Cooking hot dog for 4 minutes...
Medium hot dog.
Cooking hot dog for 3 more minutes...
Well-done hot dog.
What happens if I cook it for 10 more minutes?
Charcoal hot dog.
Now, I'm going to add some stuff on my hot dog
Charcoal hot dog with ketchup, mustard.
多态
同一个方法,不同的行为
非常简单,多态是指对于不同的类,可以有同名的两个(或多个)方法。取决于这些方法分别应用到哪个类,它们可以有不同的行为。
例如,假设你要建立一个程序做几何题,需要计算不同形状的面积,比如三角形和正方形。你可以创建两个类,如下:
class Triangle:
def __init__(self, width, height):
self.width = width
self.height = height
def getArea(self):
area = self.width * self.height / 2.0
return area
class Square:
def __init__(self, size):
self.size = size
def getArea(self):
area = self.size * self.size
return area
这两个形状都使用了方法名 getArea()
,不过每个形状中这个方法做的工作不同。这就是一个多态的例子。
myTriangle = Triangle(4 , 5)
mySquare = Square(7)
>>> myTriangle.getArea()
10.0
>>> mySquare.getArea()
49
继承
在真实的(非编程)世界中,人们可以从他们的父母或者其他亲戚那里继承一些东西。你可以继承一些特征,比如说红头发,或者可以继承像钱和财产之类的东西。
在面向对象编程中,类可以从其他类继承属性和方法。这样就有了类的整个“家族”,这个“家族”中的每个类共享相同的属性和方法。这样一来,每次向“家族”增加新成员时就不必从头开始。
从其他类继承属性或方法的类称为派生类(derived class)或子类(subclass)。
假想我们要建立一个游戏,玩家一路上可以捡起不同的东西,比如食物、钱或衣服。可以建一个类,名为GameObject
。GameObject
类有 name
等属性(例如 coin、apple 或 hat)和 pickUp()
等方法(它会把硬币增加到玩家的物品集合中)。所有游戏对象都有这些共同的方法和属性。
然后,可以为硬币建立一个子类。Coin
类从 GameObject
派生。它要继承 GameObject
的属性和方法,所以 Coin
类会自动有一个 name
属性和 pickUp()
方法。Coin
类还需要一个 value
属性(这个硬币价值多少)和一个 spend()
方法(可以用这个硬币去买东西)。
class GameObject:
def __init__(self, name):
self.name = name
def pickUp(self, player):
#put code here to add the object#to the player's collection
pass
pass
class Coin(GameObject):
def __init__(self, value):
GameObject.__init__(self, "coin")
self.value = value
def spend(self, buyer, seller):
# put code here to remove the coin
# from the buyer's money and
# add it to the seller' s money
pass
revision
- 什么是对象。
- 属性和方法。
- 什么是类。
- 创建类的一个实例。
- 特殊方法:
__init__()
和__str__()
。 - 多态。
- 继承。
测试题
- 定义一个新的对象类型时用什么关键字?
- 什么是属性?
- 什么是方法?
- 类和实例之间有什么区别?
- 方法中实例引用通常用什么关键字?
- 什么是多态?
- 什么是继承?
动手试一试
- 为
BankAccount
建立一个类定义。它应该有一些属性,包括账户名(一个字符串)、账号(一个字符串或整数)和余额(一个浮点数),另外还要有一些方法显示余额、存钱和取钱。 - 建立一个可以挣利息的类,名为
InterestAccount
。这应当是BankAccount
的一个子类(所以会继承BankAccount
的属性和方法)。InterestAccount
还应当有一个对应利息率的属性,另外有一个方法来增加利息。为了力求简单,假设每年会调用一次addInterest()
方法计算利息并更新余额。