学习自定义函数之前,需要了解编程中的抽象。 抽象除了可以避免重复,更重要的点在于:抽象是程序能够被理解的关键。 计算机本身喜欢具体而明确的指令,但人通常是不行的。可以通过抽象的方式让一个程序变得容易理解,例如以下的伪代码:
1 2 3 4 page = download_page() freqs = compute_frequencies(page) for word, freq in freqs: print(word, freq)
通过阅读上述伪代码即可知程序的用途,但实际操作的具体细节是独立在定义的函数中的。
自定义函数 函数执行特定的操作并返回一个值(并不是所有的函数都会返回值),通过函数的名称可以进行调用(调用时可能需要提供一些参数,参数放在圆括号中)。 判断某个对象是否可以被调用,可以使用内置函数 callable :
1 2 3 4 5 6 7 callable(print) True callable(list) True x = 1 callable(x) False
使用 def 语句来定义自定义函数:
1 2 3 4 5 def hello(name): return ('Hi, ' + name + '!') print(hello('imxcai')) Hi, imxcai!
给自定义函数编写文档 给自定义函数写文档,通常在 def 语句后面添加独立的字符串,以便他人能够理解。
1 2 3 4 5 6 7 8 9 def square(x): 'Calculates the square of the number x.' return x * x help(square) Help on function square in module __main__: square(x) Calculates the square of the number x.
也可以通过自定义函数中的特殊属性 __doc__ 进行查看:
1 2 square.__doc__ 'Calculates the square of the number x.'
不是函数的函数 Python 中的有些函数什么都不返回。什么都不返回的函数不包含 return 语句,或者 return 语句后面没有指定值。
1 2 3 4 5 6 7 def test(): print('this is printed') return print('this is not print') test() this is printed
此处的 return 只是为了结束函数,
1 2 3 4 5 6 7 x = test() this is printed x type(x) <class 'NoneType'> print(x) None
因为函数没有返回值,所以 x 的值为空。
参数 定义函数时,函数内部使用的参数从何而来? 编写函数旨在为当前程序提供服务,对应的职责是确保它在提供正确参数时完成任务,并在参数不对时提示错误。
修改参数 函数通过参数获得了一系列的值,但是在函数内部赋值对外部没有任何影响:
1 2 3 4 5 6 7 def test(x): x = 1000 x = 10 test(x) x 10
参数存储在局部作用域内。
字符串和元组是不可变的,意味着不能修改,只能替换为新值,如果参数是可变的,例如列表:
1 2 3 4 5 6 7 def change(x): x.append(1000) x = [1,2,3] change(x) x [1, 2, 3, 1000]
将同一个列表赋给两个变量时,这两个变量将同时指向这个列表。 要避免这样的结果,必须创建列表的副本:
1 2 3 4 a = [1,2,3] change(a[:]) a [1, 2, 3]
为什么要修改参数 使用函数来修改数据结构是能提供程序的抽象程度。抽象的关键在于隐藏所有的操作细节。 参考示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def init(data): 'init a new dict to store name info.' data['first'] = {} data['middle'] = {} data['last'] = {} def lookup(data, label, name): 'lookup name info' return data[label].get(name) def store(data, full_name): 'store name in data dict' names = full_name.split() if len(names) == 2: names.insert(1, ' ') labels = 'first', 'middle', 'last' for label, name in zip(labels, names): #print(label) people = lookup(data, label, name) if people: people.append(full_name) else: data[label][name] = [full_name]
如果参数是不可变的 在 Python 中,没有办法直接在函数内部赋值来影响函数外部的变量。 这种情况下可以从函数返回所需要的值,如果是多个值使用元组的方式:
1 2 3 4 5 6 7 def foo(x): return x + 1 x = 10 x = foo(x) x 11
如果一定要修改参数,另一种方式就是使用列表或字典结构:
1 2 3 4 5 6 7 data = {'name': 'imxcai', 'blog': 'imxcai.com'} def change(d, k, v): d[k] = v change(data, 'blog', 'www.imxcai.com') data {'name': 'imxcai', 'blog': 'www.imxcai.com'}
关键字参数和默认值 前面使用的参数都是位置参数,位置参数最重要的就是位置:
1 2 3 4 5 6 7 8 9 10 def test_1(a, b): print('{}, {}!'.format(a, b)) def test_2(b, a): print('{}, {}!'.format(b, a)) test_1('hi', 'imxcai') hi, imxcai! test_2('hi', 'imxcai') hi, imxcai!
但是参数很多时,参数排列的顺序就很难记忆,可指定参数的名称,这种参数称为关键字参数:
1 2 3 4 5 6 7 def test_3(greeting, name): print('{}, {}!'.format(greeting, name)) test_3(greeting='hi', name='imxcai') hi, imxcai! test_3(name='imxcai', greeting='hi') hi, imxcai!
此时参数是根据名称来调用的,跟顺序没有关系,同时在创建函数时可以赋予默认值:
1 2 3 4 5 6 7 8 9 10 11 12 13 def test_4(greeting='hi', name='imxcai'): print('{}, {}!'.format(greeting, name)) test_4() hi, imxcai! test_4('Hello') Hello, imxcai! test_4(name='bob') hi, bob! test_4(greeting='hi', name='bob') hi, bob! test_4('hello', 'bob') hello, bob!
指定默认参数后,调用函数可以一个参数也不传,也可以按照位置传递参数,当只传递 name 时,需要通过名称指定,非常灵活。
收集参数 允许用户提供任意数量的参数很有用,在 Python 中,在函数定义时,参数前面添加一个星号就可以实现:
1 2 3 4 5 6 7 def print_params(*params): print(params) print_params(1) (1,) print_params(1,2,3) (1, 2, 3)
参数前面的星号将提供的所有值(包括一个)都放在一个元组中。 星号可以收集余下位置的参数,如果没有可供收集的参数,将返回一个空元组:
1 2 3 4 5 6 7 8 9 def print_info(title, *params): print(title, params) print_info('this is title', 1,2,3) this is title (1, 2, 3) print_info('title', 1) title (1,) print_info('title') title ()
带星号的参数放在开头、中间、末尾都是一样的。
星号不会收集关键字参数:
1 2 3 4 5 print_info('title',1,2,3, z=4) Traceback (most recent call last): File "<pyshell#110>", line 1, in <module> print_info('title',1,2,3, z=4) TypeError: print_info() got an unexpected keyword argument 'z'
使用两个星号来收集关键字参数,得到是一个字典而不是元组:
1 2 3 4 5 6 7 def print_info2(title, *para, **key): print(title, para, key, sep='\n') print_info2('This is titile', 1,2,3, z=4) This is titile (1, 2, 3) {'z': 4}
使用收集参数为前面的程序做修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def init(data): 'init a new dict to store name info.' data['first'] = {} data['middle'] = {} data['last'] = {} def lookup(data, label, name): 'lookup name info' return data[label].get(name) def store(data, *full_names): 'store name in data dict' for full_name in full_names: names = full_name.split() if len(names) == 2: names.insert(1, ' ') labels = 'first', 'middle', 'last' for label, name in zip(labels, names): #print(label) people = lookup(data, label, name) if people: people.append(full_name) else: data[label][name] = [full_name] storage = {} init(storage) store(storage, 'Geg Tom', 'Hanna Withe') print(storage)
这样可以一次性存储多个名字。
分配参数 在定义参数时使用一个或两个星号来收集参数,在调用参数时 使用运算符星号来分配参数。
1 2 3 4 5 x = (1,2) def foo(x, y): return x+y foo(*x) 3
也可以将字典中的值分配给关键字参数:
1 2 3 4 5 def test_3(greeting, name): print('{}, {}!'.format(greeting, name)) test_3(**data) hi, imxcai!
只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才能发挥作用。
练习使用参数 参数有很多提供和接受方式,以下是一个综合示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 result = [] i = start while i < stop: result.append(i) i += step return result
使用上述函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 print(story(job='writter', name='bob')) Once upon a time, there was a writter called bob. print(story(**params)) Once upon a time, there was a language called python. del params['job'] print(story(job='stroke of genius', **params)) Once upon a time, there was a stroke of genius called python. power(2,3) 8 power(2,3,4) Received redundant parameters: (4,) 8 power(x=2, y=3) 8 params = (5,) * 2 params (5,5) power(*params) 3125 power(3,3, 'hello world') Received redundant parameters: ('hello world',) 27 interval(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] interval(1,5) [1, 2, 3, 4] interval(1,12,4) [1, 5, 9] power(*interval(3, 7)) Received redundant parameters: (5, 6) 81
作用域 内置函数 vars 返回一个字典,这个字典称为命名空间或作用域。
1 2 3 4 x = 1 scope = vars() scope['x'] 1
除全局作用域外,每个函数调用都将创建一个作用域。 在函数内部使用的变量称为局部变量,不影响全局变量,因此参数与全局变量同名不会有任何问题。
要在函数中读取全局变量,如果仅仅是读取不重新关联,通常不会引发问题:
1 2 3 4 5 6 def combine(para): return para + external external='world' print(combine('hello')) helloworld
如果局部变量与全局变量同名,就无法直接访问全局变量,因为全局变量会被局部变量遮盖,如有需要,可以使用 globals 来访问全局变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 def combine(para): external = 'word' return para + external external = 'world' print(combine('hello')) helloword def combine_global(para): return para + globals()['external'] print(combine_global('hello',)) helloworld
在函数内部给变量赋值时,该变量默认为 局部变量,可以通过 global 关键字告诉 Python 这是全局变量:
1 2 3 4 5 6 7 8 x = 1 def change_global(): global x x += 1 change_global() x 2
作用域嵌套 Python 函数可以嵌套,可将一个函数放置到另一个函数中:
1 2 3 4 def foo(): def bar(): print('Hello world!') bar()
使用一个函数来创建另一个函数:
1 2 3 4 def multiplier(factor): def multiplyByFactor(number): return number * factor retrun multilyByFactor
外面的函数返回的是里面的函数,而不是调用内部的函数,重要的是,返回的函数能够访问其定义所在的作用域,每个返回的函数携带着自己所在的环境。
1 2 3 4 5 6 7 double = multiplier(2) double(5) 10 triple = multiplier(3) triple(5) 15
multiplyByFactor 这样存储其所在作用域的函数称为闭包 。
递归 函数可以调用其它函数,递归就是函数调用函数本身。 递归函数通常包含以下两部分:
基线条件:满足这种条件时函数将直接返回一个值(结束条件)
递归条件:包含一个或多个调用,这些调用旨在解决问题的一部分
关键是将问题分解为小问题,可避免递归没完没了,因为问题终将被分解为成基线条件可以解决的最小问题。
案例一:阶乘 计算数字 n 的阶乘为 n*(n-1)*(n-2)...*1,可用循环的方式解决:
1 2 3 4 5 6 7 8 def factorial(n): result = n for i in range(1, n): result *= i return result print(factorial(10)) 3628800
使用递归的方式,可以考虑基线条件和递归条件:
基线条件: 1 的阶乘为 1
递归条件:对于大于1的数字n,其阶乘为 n-1 的阶乘再乘以 n
1 2 3 4 5 6 7 8 def factorial(n): if n==1: return 1 else: return n * factorial(n - 1) factorial(10) 3628800
函数调用 factorial(n) 和 factorial(n-1) 是不同的实体。 大多数情况下循环的效率可能较高,但使用递归的可读性更高。
案例二:二分查找 二分查找算法引出了递归定义和实现,分析递归两部分:
基线条件:如果上限和下限相同,就表明都指向数字所在的位置,只需将数字返回即可
递归条件:否则找出区间的中间位置,再确定数字是在左半部分还是右半部分,然后再从这半部分中查找
二分查找的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 result = [] i = start while i < stop: result.append(i) i += step return result
测试:
1 2 3 4 5 6 7 8 seq = [34, 65,78,123,4,100,95] seq.sort() seq [4, 34, 65, 78, 95, 100, 123] search(seq, 95) 4 seq[4] 95
评论· · · · · ·