函数与函数编程#

函数#

当调用一个函数时,它的参数是按引用传递给。如果函数的实参一个可变对象(如列表或字典),则函数内对该对象的修改将会影响到函数之外。例如:

a = [1, 2, 3, 4, 5]

def foo(x):
    global b        # 声明全局变量
    b = 10
    x[3] = -33      # 修改 x 中的元素

foo(a)
print(a)
print(b)
[1, 2, 3, -33, 5]
10

如果没有指定返回对象或者 return 语句被省略,则会返回一个 None 对象。如果要返回多个值,可以通过返回一个元组或其它包含对象来完成。

带默认参数的函数#

def repeat_str(s, times=1):
    repeated_strs = s * times
    return repeated_strs

repeated_strings = repeat_str("Happy Birthday! ")
print(repeated_strings)

repeated_strings_2 = repeat_str("Happy Birthday! " , 4)
print(repeated_strings_2)
Happy Birthday! 
Happy Birthday! Happy Birthday! Happy Birthday! Happy Birthday! 

关键字参数#

def func(a, b=40, c=80):
    print('a:', a, ', b:', b, ', c:', c)

func(13, 17)
func(12, c=24) # 指定给哪个关键字赋值
func(c=40, a=80) # 指定给 a 赋值,可以把它放在后面
a: 13 , b: 17 , c: 80
a: 12 , b: 40 , c: 24
a: 80 , b: 40 , c: 40

可变长参数的函数#

  • *args 可变长度元组参数

  • **kwargs 可变长度字典参数

def var_paras(fpara, *args, **kwargs):
    print("fpara: " + str(fpara))
    print("args: " + str(args))
    print("kwargs: " + str(kwargs))
    print()

var_paras("hello1")
var_paras("hello2", 1, 3, 5)
var_paras("hello3", ele="world3")
var_paras("hello4", 1, 3, ele="world4")
fpara: hello1
args: ()
kwargs: {}

fpara: hello2
args: (1, 3, 5)
kwargs: {}

fpara: hello3
args: ()
kwargs: {'ele': 'world3'}

fpara: hello4
args: (1, 3)
kwargs: {'ele': 'world4'}

递归#

Python 对递归函数调用的次数作了限制。函数 sys.getrecursionlimit() 返回当前允许的最大递归次数,而函数 sys.setrecursionlimit() 可以改变该函数的返回值。

默认的最大递归次数为1000,当一个函数递归次数超过最大递归次数时,就会引发 RuntimeError 异常.

lambda 操作符#

lambda 语句用来创建一个匿名函数(没和名字绑定的函数):

lambda args: expression

args 是一个用逗号分隔的参数,expression 是一个调用这些参数的表达式。例如:

a = lambda x, y: x + y
print(a(2, 3))
5

lambda 定义的代码必须是一个合法的表达式。多重语句和其他非表达式语句(如 printforwhile 等)不能出现在 lambda 语句中。lambda 表达式也遵循和函数一样的作用域规则。

map() 函数#

t = map(func, s) 函数将序列 s 中的每个元素传递给 func 函数做参数, 函数的返回值组成了列表 t。 即 t[i] = func(s[i])。需要注意的是, func 函数必须有只有一个参数,例如:

a = [1, 2, 3, 4, 5, 6]
def foo(x):
    return 3 * x
b = map(foo, a)   # b = [3, 6, 9, 12, 15, 18]
b
<map at 0x7f1733633e80>

上边的例子中的函数也可以用匿名函数来创建:

b = map(lambda x: 3 * x, a)   # b = [3, 6, 9, 12, 15, 18]
b
<map at 0x7f17336333a0>

map() 函数也可以用于多个列表,如 t = map(func, s1, s2, ..., sn)。 如果是这种形式,t 中的每个元素 t[i] = func(s1[i], s2[i], ..., sn[i])func 函数的形参个数必须和列表的个数 n 相同,结果与 s1, s2, ..., sn 中的最长的列表的元素个数相同。在计算过程中,短的列表自动用 None 扩充为统一长度的列表。

如果函数 funcNone,则 func 就被当成是恒等函数处理。这样函数就返回一个包含元组的列表:

a = [1, 2, 3, 4]
b = [100, 101, 102, 103]
c = map(None, a, b)  # c = [(1,100), (2,101), (3,102), (4,103)]
c
<map at 0x7f1733631600>

zip() 函数#

上边这个例子也可以用 zip(s1, s2, ..., sn) 函数来完成。zip() 用来将几个序列组合成一个包含元组的序列,序列中的每个元素 t[i] = (s1[i], s2[i], ..., sn[i])。 与 map() 不同的是, zip() 函数将所有较长的序列序列截的和最短序列一样长:

d = [1, 2, 3, 4, 5, 6, 7]
e = [10, 11, 12]
f = zip(d, e)  # f = [(1, 10), (2, 11), (3, 12)]

reduce() 函数#

reduce(func, s) 函数从一个序列收集信息,然后只返回一个值(例如求和,最大值等)。它首先以序列的前两个元素调用函数,再将返回值和第三个参数作为参数调用函数,依次执行下去,返回最终的值。 func 函数有且只有两个参数。例如:

from functools import reduce

def sum(x, y):
    return x + y
b = reduce(sum, a)
b  # b = (((1 + 2) + 3) + 4) = 10
10

filter() 函数#

filter(func, s) 是个序列过虑器,它使用 func() 函数来过滤 s 中的元素。使 func 返回值为 false 的元素被丢弃,其它的存入 filter 函数返回的列表中,例如:

c = filter(lambda x: x < 4, a)  # c = [1, 2, 3]

如果函数 funcNone,则 func 就被当成是恒等函数处理。这样,函数就返回序列 s 中值为 True 的元素。

列表内涵#

列表内涵可以代替许多调用 map()filter() 函数的操作。列表内涵的一般形式是:

[表达式 for item1 in 序列1
    for item2 in 序列2
        ...
            for itemN in 序列N
                if 条件表达式]

上边的例子等价于:

s = []
for item1 in sequence1:
    for item2 in sequence2:
        ...
            for itemN in sequenceN:
                if condition: s.append(expression)

下面这个代码片段可以帮助你理解列表内涵:

import math
from functools import reduce

a = [-3, 5, 2, -10, 7, 8]
b = "abc"
c = [2 * s for s in a]          # c = [-6,10,4,-20,14,16]
d = [s for s in a if s >= 0]    # d = [5,2,7,8]
e = [                           # e = [(5,'a'),(5,'b'),(5,'c'),
    (x, y)                      #      (2,'a'),(2,'b'),(2,'c'),
    for x in a                  #      (7,'a'),(7,'b'),(7,'c'),
    for y in b                  #      (8,'a'),(8,'b'),(8,'c')]
    if x > 0
]

f = [(1, 2), (3, 4), (5, 6)]    # f = [2.23606, 5.0, 7.81024]
g = [math.sqrt(x * x + y * y) for x, y in f]
h = reduce(lambda x, y: x + y, [math.sqrt(x * x + y * y) for x, y in f])  # 平方根的和

提供给列表内涵的序列不必等长,因为系统内部使用嵌套的一系列 for 循环来迭代每个序列中的每个元素,然后由 if 从句处理条件表达式,若条件表达式为真,计算表达式的值并放入到列表内涵返回的序列中。 if 从句是可选的。

当使用列表内涵来构建包含元组的列表时,元组的值必须放在括号里。例如 [(x, y) for x in a for y in b] 是一个合法的语句,而 [x, y for x in a for y in b] 则不是。

最后,你应该注意在一个列表内涵中定义的变量是与列表内涵本身有同样的作用域,在列表内涵计算完成后会继续存在。例如 [x for x in a] 会覆盖内涵外先前定义的 x ,最终 x 的值会是 a 中的最后一个元素的值。

eval()、exec 和 execfile()#

eval(str [, globals [, locals ]]) 函数将字符串 str 当成有效 Python 表达式来求值,并返回计算结果。

eval("3 + 4")
7

同样地, exec 语句将字符串 str 当成有效 Python 代码来执行。提供给 exec 的代码的名称空间和 exec 语句的名称空间相同。

exec("a = 100")
a
100

最后,execfile(filename [, globals [, locals ]]) 函数可以用来执行一个文件,看下面的例子:

import os
import sys

sys.path.append(os.getcwd() + "/modules_and_packages")

execfile(r"modules_and_packages/myprogram.py")
Hey I am in some_main_script in main package.
Hey Im a function inside mysubscript

默认地,eval()execexecfile() 所运行的代码都位于当前的名字空间中。eval()execexecfile() 函数也可以接受一个或两个可选字典参数作为代码执行的全局名字空间和局部名字空间。 例如:

%%writefile ../_tmp/function_exec.py
result = 3 * x + 4 * y
print(result)

for b in birds:
    print(b)
Writing ../_tmp/function_exec.py
globals = {"x": 7, "y": 10, "birds": ["Parrot", "Swallow", "Albatross"]}
locals = {}

# 将上边的字典作为全局和局部名称空间
a = eval("3 * x + 4 * y", globals, locals)
exec("for b in birds: print(b)", globals, locals)
exec(open("../_tmp/function_exec.py").read(), globals, locals)
Parrot
Swallow
Albatross
61
Parrot
Swallow
Albatross

如果你省略了一个或者两个名称空间参数,那么当前的全局和局部名称空间就被使用。如果一个函数体内嵌嵌套函数或 lambda 匿名函数时,同时又在函数主体中使用 execexecfile() 函数时, 由于牵到嵌套作用域,会引发一个 SyntaxError 异常

注意例子中 exec 语句的用法和 eval()execfile() 是不一样的。 exec 是一个语句(就像 printwhile),而 eval()execfile() 则是内建函数。

exec(str) 这种形式也被接受,但是它没有返回值。

当一个字符串被 eval()execexecfile() 执行时,解释器会先将它们编译为字节代码,然后再执行。这个过程比较耗时,所以如果需要对某段代码执行很多次时,最好还是对该代码先进行预编译,这样就不需要每次都编译一遍代码,可以有效提高程序的执行效率。

compile()#

compile(str, filename, kind) 函数将一个字符串编译为字节代码, str 是将要被编译的字符串, filename 是定义该字符串变量的文件,kind 参数指定了代码被编译的类型('single' 指单个语句,'exec' 指多个语句,'eval' 指一个表达式)。cmpile() 函数返回一个代码对象,该对象当然也可以被传递给 eval() 函数和 exec 语句来执行,例如:

x = 7
y = 10

str1 = "for i in range(0, 10): print(i)"
c1 = compile(str1, "", "exec")  # 编译为字节代码对象
exec(c1)  # 执行

str2 = "3 * x + 4 * y"
c2 = compile(str2, "", "eval")  # 编译为表达式
result = eval(c2)  # 执行
print(result)
0
1
2
3
4
5
6
7
8
9
61