6.1 自定义函数
函数执行特定的操作并返回一个值1,你可以调用它。一般而言,要判断某个对象是否可调用,可使用内置函数callable。
import math
x = 1
y = math.sqrt
callable(x)
# False
callable(y)
# True
使用def来定义函数。
def hello(name):
return 'Hello, ' + name + '!'
6.1.1 给函数编写文档
在有些地方,如def语句后面(以及模块和类的开 头,这将在第7章和第10章详细介绍),添加这样的字符串很有用。放在函数开头的字符串称为 文档字符串(docstring),将作为函数的一部分存储起来。
特殊的内置函数help很有用。在交互式解释器中,可使用它获取有关函数的信息,其中包含 函数的文档字符串。
def square(x):
'Calculates the square of the number x.'
return x * x
# 访问函数文档字符串
square.__doc__
# 'Calculates the square of the number x.'
help(square)
# Help on function square in module __main__:
# square(x)
# Calculates the square of the number x.
6.1.2 其实并不是函数的函数
在Python中,有些函数什么都不返回。什么都不返回的函数不包含return语句,或者包含return语句,但没 有在return后面指定值。
def test():
print('This is printed')
return
x = test()
# This is printed
print(x)
# None
由此可知,所有的函数都返回值。如果你没有告诉它们该返回 什么,将返回None。
6.2 参数魔法
在def语句中,位于函数名后面的变量通常称为形参,而调用函数时提供的值称为实参,在很重要的情况下,我会将实参称为值
6.2.1 我能修改参数吗
函数通过参数获得了一系列的值,你能对其进行修改吗?如果这样做,结果将如何?参数不过是变量而已,行为与你预期的完全相同。在函数内部给参数赋值对外部没有任何影响。
def try_to_change(n):
n = 'Mr. Gumby'
name = 'Mrs. Entity'
try_to_change(name)
name
# 'Mrs. Entity'
在try_to_change内,将新值赋给了参数n,但如你所见,这对变量name没有影响。在函数内部重新关联参数(即给它赋值)时,函数外部的变量不受影响。参数存储在局部作用域内.
但如果参数为可变的数据结构(如列表)呢?
def change(n):
n[0] = 'Mr. Gumby'
names = ['Mrs. Entity', 'Mrs. Thing']
change(names)
names
# ['Mr. Gumby', 'Mrs. Thing']
而在这个示例中,修改了变量关联到的列表。这是因为将同一个列表赋给两个变量时,这两个变量将同时指向这个列表。 就这么简单。下面这个示例说明了这种情况。
names = ['Mrs. Entity', 'Mrs. Thing']
n = names # 再次假装传递名字作为参数 >>>
n[0] = 'Mr. Gumby' # 修改列表
names
# ['Mr. Gumby', 'Mrs. Thing']
使用函数来修改数据结构(如列表或字典)是一种不错的方式
抽象的关键在于隐藏所有的更新细节,为此可使用函数。
def init(data):
data['first'] = {}
data['middle'] = {}
data['last'] = {}
storage = {}
init(storage)
# storage
# {'first': {}, 'middle': {}, 'last': {}}
6.2.3 关键字参数和默认值
有时候,参数的排列顺序可能难以记住,尤其是参数很多时。为了简化调用工作,可指定参 数的名称。 然而,关键字参数最大的优点在于,可以指定默认值。
def hello_1(greeting, name):
print('{}, {}!'.format(greeting, name))
hello_1(greeting='Hello', name='world')
def hello_3(greeting='Hello', name='world'):
print('{}, {}!'.format(greeting, name))
hello_3()
# Hello, world!
hello_3('Greetings', 'universe')
# Greetings, universe!
6.2.4 收集参数
有时候,允许用户提供任意数量的参数很有用。为此,应允许用户提供任意数量的姓名。实际上,这实现起来并不难。
请尝试使用下面这样的函数定义:
def print_params(*params):
print(params)
print_param('Testing')
# 'Testing',
print_param('Testing','Hello')
# 'Testing','Hello'
def print_params_2(title, *params): # 因此星号意味着收集余下的位置参数。
print(title)
print(params)
# Params:
# (1, 2, 3)
与赋值时一样,带星号的参数也可放在其他位置(而不是最后),但不同的是,在这种情况 下你需要做些额外的工作:使用名称来指定后续参数。
def in_the_middle(x,*y,z):
print(x,y,z)
in_the_middle(1,2,3,4,z=6)
# 1 (2, 3, 4) 6
星号不会收集关键字参数。要收集关键字参数,可使用两个星号。
def print_params_3(**params):
print(params)
print_params_3(x=1,y=2,z=3)
# {'x': 1, 'y': 2, 'z': 3}
def print_params_4(x, y, z=3, *pospar, **keypar):
print(x, y, z)
print(pospar)
print(keypar)
print_params_4(1, 2, 3, 5, 6, 7, foo=1, bar=2)
# 1 2 3
# (5, 6, 7)
# {'foo': 1, 'bar': 2}
6.2.5 分配参数
def add(x, y):
return x + y
params = (1, 2)
add(*params)
# 3
def hello_3(greeting='Hello', name='world'):
print('{}, {}!'.format(greeting, name))
params = {'name': 'Sir Robin', 'greeting': 'Well met'}
hello_3(**params)
# Well met, Sir Robin!
6.3 作用域
在函数内使用的变量称为局部变量(与之相对的是全局变量)。参数类似于局部变量,因此参数与全局变量同名不会有任何问题。
如果有一个局部变量或参数与你要访问的全局变量同名,就无法直接访问全局变量,因为它被局部变量遮住了。必要时可使用globals()[‘parameter’]来访问它。
def combine(parameter):
print(parameter + globals()['parameter'])
parameter = 'berry'
combine('Shrub')
# Shrubberry
在函数内部给变量赋值时,该变量默认为 局部变量,除非你明确地告诉Python它是全局变量。那么如何将这一点告知Python呢?
x = 1
def change_global():
global x
x=x+1
change_global()
# x = 2
Python函数可以嵌套,即可将一个函数放在另一个函数内。
6.4 递归
通常递归包括两个部分:
基线条件(针对最小的问题):满足这种条件时函数将直接返回一个值。
递归条件:包含一个或多个调用,这些调用旨在解决问题的一部分。
# 阶乘的递归
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
# factorial(10) = 3628800
# 幂函数的递归
def power(x, n):
if n == 0:
return 1
else:
return x * power(x, n - 1)
# power(2,3) = 8
6.4.2 二分查找
这里的关键是,这种算法自然而然地引出了递归式定义和实现。先来回顾一下定义,确保你知道该如何做。
如果上限和下限相同,就说明它们都指向数字所在的位置,因此将这个数字返回。
否则,找出区间的中间位置(上限和下限的平均值),再确定数字在左半部分还是右半部分。然后在继续在数字所在的那部分中查找。
def search(sequence, number, lower=0, upper=None):
if upper is None:
upper = len(sequence) - 1
if lower == upper:
assert number == sequence[upper]
return upper
else:
middle = (lower + upper) // 2
if number > sequence[middle]:
return search(sequence, number, middle + 1, upper)
else:
return search(sequence, number, lower, middle)
seq = [34, 67, 8, 123, 4, 100, 95]
seq.sort()
search(seq, 34)
# 2