抽象
抽象和结构
给你一个任务,对1、2、3… 、n之间的数求和,这个问题一个循环就可以解决。
>>> for i in range(1, n+1):
sum += i
这很简单,但是如果需要多次使用呢?如果按照上面那样,代码就会很乱。
>>> for i in range(1, n+1):
sum += i
.....
>>> for i in range(1, n+1):
sum += i
.....
>>> for i in range(1, n+1):
sum += i
.....
这样代码会很多,另外也有大量重复的代码,很没有必要。那么真正的程序员会怎么做呢?
>>> sum(1,n)
.....
>>> sum(1,n)
.....
>>> sum(1,n)
.....
上面这种形式就是抽象,只具体编写了这个程序独特的部分(获得求和范围并打印结果)。这样在需要在多个地方计算求和时,这样做可节省很多精力。
抽象可节省人力,但实际上还有个更重要的优点: 抽象是程序能够被人理解的关键所在(无论对编写程序还是阅读程序来说,这都至关重要。)Python中提供了抽象这种“功能”,也就是函数。
自定义函数
def语句
函数执行特定的操作并返回一个值(也可不返回),调用的时候提供参数(可没有)。可以使用内置函数callable判断某个对象是否可调用。
>>> import math
>>> x =1
>>> y = math.sqrt
>>> callable(x)
False
>>> callable(y)
True
那么如何定义函数呢,Python中使用def(define)语句定义函数。下面是之前求和函数的定义。
>>> def sum(start, end):
sum = 0
for i in range(start, end+1):
sum += i
return sum
sum函数有两个参数,分别是求和范围的开始和结束。求得结果后,使用return语句返回结果。return语句可以结束函数。
给函数编写文档
我们还可以给函数编写文档,以确保其他人能够理解,可以添加注释。或者在def语句后面添加独立的字符串。放在函数开头的字符串称为文档字符串,将作为函数的一部分存储起来。下面是一个演示:
>>> def square(x):
'Calculates the square of the number of x.'
return x * x
可以使用.doc 访问文档字符串。
>>> square.__doc__
'Calculates the square of the number of x.'
使用内置函数help可以获取有关函数的文档字符串。
>>> help(square)
Help on function square in module __main__:
square(x)
Calculates the square of the number of x.
参数魔法
关于修改参数
和c一样,Python中位于函数名后面的变量称为形参,调用函数时提供的值称为实参。
通常我们是不能修改参数的,因为作用域的问题。
>>> def try_to_change(n):
n = 'Mr. Gumby'
>>> name = 'Mr. Yang'
>>> try_to_change(name)
>>> name
'Mr. Yang'
参数并没有改变。如果参数是元组或者字符串这种不可变的参数,那么在函数中本就无法影响到参数的值。但是如果使用的是列表这种可变的数据结构,那么可能会有这种情况
>>> def change(n):
n[0] = 'Mr. Gumby'
pass
>>> names = ['Mrs.Entity', 'Mrs.Ting']
>>> change(names)
>>> names
['Mr. Gumby', 'Mrs.Ting']
列表改变了,为什么会这样呢?我们不用函数模仿一下这个函数的过程。
>>> names = ['Mrs.Entity', 'Mrs.Ting']
>>> n = names # 再次假装传递名字作为参数。
>>> n[0] = 'Mr. Gumby'
>>> names
['Mr. Gumby', 'Mrs.Ting']
可以看出,这种现象的原因是,常规复制将另一个名称关联到列表。
>>> names is n
True
若想避免这种情况,要使用深复制,创建列表的副本。
>>> names = ['Mrs.Entity', 'Mrs.Ting']
>>> n = names[:]
>>> n is names
False
>>> n[0] = 'Mr.Gumby'
>>> n
['Mr.Gumby', 'Mrs.Ting']
>>> names
['Mrs.Entity', 'Mrs.Ting']
使用这种方法将不会影响到names。因为参数n包含的是副本,所以原始列表是安全的。
>>> change(names[:])
>>> names
['Mrs.Entity', 'Mrs.Ting']
另外如果参数是不可变的,但是需要修改参数的值,应该从函数返回所有需要的值。
>>> def inc(x):
return x+1
>>> foo = 10
>>> foo = inc(foo)
>>> foo
11
关键字参数和默认值
前面学习的都是位置参数,形参和实参依次对应。位置比名称重要。举个例子
>>> def hello_1(greeting, name):
print('{}, {}!'.format(greeting, name))
>>> def hello_2(name, greeting):
print('{}, {}!'.format(name, greeting))
>>> hello_1('Hello', 'world')
Hello, world!
>>> hello_2('Hello', 'world')
Hello, world!
当参数很多时,参数的排列顺序就不是很好记了,这时候可以指定参数的名称。这样参数的顺序就无关紧要了。
>>> hello_1(greeting='Hello', name='world')
Hello, world!
像这样使用名称指定的参数称为关键字参数。可以澄清各个参数的作用。另外还可以给参赛设置默认值。
>>> def hello_3(greeting='Hello', name='world'):
print("{}, {}!".format(greeting, name))
>>> hello_3() # 关键字参数,可以指定默认值。
Hello, world!
给参数指定默认值后,调用函数时可不提供它。
>>> hello_3('Greeting')
Greeting, world!
>>> hello_3('jk')
jk, world!
>>> hello_3(name='Gumby')
Hello, Gumby!
收集参数
当参数过多时,使用函数会报错。 不过Python提供了收集函数的符号星号( * )。
>>> def print_params(*params):
print(params)
>>> print_params('Testing')
('Testing',)
前面有星号的参数将被放在元组中。
>>> print_params(1, 2, 3)
(1, 2, 3)
如果没有可供收集的参数,params将是一个空元组。
>>> print_params()
()
带星号的参数可以放在其他位置,但是和赋值时不同,这是需要使用名称来指定后续参数。
>>> def in_the_middle(x, *y, z):
print(x, y, z)
>>> in_the_middle(1, 2, 3, 4, 5, 6, z=7)
1 (2, 3, 4, 5, 6) 7
星号不会收集关键字参数。所以需要在后面指定后续参数。要收集关键字参数,可使用两个星号。
>>> def print_params_3(**params):
print(params)
>>> print_params_3(x=1, y=2, z=3)
{'x': 1, 'y': 2, 'z': 3}
这种情况得到的是一个字典而不是元组。
分配参数
*号可以收集参数,也可以分配参数。
>>> params = (1, 2)
>>> def add(x, y):
return x + y
>>> add(*params)
3
只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才能发挥作用。
下面看一个使用参数的综合实例。
def story(**kwds):
return 'Once upon a time, there was a' \
'{job} called {name},'.format_map(kwds)
def power(x, y, *others):
if others:
print('Received redundant parameters:', others)
return pow(x, y)
def interval(start, stop=None, step=1):
'Imitates range() for step > 0'
if stop is None:
start, stop = 0, start # 如果没有给参数stop指定值,就调整参数start和stop的值。
result = []
i = start # 从start开始往上数。
while i < stop: # 数到stop位置
result.append(i) # 将当前数的数附加到result末尾
i += step
return result
测试一下
>>> print(story(job='King', name='Gumby'))
Once upon a time, there was aKing called Gumby,
>>> params = (5,) * 2
>>> power(*params)
3125
>>> interval(3, 12, 4)
[3, 7, 11]
>>> interval(3)
[0, 1, 2]
作用域
每个函数调用时都将创建一个命名空间或作用域(“看不见的字典”)如果要在局部变量中访问相同名的全局变量,使用函数globals(),这个函数会返回一个包含全局变量的字典。(locals 返回一个包含局部变量的字典)
>>> def combine(parameter):
print(parameter + globals()['parameter'])
>>> parameter = 'berry'
>>> combine('Shrub')
Shrubberry
也可以使用global 告诉Python变量是全局变量。
>>> x = 1
>>> def change_global():
global x
x += 1
>>> change_global()
>>> x
2
小结
- 抽象:抽象是隐藏不必要细节的艺术。通过定义处理细节的函数,可让程序更抽象。
- 函数定义:函数是使用def语句定义的。函数由语句块组成,它们从外部接受值(参数),并可能返回一个或多个值(计算结果)
- 参数:函数通过参数(调用函数时被设置的变量)接收所需的信息。在Python中,参数有两类:位置参数和关键字参数。通过给参数指定默认值,可使其变成可选的。
- 作用域:变量存储在作用域(也叫命名空间)中。在Python中,作用域分两大类:全局作用域和局部作用域。作用域可以嵌套。
- 递归:函数可调用自身,这称为递归。可使用递归完成的任何任务都可以使用循环来完成,但有时使用递归函数的可读性更高。
- 函数式编程:Python提供了一些函数式编程工具,其中包括lambda表达式以及filter和reduce。
学习的新函数:
函数 | 描述 |
---|---|
map(func, seq[, seq, …]) | 对序列中的所有元素都执行函数 |
filter(func, seq) | 返回一个列表,其中包含对其执行函数时结果为真的所有元素 |
reduce(func, seq[, initial]) | 等价于func(func(func(seq[0], seq[1]), seq[2]), …) |
sum(seq) | 返回seq中所有元素的和 |
apply(func[, args[, kwargs]]) | 调用函数(还提供要传递给函数的参数) |
注: 小结摘抄于Python基础教程(第三版)