## 1. 函数 使用def语句可定义函数: ``` def add(x, y): return x + y ``` 函数体就是在调用函数时所执行的一系列语句。调用函数的方法是在函数名称后面加上参数。参数的顺序必须与函数定义匹配,否则会引发TypeError异常。可以为函数的参数设置默认值,例如: ``` def split(line, delimiter=','): statements ``` 如果给最后一个参数名加上星号"*",函数就可以接受任意数量的参数: ``` def fprintf(file, fmt, *args): file.write(fmt % args) fprintf(out, "%d %s %f", 42, "hello world", 3.45) ``` 在这个例子中,所有余下的参数都作为一个元组放入args变量。要把元组args当作参数传递给函数,可以在函数调用中使用*args语法。例如: ``` def printf(fmt, *args): fprintf(sys.stdout, fmt, *args) ``` 提供函数参数还有一种方式,即显示地命名每个参数并为其指定一个值,这称为关键字参数,例如: ``` def foo(w, x, y, z): statements foo(x=3, y=22, w='hello', z=[1, 2]) ``` 使用关键字参数时,参数的顺序无关紧要。但除非提供了默认值,否则必须显式地命名所有必需的函数参数。位置参数和关键字参数可以同时使用,前提是所有位置参数必须先出现,给所有非可选参数提供值,例如: ``` foo('hello', 3, z=[1, 2], y=22) ``` 如果函数定义的最后一个参数以"\**"开头,可以把所有额外的关键字参数都放入一个字典中,并把这个字典传递给参数。例如: ``` def make_table(data, **params): fgcolor = params.pop("fgcolor", "black") bgcolor = params.pop("bgcolor", "white") width = params.pop("width", None) if params: raise TypeError("Unsupported configuration options %s" % list(params)) make_table(items, fgcolor="black", bgcolor="white", border=1, borderstyle="grooved", cellpoadding=10, width=400) ``` 关键字参数和可变长度参数列表可以一起使用,只要"\**"参数出现在最后即可,例如: ``` def spam(*args, **kwargs): statements ```   ## 2. 参数传递与返回值 调用函数时,函数参数仅仅是引用传入对象的名称。参数传递的基本语义和其他编程语言中已知的方式不完全相同,如“按值传递”和“按引用传递”。比如传递不可变的值,参数看起来实际是按值传递的,如果传递的是可变对象(如列表或字典)给函数,然后再修改此可变对象,这些改动将反映在原始对象中。例如: ``` a = [1, 2, 3, 4, 5] def square(items): for i, x in enumerate(items): items[i] = x * x square(a) # a = [1, 4, 9, 16, 25] ``` return语句从函数返回一个值。如果没有指定任何值或者省略return语句,就会返回None对象。如果返回值有多个,可以把它们放在一个元组中,例如: ``` def factor(a): d = 2 while (d <= (a / 2)): if ((a / d) * d == a): return ((a / d), d) d = d + 1 return (a, 1) ```   ## 3. 作用域规则 每次执行一个函数时,就会创建新的局部命名空间。该命名空间代表一个局部环境,其中包含函数参数的名称和在函数体内赋值的变量名称。解析这些名称时,解释器将首先搜索局部命名空间。如果没有找到匹配的名称,它就会搜索全局命名空间。如果在全局命名空间中也找不到匹配值,最终会检查内置命名空间。如果仍然找不到,就会引发NameError异常。 命名空间的特性之一是在函数中对全局变量的操作,例如: ``` a = 42 def foo(): a = 13 foo() # a仍然是42 ``` 执行这段代码时,尽量在函数foo中修改了变量a的值,但最终a仍然是42.在函数中对变量进行赋值时,这些变量始终绑定到该函数的局部命名空间中,因此函数体中的变量a引用的是一个包含值13的全新对象,而不是外部的变量。使用global语句可以改变这种行为,例如: ``` a = 42 def foo(): global a a = 13 foo() # a的值已变13 ``` Python支持嵌套的函数定义,例如: ``` def countdown(start): n = start def display(): print('T-minus %d' % n) while n > 0: display() n -= 1 ``` 使用静态作用域绑定嵌套函数中的变量,即解析名称时首先检查局部作用域,而后由内向外一层层检查外部嵌套函数定义的作用域。如果找不到匹配,最后将搜索全局命名空间和内置命名空间。可以使用nonlocal语句绑定外部变量,例如: ``` def countdown(start): n = start def display(): print('T-minus %d' % n) def decrement(): nonlocal n n -= 1 while n > 0: display() decrement() ``` nonlocal声明不会把名称绑定到任意函数中定义的局部变量,而是搜索当前调用栈中的下一层函数定义,即动态作用域。例如: ``` i = 0 def foo(): i = i + 1 # UnboundLocalError异常 ``` 尽管有一个全局变量i,但它不会给局部变量i提供值。函数定义时就确定了变量是局部的还是全局的,而且在函数中不能突然改变它们的作用域。   ## 4. 函数对象与闭包 函数在Python中是第一类对象。即可以把它们当作参数传递给其他函数,放在数据结构中,以及作为函数的返回结果。例如: ``` def callf(func): return func() ``` 把函数当作数据处理时,它将显式地携带与定义该函数的周围环境相关的信息。这将影响到函数中自由变量的绑定方式。例如: ``` # foo.py x = 42 def callf(func): return func() # main.py import foo x = 37 def helloworld(): reutrn "x is %d" % x foo.callf(helloworld) # x is 37 ``` 在上例中,即使foo.py中也定义了一个变量x,变际调用的是与helloworld()函数相同的环境中定义的值。将组成函数的语句和这些语句的执行环境打包在一起时,得到的对象称为闭包。事实上所有函数都拥有一个指向了定义该函数的全局命名空间的\__globals\__属性。例如: ``` def page(url): def get(): return urlopen(url).read() return get python = page("http://www.python.org") jython = page("http://www.jython.org") pydata = python() # 获取http://www.python.org jydata = jython() # 获取http://www.jython.org ```   ## 5. 装饰器 装饰器是一个函数,其主要用途是包装另一个函数或类。这种包装的首要目的是透明地修改或增强被包装对象的行为。表示装饰器的语法是特殊符号"@",例如: ``` @trace def square(x): return x * x ``` 上面的代码可以简化为: ``` def square(x): return x * x square = trace(square) ``` 现在考虑trace的实现: ``` enable_tracing = True if enable_tracing: debug_log = open("debug.log", "w") def trace(func): if enable_tracing: def callf(*args, **kwargs): debug_log.write("Calling %s: %s, %s\n" % (func.__name__, args, kwargs)) r = func(*args, **kwargs) debug_log.write("%s returned %s\n" % (func.__name__, r)) return r return callf else: return func ``` 这段代码中,trace()创建了写有一些调试输出的包装器函数,然后调用了原始函数对象。因此如果调用square()函数,看到的将是包装器中write()方法的输出。 使用装饰器时,它们必须出现在函数或类定义之前的单独行上。可以同时使用多个装饰器,例如: ``` @foo @bar @spam def grok(x): pass\ grok = foo(bar(spam(grok))) ``` 装饰器也可以接受参数,例如: ``` @eventhandler('BUTTON') def handle_button(msg): ... @eventhandler('RESET') def handle_reset(msg): ... ``` 如果提供参数,装饰器的语义如下所示: ``` def handle_button(msg): ... temp = eventhandler('BUTTON') handle_button = temp(handle_button) ``` 对于类装饰器,应该让装饰器函数始终返回类对象作为结果。需要使用原始类定义的代码可能要直接引用类成员。   ## 6. 生成器与yield 函数使用yield关键字可以定义生成器对象。生成器是一个函数,它生成一个值的序列,以便在迭代中使用,例如: ``` def countdown(n): while n > 0: yield n n -=1 return ``` 如果调用该函数,其中的代码不会开始执行,它会返回一个生成器对象,该对象在\_\_next\_\_()被调用,例如: ``` c = countdown(10) c.__next__() ``` 调用\_\_next\_\_()时,生成器函数将不断执行语句,直到遇到yield语句为止。通常不会在生成器上直接调用\_\_next\_\_()方法,而是在for语句、sum()或一些使用序列的其他操作中使用,例如: ``` for n in countdown(10): statements a = sum(countdown(10)) ``` 生成器函数完成的标志是返回或引发StopIteration异常,这标志着迭代的结束。如果生成器没有全部完成,并且不再使用,可以调用close()方法,虽然通常情况下可以不必调用,例如: ``` c = countdown(10) c.__next__() c.close() c.__next__() # 抛出异常 ``` 在生成器函数内部,在yield语句上出现GeneratorExit异常时就会调用close()方法。可以选择获取这个异常,例如: ``` def countdown(n): try: while n > 0: yield n n -= 1 except GeneratorExit: print("Only made it to %d" % n) ```   ## 7. 协程与yield表达式 在函数内,yield语句还可以用作出现在赋值运算符右边的表达式,例如: ``` def receiver(): while True: n = (yield) print("Got %s" % n) ``` 以这种方式使用yield语句的函数称为协程,它的执行是为了响应发送给它的值。它的行为也类似于生成器,例如: ``` r = receiver() r.__next__() r.send(1) r.send(2) ``` 在协程中需要首先调用\_\_next\_\_()这件事很容易被忘记,可以用一个自动完成该步骤的装饰器来包装协程,例如: ``` def coroutine(func): def start(*args, **kwargs): g = func(*args, **kwargs) g.next() return g return start @coroutine def receiver(): while True: n = (yield) print("Got %s" % n) r = receiver() r.send("Hello World") ``` 协程的运行一般是无限期的,除非它被显式关闭或者自己退出。使用close()可以关闭输入值的流,例如: ``` r.close() r.send() # 抛出异常 ``` 关闭后如果继续给协程发送值,就会引发StopIteration异常,close()操作将在协程内部引发GeneratorExit异常。   ## 8. 列表包含 函数的常用操作是将函数应用给一个列表的所有项,并使用结果创建一个新列表。这种操作很常见,因此出现了叫做列表推导的运算符,例如: ``` nums = [1, 2, 3, 4, 5] squares = [n * n for n in nums] ``` 列表推导的一般语法如下: ``` [expression for item1 in iterable1 if condition1 for item2 in iterable2 if condition2 ... for itemN in iterableN if conditionN] ``` 下面给出一些例子: ``` 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= [(x, y) for x in a for y in b if x > 0] # e = [(5, 'a'), (5, 'b'), (5, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (7, 'a'), (7, 'b'), (7, 'c'), (8, 'a'), (8, 'b'), (8, 'c')] f = [(1, 2), (3, 4), (5, 6)] g = [math.sqrt(x * x + y * y) for x, y in f] # g = [2.23606797749979, 5.0, 7.810249675906654] ```   ## 9. 生成器表达式 生成器表达式是一个对象,它执行的计算与列表包含相同,但会迭代地生成结果,语法与列表包含相同,除了用圆括号代替方括号,如下: ``` (expression for item1 in iterable1 if condition1 for item2 in iterable2 if condition2 ... for itemN in iterableN if conditionN) ``` 生成器表达式实际上不创建列表或者立即对圆括号内的表达式求值,它创建一个通过迭代并按照需要生成值的生成器对象,例如: ``` a = [1, 2, 3, 4] b = (10 * i for i in a) print(b.__next__()) print(b.__next__()) ``` 使用列表推导时,Python实际上创建了包含结果数据的列表。而使用生成器表达式时,Python创建的是只知道如何按照需要生成数据的生成器。在某些应用中,可能影响性能和内存使用,例如: ``` f = open("data.txt") lines = (t.strip() for t in f) comments = (t for t in lines if t[0] == '#') for c in comments: print(c) ``` 生成器表达式不会创建序列形式的对象,不能对它进行索引。但是,使用内置的list()函数可以将生成器表达式转换为列表,例如: ``` clist = list(comments) ```   ## 10. lambda运算符 使用lambda语句可以创建表达式形式的匿名函数: ``` lambda args: expression ``` args是以逗号分隔的参数列表,而expression是用到这些参数的表达式,例如: ``` a = lambda x, y: x + y r = a(2, 3) ``` 使用lambda语句定义的代码必须是合法的表达式。lambda语句中不能出现多条语句和其他非表达式语句,比如for或while。   ## 11. 文档字符串 通常,函数的第一条语句会使用文档字符串,用于描述函数的用途,例如: ``` def factorial(n): """Computes n factorial. For examples: >>> factorial(6) 120 """ if n <= 1: return 1 else: return n* factorial(n-1) ``` 文档字符串保存在函数的\__doc\__属性中,IDE通常使用该函数提供交互式帮助。如果需要使用装饰器,可能会破坏与文档字符串相关的帮助功能,例如: ``` def wrap(func): call(*args, **kwargs): return func(*args, **kwargs) return call @wrap def factorial(n): """Computes n factorial.""" ``` 如果查目的地以上函数的帮助,可能会看到一个相当奇怪的内容,解决方法是编写可以传递函数名称和文档字符串的装饰器函数,例如: ``` def wrap(func): call(*args, **kwargs): return func(*args, **kwargs) call.__doc__ = func.__doc__ call.__name__ = func.__name__ return call ``` 因为这是一个常见问题,所以functools模块提供了函数wraps,用于自动复制这些属性,例如: ``` from functools import wraps def wrap(func): @wrap(func) call(*args, **kwargs): return func(*args, **kwargs) return call ```   ## 12. 函数属性 可以给函数添加任意属性,例如: ``` def foo(): statements foo.secure = 1 foo.private = 1 ``` 函数属性保存在函数的\__dict\__属性中,\__dic\__属性是一个字典。和文档字符串一样,也要注意混合使用函数属性和装饰器的问题。如果使用装饰器包装函数,实际上是由装饰器函数而非原始函数来访问属性。   ## 13. eval()、exec()和compile()函数 eval(str [, globals [, locals]])函数执行一个表达式字符串并返回结果,例如: ``` a = eval('3 * math.sin(3.5 + x) + 7.2') ``` 相似地,exec(str [, globals [, locals]])函数执行一个包含任意Python代码的字符串。例如: ``` a = [3, 5, 10, 13] exec("for i in a: print(i)") ``` 这两个函数都会在调用者的命名空间中执行。eval()和exec()函数可以接受一个或两个可选的映射对象,分别用作代码执行的全局和局部命名空间,例如: ``` globals = {'x': 7, 'y': 10, 'birds': ['Parrot', 'Swallow', 'Albatross']} locals = {} a = eval("3 * x + 4 * y", globals, locals) exec("fro b in birds: print(b)", globals, locals) ``` compile(str, filename, kind)函数将字符串编译为字节码,其中str是包含要编译代码的字符串,而filename是定义该字符串的文件,kind参数指定了要编译代码的类型。single表示一条语句,exec代表一组语句,而eval代表一个表达式。例如: ``` s = "for i inrange(0, 10): print(i)" c = compile(s, '', 'exec') exec(c) s2 = "3 * x + 4 * y" c2 = compile(s2, '', 'eval') result = eval(c2) ```