* ,MMM8&&&. *
MMMM88&&&&& .
MMMM88&&&&&&&
* MMM88&&&&&&&&
MMM88&&&&&&&&
'MMM88&&&&&&'
'MMM8&&&' *
|\___/| /\___/\
) ( ) ~( . '
=\ /= =\~ /=
)===( ) ~ (
/ \ / \
| | ) ~ (
/ \ / ~ \
\ / \~ ~/
jy__/\_/\__ _/_/\_/\__~__/_/\_/\_/\_/\__ww
| | | |( ( | | | )) | | | | | |
| | | | ) ) | | |//| | | | | | |
| | | |(_( | | (( | | | | | | |
| | | | | | | |\)| | | | | | |
| | | | | | | | | | | | | | |
如何将语句组合成函数,这让你能够告诉计算机如何完成任务,且只需说一次,无需反复向计算机传达详细指令。
6.1 懒惰是一种美德
定义函数,在需要时直接调用,这就是以抽象的方式完成功能的实现。比如:
print(sum_1_add_1)
函数“sum_1_add_1” 实现了计算 “1+1” 的计功能,通过函数对其进行抽象,可以增强程序的可视化和阅读性。
6.2 抽象和结构
比如,下载网页、计算使用频率、打印每个单词的使用频率,将上述步骤简单描述转换为一个Python程序为:
page = dowmload_page()
freqs = compute_frequencies(page)
for word,freq in freqs:
pring("{} 的频率为 {}:".format(word,freq))
看到这些代码,任何人都知道这个程序是做什么的。至于这些操作的具体细节,将在独立的函数定义中给出。
6.3 自定义函数
函数执行特定的操作并返回一个值,你可以调用它(可能需要提供参数)。要判断每个对象是否可调用,可使用内置函数callable。
import math
x = 1
y = math.sqrt
print(callable(x))
print(callable(y))
[Out]:False
[Out]:True
使用 def 定义函数(斐波那契数列)
def fibs(num):
result = [0,1]
for i in range(num-2):
result.append(resule[-2] + result[-1])
return result
[In] :fibs(5)
[Out]:[0,1,1,2,3]
6.3.1 给函数编写文档
给函数编写文档,以确保其他人能够理解,可添加注释。也可在def语句后面添加独立的字符串作以注释。这个字符串称为文档字符串(docstring)。
def square(x):
'对输入值进行平方运算'
return x*x
还可以像下面这样访问文档字符串:
[In] :square.__doc__
[Out]:'对输入值进行平方运算'
特殊的内置函数 help,可以获取有关函数的信息,包括文档字符串
[In] :help(square)
[Out]:help on function square in module __main__:
square(x)
对输入值进行平方运算
6.3.2 其实并不是函数的函数
def test():
print('This is printed')
return
print('This is not')
[In] :x = test()
[Out]:This is printed
上面的代码中,return 返回了一个空值,跳过了下面的print语句。
print(x)
[Out]:None #返回空值
6.4 参数魔法
6.4.1 值从哪里来
在 def 语句中,位于函数名后面的变量通常称为形参,而调用函数时提供的值称为实参,我们将实参称为值。
6.4.2 我能修改参数吗
在函数内部给参数复制对外部没有任何影响。
name = 'Ling Yu'
def try_to_change(n):
name = 'Wei Wu'
[In] :try_to_change(name)
name
[Out]:'Ling Yu'
在 try_to_change 内,将新值赋给了函数内部参数 name ,但是并没有对外部变量name造成影响。因为这是两个完全不同的变量。传递并修改参数的效果类似下面。
name = 'Ling Yu'#外部
name = name #内部=外部
name = 'Wei Wu' #内部
name #外部
[Out]:'Ling Yu'
结果显而易见,内部参数name变了,但外部变量name没变。同样,在函数内部重新关联参数(即给它赋值)时,函数外部的变量不受影响。这是因为参数存储在局部作用域内,即函数内部的参数,跳出函数后就不在作用。
字符串(以及数和元组)是不可变的。但如果参数为可变的数据结构(比如列表)呢?
def change(n):
n[0] = 'Gouzi Er'
names = ['Wei Wu','Ling Yu']
change(names)
print(names)
[Out]:['Gouzi Er','Ling Yu']
在这个示例中,也在函数内部修改了参数。但是,在前一个示例中,只是给局部变量赋予了新值,并没有影响到外部变量;而在这里,修改了关联到的列表。可以不用函数,作如下表示。
names = ['Wei Wu','Ling Yu']
n = names = ['Wei Wu','Ling Yu']
n[0] = 'Gouzi Er'
n = names = ['Gouzi Er','Ling Yu']
因为内部参数 n 和外部变量 names 都指向同一个列表,函数 change 对列表进行了修改。若想避免这种情况,就需要创建列表的副本。操作为对整个列表进行切片,进而得到列表的副本。
names = ['Wei Wu','Ling Yu']
n = names[:]
现在n和names包含两个相等但不同的列表。
n == names
[Out]:True
n is names
[Out]:False
现在,我们像在函数中一样修改参数n,将不会影响外部变量names:
names = ['Wei Wu','Ling Yu']
n = names[:] = ['Wei Wu','Ling Yu']
n[0] = 'Gouzi Er'
n = ['Gouzi Er','Ling Yu']
names = ['Wei Wu','Ling Yu']
我们尝试结合使用这种技巧和函数change
change(names[:])
names
[Out]:['Wei Wu','Ling Yu']
1 为何要修改参数?
在提高程序的抽象程度方面,使用函数来修改数据结构是一种不错的方式。加入你要编写一个程序,让它存储姓名,并让用户能够根据名字、中间名或姓找人。为此,可以使用一个类似于下面的数据结构。
storage = {}
storage['first'] = {}
storage['middle'] = {}
storage['last'] = {}
# Like: storage['last']['Wu'] = 'Wei Wu'
数据结构 storage 是一个字典,包含“first”、“middle”和“last”三个键,值为子字典,用于存储完整姓名。可以如下实现我们上述的功能。
#初始化数据结构
def init(data):
data['first'] = {}
data['middle'] = {}
data['last'] = {}
return data
# 根据键和名字字段查找 name
def lookup(data,key,part_name):
return data[key].get(part_name) # 返回字段对应的full_name列表
# 编写将人员存储在到数据结构中的函数
def store(data, full_name):
names = full_name.split() #英文名,例如 Wei Wu,以空格划分字段
if len(names) == 2:
names.insert(1,' ') # 两个字段的名字,middle字段以空格填充
keys = 'first','middle','last'
for key , part_name in zip(keys,names):
people = lookup(data, key, part_name)
# 已有该字段,将 full_name 加入该字段下的列表中
if people:
people.append(full_name)
# 等同 data[key][part_name].append(full_name)
# 若该字段下为空,将该 full_name 加入列表
else:
data[key][part_name] = [full_name]
测试:
myname = {}
init(myname)
store(myname,'Wei Wu')
store(myname,'Gou Zi Wu')
lookup(myname,'last','Wu')
[Out]:['Wei Wu', 'Gou Zi Wu']
其中,
people = lookup(data,label,name)
if people:
people.append(full_name)
people 指向 name 下的列表,因此对people append元素,即是对data[laebl][name]下 append 元素。
2 如果参数时不可变的
在 Python 中,只能修改参数对象本身,而无法给参数赋值并让这种修改影响函数外部的变量(C++,Pascal和Ada),但如果参数不可变,应从函数返回所有需要的值。比如:
# 编写将变量的值加1的函数
def inc(x):
return x + 1
#
def inc(x):
x[0] = x[0] + 1
清晰的解决方案是返回修改后的值
6.4.3 关键字参数和默认值
前面使用的参数都是位置参数,比如:
def hello(b,a):
print('{},{}!'.formt(a,b))
[In] :hello('hello','word')
[Out]:word hello!
# 顺序搞错了,应该按照下面的顺序输入参数
[In] :hello('word','hello')
[Out]:hello word!
我们发现,如果不为参数指定名字,单凭借参数在代码中的为位置去输入参数,将会带来相当大的麻烦。因此,有如下方式:
def hello(a = 'hello', b = 'word'):
print('{},{}!'.format(a,b))
在这里,参数的顺序无关紧要,只要给对应的参数赋值就可以了。这些使用名称制定的参数称为关键字参数。当函数中参数很多时,可以使每个参数的作用清晰明了。同时还可以指定参数的默认值。比如上面的 a 的默认值就是‘hello’。
6.4.4 收集参数
def print_params(*params):
print(params)
[In] :print_params(1,2,3)
[Out]:(1,2,3)
我们发现,参数params前面的星号将提供的所有值都放在一个元组中,也就是将这些值收集起来。
但星号不会收集关键字参数。比如:
def print_params(*y):
print(y)
[In] :print_params(x=7)
[Out]:报错
上面之所以会报错,是因为没有收集到关键字参数。要收集关键字参数,可以使用两个星号。
def print_params(**y):
pring(y)
[In] :print_params(x=1,y=2)
[Out]:{'x':1,'y':2}
得到了一个字典,而不是元组
结合收集参数,我们可以对前面姓名存储的函数 store 进行改变
#原代码
# 编写将人员存储在到数据结构中的函数
def store(data, full_name):
names = full_name.split() #英文名,例如 Wei Wu,以空格划分字段
if len(names) == 2:
names.insert(1,' ') # 两个字段的名字,middle字段以空格填充
keys = 'first','middle','last'
for key , part_name in zip(keys,names):
people = lookup(data, key, part_name)
# 已有该字段,将 full_name 加入该字段下的列表中
if people:
people.append(full_name)
# 等同 data[key][part_name].append(full_name)
# 若该字段下为空,将该 full_name 加入列表
else:
data[key][part_name] = [full_name]
# 修改后的代码
def store(data, *full_name):
for full_name in full_names:
names = full_name.split() #英文名,例如 Wei Wu,以空格划分字段
if len(names) == 2:
names.insert(1,' ') # 两个字段的名字,middle字段以空格填充
keys = 'first','middle','last'
for key , part_name in zip(keys,names):
people = lookup(data, key, part_name)
# 已有该字段,将 full_name 加入该字段下的列表中
if people:
people.append(full_name)
# 等同 data[key][part_name].append(full_name)
# 若该字段下为空,将该 full_name 加入列表
else:
data[key][part_name] = [full_name]
可以输入多个full_name参数,不用像前面一样调用多次函数。
6.4.5 分配参数
# 分配参数-元组
def add(x,y):
pring(x+y)
params = (1,2)
[In] :add(*params)
[Out]:3
# 分配参数-字典
def hello(a = 'hello',b = 'word'):
print('{},{}!'.format(a,b))
params = {'a':Love you,'b':Ling yu}
[In] :hello(**params)
[Out]:Love you,Ling yu!
在编程中,尽量不用这种方式!会挨打的!!!
6.4.6 练习使用参数
def story(**kwds):
return 'Once upon a time,there was a {job} called {name}.'.format_map(kwds)
#[In] :story(job = '职业',name = '程序猿')
#[Out]:'Once upon a time,there was a 职业 called 程序猿.'
#[In] :params = {'job':'汪','name':'单身狗'}
story(**params)
#[Out]:'Once upon a time,there was a 汪 called 单身狗.'
def power(x,y,*others):
if others:
print('Received redundant parameters:',others)
return pow(x,y)
#[In] :power(2,3)
#[Out]:8
#[In] :power(2,2,'hello word!')
#[Out]:Received redundant parameters: ('hello word!',)
4
def interval(start, stop = None, step = 1):
'Imitates range() for step > 0'
if stop is None:
start,stop = 0, start
result = []
i = start
while i < stop:
result.append(i)
i += step
return result
#[In] :interval(5)
#[Out]:[0,1,2,3,4]
#[In] :power(*interval(5))
#[Out]:Received redundant parameters: (2,3,4)
0
6.5 作用域
变量到底是什么?可以将其视为指向值的名称当执行赋值语句 x=1 后,名称 x 指向值 1。这与使用字典时一样(键指向值)。有一个名为 vars 的内置函数,它返回了一个看不见的字典。
In [31]: x = 1
In [32]: scope = vars()
In [33]: scope['x']
Out[33]: 1
警告:不应修改 vars 返回的字典,根据官方文档的说法,这样做的结果时不确定的。
这种“看不见的字典”称为命名空间或作用域。
# 局部变量
In [1]: def foo():x=42
In [2]: x=1
In [3]: foo()
In [4]: x
Out[1]: 1
局部变量不会影响全局变量。因此,局部变量可以与全局变量重名。
可以在函数中访问全局变量,但是不建议使用,因为容易导致 BUG。示意如下:
# 在函数中访问全局变量
In [1]: def test(a):
...: print(a + b)
In [2]: b = ' Oh!'
In [3]: test('Wow')
Out[1]: Wow Oh!
# 若函数内有一个同全部变量同名的局部变量,出现遮盖问题,无法直接调用。
In [1]: a = ' Oh!'
In [2]: def test(a):
...: print(a + globals()['a'])
In [3]: test('Wow')
Out[1]: Wow Oh!
重新关联全局变量,告诉函数这个变量是全局变量,.
# 通过函数修改全局变量
In [51]: x = 1
In [52]: def change_global():
...: global x
...: x += 1
In [53]: change_global()
In [54]: x
Out[54]: 2
作用域嵌套
# 赋值顺序从外向里
In [57]: def mutiplier(factor):
...: def mutiplier_by_factor(number):
...: print('{} * {} = {}'.format(number,factor, number*factor))
...: return mutiplier_by_factor
In [58]: mutiplier(4)(6)
6 * 4 = 24
6.6 递归
什么是递归?如下解释。
递归[名词]:参见“递归”
def 递归:
return 递归
6.6.1 两个经典案例:阶乘和幂
# 阶乘 n个人排成一队有多少种方式,# n * n-1 * n-2 * ... * 1
#普通方法
In [61]: def factorial(n):
...: result = n
...: for i in range(n-1,0,-1):
...: result *= i
...: return result
In [68]: factorial(5)
Out[68]: 120
# 用递归的方法
# 如果n==1,那么直接返回1
# 如果n>1,那么阶乘为n-1的阶乘*n
In [67]: def factorial(n):
...: if n == 1:
...: return 1
...: else:
...: return n * factorial(n-1)
In [68]: factorial(5)
Out[68]: 120
# 幂
#普通方法
In [69]: def power(x,n):
...: result = 1
...: for i in range(n):
...: result *= x
...: return result
In [70]: power(2,3)
Out[70]: 8
# 递归方法
# 如果n==0,那么直接返回1
# 如果n>1,那么为power(x,n-1)与x的乘积
In [71]: def power(x,n):
...: if n == 0:
...: return 1
...: else:
...: return x*power(x,n-1)
In [72]: power(2,3)
Out[72]: 8
6.6.2 另一个经典案例:二分查找
比如,给定[1,1000]范围擦价格:
(1)是500吗?
(2)不是 → 比500大还是小?
(3)小 → 是 250 吗?
(4) ……
我们用这种思想来从序列中查找目标值位置。
有序列 sequence = [1,3,6,7,15,21,30],目标值为21,位置索引为 5
(1)上下限为sequence长度和0
(2)用上面的思想找到 21 的位置
In [81]: def search(squence,number,lower=0,upper=None):
...: if upper == None:
...: upper = len(squence) - 1
...: if lower == upper:
...: assert number == squence[upper]
...: return number
...: else:
...: middle = (lower + upper) // 2
...: if number > squence[middle]:
...: return search(squence,number,middle+1,upper)
...: elif number < squence[middle]:
...: return search(ssquence,number,lower,middle)
...: else: return middle
In [82]: a = [1,3,6,7,15,21,30]
In [83]: search(a,21)
Out[83]: 5
函数式编程
#(1) map(function,list),将list传递给函数 function,返回函数结果
list(map(str,range(10)))
等价于
[str(i) for i in range(10)]
#(2) filter(function,list),将list传递给函数function,根据布尔返回值对list中元素进行过滤
# Python isalnum() 方法检测字符串是否由字母和数字组成
In [94]: def func(x):
...: return x.isalnum()
In [95]: seq = ['foo','x41','>!','$&*']
In [96]: list(filter(func,seq))
Out[96]: ['foo', 'x41']
#用列表推导替换
In [97]: [x for x in seq if x.isalnum()]
Out[97]: ['foo', 'x41']
#(3) reduce()传入的函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f。将每次函数结果当作第一个参数,list下一个元素作第二个参数,并返回最终结果值。若接收3个参数,则第三个参数为初始值,此时只调用list中的1个元素。
# (1+2)+3
In [104]: from functools import reduce
In [105]: numbers = [1,2,3]
In [106]: reduce(lambda x,y : x+y, numbers)
Out[106]: 6
#若有第三个参数为 100, 则计算为: ( (100+1)+2 )+3
In [107]: reduce(lambda x,y : x+y, numbers,100)
Out[107]: 106