[Python]函数与函数编程
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, border, 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)
当前文章:[Python]函数与函数编程
本文地址:http://scjbc.cn/article/gcedig.html