Python中的函数详解

本文由 伯乐在线PyPer 翻译,Daetalus 校稿。未经许可,禁止转载!
英文出处:intermediatepythonista.com。欢迎加入翻译组

Python中的函数,无论是命名函数,还是匿名函数,都是语句和表达式的集合。在Python中,函数是第一个类对象,这意味着函数的用法并没有限制。Python函数的使用方式就像Python中其他值一样,例如字符串和数字等。Python函数拥有一些属性,通过使用Python内置函数dir就能查看这些属性,如下代码所示:

Python

1
2
3
4
5
6
7
8
def square(x):
    return x**2
 
>>> square
<function square at 0x031AA230>
>>> dir(square)
[‘__call__’, ‘__class__’, ‘__closure__’, ‘__code__’, ‘__defaults__’, ‘__delattr__’, ‘__dict__’, ‘__doc__’, ‘__format__’, ‘__get__’, ‘__getattribute__’, ‘__globals__’, ‘__hash__’, ‘__init__’, ‘__module__’, ‘__name__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘func_closure’, ‘func_code’, ‘func_defaults’, ‘func_dict’, ‘func_doc’, ‘func_globals’, ‘func_name’]
>>>


其中,一些重要的函数属性包括以下几个:

1. __doc__返回指定函数的文档字符串。

Python

1
2
3
4
5
6
def square(x):
    “””return square of given number”””
    return x**2
 
>>> square.__doc__
‘return square of given number’

2. __name__返回函数名字。

Python

1
2
3
4
5
6
def square(x):
    “””return square of given number”””
    return x**2
 
>>> square.func_name
‘square’

3. __module__返回函数定义所在模块的名字。

Python

1
2
3
4
5
6
def square(x):
    “””return square of given number”””
    return x**2
 
>>> square.__module__
‘__main__’

4. func_defaults返回一个包含默认参数值的元组,默认参数将在后文进行讨论。

5. func_globals返回一个包含函数全局变量的字典引用。

Python

1
2
3
4
5
6
def square(x):
    “””return square of given number”””
    return x**2
 
>>> square.func_globals
{‘__builtins__’: <module ‘__builtin__’ (builtin)>, ‘__name__’: ‘__main__’, ‘square’: <function square at 0x10f099c08>, ‘__doc__’: None, ‘__package__’: None}

6. func_dict返回支持任意函数属性的命名空间。

Python

1
2
3
4
5
6
def square(x):
    “””return square of given number”””
    return x**2
 
>>> square.func_dict
{}

7. func_closure返回一个胞体元组,其中胞体包含了函数自由变量的绑定,闭包将在后文讨论。

函数可以作为参数传递给其他函数。这些以其他函数作为参数的函数通常称为更高阶函数,这就构成了函数式编程中一个非常重要的部分。高阶函数一个很好的例子就是map函数,该函数接受一个函数和一个迭代器作为参数,并将函数应用于迭代器中的每一项,最后返回一个新的列表。我们将在下面的例子中演示这一点,例子中将前面定义的square函数和一个数字迭代器传递给map函数。

Python

1
2
>>> map(square, range(10))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

此外,函数也可以在其他函数代码块内部定义,同时也能从其他函数调用中返回。

Python

1
2
3
4
5
def outer():
    outer_var = “outer variable”
    def inner():
        return outer_var
    return inner

在上面的例子中,我们在函数outer中定义了另一个函数inner,并且当函数outer执行时将返回inner函数。此外,像任何其他Python对象一样,函数也可以赋值给变量,如下所示:

Python

1
2
3
4
5
6
7
8
9
10
def outer():
    outer_var = “outer variable”
    def inner():
        return outer_var
    return inner
 
>>> func = outer()
>>> func
<function inner at 0x031AA270>
>>>

在上面的例子中,outer函数被调用时将会返回一个函数,并将返回的函数赋值给变量func。最后,该变量就可以像被返回的函数一样被调用:

Python

1
2
>>> func()
‘outer variable’

函数定义

关键字def用于创建用户自定义函数,函数定义就是一些可执行的语句。

Python

1
2
def square(x):
    return x**2

在上面的square函数中,当包含该函数的模块加载到Python解释器中时,或者如果该函数在Python REPL中定义,那么将会执行函数定义语句def square(x)。然而,这对以可变数据结构作为值的默认参数有一些影响,这一点我们将会在后文讲述。函数定义的执行会绑定当前本地命名空间中的函数名(可以将命名空间当作名字到值的一种映射,并且这种映射还可以嵌套,命名空间和范围会在另一个教程中详细介绍)到一个函数对象,该对象是一个对函数中可执行代码的包装器。这个函数对象包含了一个对当前全局命名空间的引用,而当前命名空间指该函数调用时所使用的全局命名空间。此外,函数定义不会执行函数体,只有在函数被调用时才会执行函数体。

函数调用参数

除了正常的参数之外,Python函数还支持数量可变的参数。这些参数有主要有下面描述的三种类别:

1. 默认参数值:这允许用户为函数的参数定义一些默认值。这种情况下,可以以更少的参数来调用该函数,而函数调用时未提供的参数,Python会使用默认提供的值作为这些参数值。下面的例子展示了这种用法:

Python

1
2
def show_args(arg, def_arg=1, def_arg2=2):
      return “arg={}, def_arg={}, def_arg2={}”.format(arg, def_arg, def_arg2)

上面例子函数的定义中,包含一个正常位置的参数arg和两个默认参数def_arg和def_arg2。该函数可以以下面中的任何一种方式进行调用:

(1)只提供非缺省位置参数值。在本例中,缺省参数取默认值:

Python

1
2
3
4
5
def show_args(arg, def_arg=1, def_arg2=2):
      return “arg={}, def_arg={}, def_arg2={}”.format(arg, def_arg, def_arg2)
 
  >>> show_args(“tranquility”)
  ‘arg=tranquility, def_arg=1, def_arg2=2’

(2)用提供的值覆盖一些默认的参数值,包括非缺省位置参数:

Python

1
2
3
4
5
def show_args(arg, def_arg=1, def_arg2=2):
      return “arg={}, def_arg={}, def_arg2={}”.format(arg, def_arg, def_arg2)
 
  >>> show_args(“tranquility”, “to Houston”)
  ‘arg=tranquility, def_arg=to Houston, def_arg2=2’

(3)为所有参数提供值,可以用这些值覆盖默认参数值:

Python

1
2
3
4
5
def show_args(arg, def_arg=1, def_arg2=2):
      return “arg={}, def_arg={}, def_arg2={}”.format(arg, def_arg, def_arg2)
 
   >>> show_args(“tranquility”, “to Houston”, “the eagle has landed”)
‘arg=tranquility, def_arg=to Houston, def_arg2=the eagle has landed’

当使用可变的默认数据结构作为默认参数时,需要特别小心。因为函数定义只执行一次,所以这些可变的数据结构(引用值)只在函数定义时创建一次。这就意味着,相同的可变数据结构将用于所有函数调用,如下面例子所示:

Python

1
2
3
4
5
6
7
8
def show_args_using_mutable_defaults(arg, def_arg=[]):
      def_arg.append(“Hello World”)
      return “arg={}, def_arg={}”.format(arg, def_arg)
 
  >>> show_args_using_mutable_defaults(“test”)
  “arg=test, def_arg=[‘Hello World’]”
  >>> show_args_using_mutable_defaults(“test 2”)
  “arg=test 2, def_arg=[‘Hello World’, ‘Hello World’]”

在每个函数调用中,“Hello World”都被添加到了def_arg列表中,在调用两次函数之后,默认参数中将有两个“Hello World”字符串。当使用可变默认参数作为默认值时,注意到这一点非常重要。当我们讨论Python数据模型时,将会清楚理解其原因。

2. 关键字参数:以“kwarg=value”的形式使用关键字参数也可以调用函数。其中,kwarg指函数定义中使用的参数名称。以下面定义的含有默认和非默认参数的函数为例:

Python

1
2
def show_args(arg, def_arg=1):
       return “arg={}, def_arg={}”.format(arg, def_arg)

为了演示使用关键字参数调用函数,下面的函数可以以后面的任何一种方式调用:

Python

1
show_args(arg=“test”, def_arg=3)

Python

1
show_args(test)

Python

1
show_args(arg=“test”)

Python

1
show_args(“test”, 3)

在函数调用中,关键字参数不得早于非关键字参数,所以以下调用会失败:

Python

1
show_args(def_arg=4)

函数不能为一个参数提供重复值,所以下面的调用方法是非法的:

Python

1
show_args(“test”, arg=“testing”)

在上面的例子中,参数arg是位置参数,所以值“test”会分配给它。而试图将其再次分配给关键字arg,意味着在尝试多重赋值,而这是非法的。

传递的所有关键字参数必须匹配一个函数接受的参数,而包含非可选参数的关键字顺序并不重要,所以下面调换了参数顺序的写法是合法的:

Python

1
show_args(def_arg=“testing”, arg=“test”)

3. 任意的参数列表:Python还支持定义这样的函数,该函数可以接受以元组形式传递的任意数量的参数,Python教程中的一个例子如下所示:

Python

1
2
def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

任意数量的参数必须在正常参数之后。在本例中,任意数量参数存在于参数file和separator之后。下面是一个调用上述定义函数的示例:

Python

1
2
f = open(“test.txt”, “wb”)
write_multiple_items(f, ” “, “one”, “two”, “three”, “four”, “five”)

上面的参数one、two、three、four、five捆绑在一起共同组成了一个元组,通过参数args就能访问该元组。

解包函数参数

有时候,函数调用的参数可能是以元组、列表或字典的形式存在。可以通过使用“*”或“**”操作符将这些参数解包到函数内部以供调用。以下面的函数为例,该函数接受两个位置参数,并打印出两个参数的值。

Python

1
2
3
def print_args(a, b):
    print a
    print b

如果提供给函数的参数值是以列表形式存在,那么我们可以直接将这些值解包到函数中,如下所示:

Python

1
2
3
4
>>> args = [1, 2]
>>> print_args(*args)
1
2

类似的,当我们有关键词时,可以使用字典来存储kwarg到值的映射关系,并利用“**”操作符将关键字参数解包到函数,如下所示:

Python

1
2
3
4
5
6
7
8
>>> def parrot(voltage, state=a stiff, action=voom):
           print “– This parrot wouldn’t”, action,
           print “if you put”, voltage, “volts through it.”,
           print “E’s”, state, “!”
 
>>> d = {“voltage”: “four million”, “state”: “bleedin’ demised”, “action”: “VOOM”}
>>> parrot(**d)
>>> This parrot wouldnt VOOM if you put four million volts through it. Es bleedin demised

利用“*”和“**”定义函数

有时候,当定义一个函数时,我们之前可能不知道参数的数量。这就导致了下面签名的函数定义:

Python

1
show_args(arg, *args, **kwargs)

“*args”参数表示未知的位置参数序列长度,而“**kwargs”代表包含关键字和值映射关系的字典,它可以包含任意数量的关键字和值映射,并且在函数定义中“*args”必须位于“**kwargs”前面。下面的代码演示了这种情况:

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def show_args(arg, *args, **kwargs):
    print arg
    for item in args:
        print args
    for key, value in kwargs:
        print key, value
 
>>> args = [1, 2, 3, 4]
>>> kwargs = dict(name=‘testing’, age=24, year=2014)
>>> show_args(“hey”, *args, **kwargs)
hey
1
2
3
4
age 24
name testing
year 2014

必须向函数提供正常的参数,但“*args”和“**kwargs”却是可选的,如下所示:

Python

1
2
>>> show_args(“hey”, *args, **kwargs)
hey

在函数调用中,普通参数以正常方式提供,而可选参数则可以通过解包的形式到达函数调用中。

匿名函数

Python也支持匿名函数,这些函数使用lambda关键字创建。Python中Lambda表达式的形式如下所示:

Python

1
lambda_expr ::=  “lambda” [parameter_list]: expression

Lambda表达式返回评估后的函数对象,并且具有与命名函数相同的属性。在Python中,Lambda表达式通常只用于非常简单的函数,如下所示:

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> square = lambda x: x**2
>>> for i in range(10):
    square(i)
0
1
4
9
16
25
36
49
64
81
>>>

上面的lambda表达式的功能与下面命名函数的功能相同:

Python

1
2
def square(x):
    return x**2

嵌套函数和闭包

在一个函数内部定义函数就创建了嵌套函数,如下所示:

Python

1
2
3
4
5
6
7
```python
   def outer():
       outer_var = “outer variable”
       def inner():
           return outer_var
        return inner
```

在这种类型的函数定义中,函数inner只在函数outer内部有效,所以当内部函数需要被返回(移动到外部作用范围)或被传递给另一个函数时,使用嵌套函数通常比较方便。在如在上面的嵌套函数中,每次调用外部函数时都会创建一个新的嵌套函数实例,这是因为,在每次执行外部函数时,都会执行一次内部函数定义,而其函数体则不会被执行。

嵌套函数可以访问创建它的环境,这是python函数定义语义的直接结果。一个结果是,外部函数中定义的变量可以在内部函数中引用,即使外部函数已经执行结束。

Python

1
2
3
4
5
6
7
8
9
10
11
def outer():
    outer_var = “outer variable”
    def inner():
        return outer_var
    return inner
 
>>> x = outer()
>>> x
<function inner at 0x0273BCF0>
>>> x()
‘outer variable’

当内部嵌套的函数引用外部函数中的变量时,我们说嵌套函数相对于引用变量是封闭的。我们可以使用函数对象的一个特殊属性“__closure__”来访问这个封闭的变量,如下所示:

Python

1
2
3
4
5
6
>>> cl = x.__closure__
>>> cl
(<cell at 0x029E4470: str object at 0x02A0FD90>,)
 
>>> cl[0].cell_contents
‘outer variable’

Python中的闭包有一个古怪的行为。在Python 2.x及更低版本中,指向不可变类型(例如字符串和数字)的变量不能在闭包内反弹。下面的例子说明了这一点:

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
def counter():
    count = 0
    def c():
        count += 1
        return count
    return c
 
>>> c = counter()
>>> c()
Traceback (most recent call last):
  File , line 1, in <module>
  File , line 4, in c
UnboundLocalError: local variable ‘count’ referenced before assignment

一个相当不可靠的解决方案是,使用一个可变类型来捕获闭包,如下所示:

Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def counter():
    count = [0]
    def c():
        count[0] += 1
        return count[0]
    return c
 
>>> c = counter()
>>> c()
1
>>> c()
2
>>> c()
3

Python 3引入了“nonlocal”关键字用来解决下面所示的闭包范围问题。在本教程命名空间一节中,我们更加详细地描述了这些古怪用法。

Python

1
2
3
4
5
6
7
def counter():
       count = 0
       def c():
           nonlocal count
           count += 1
           return count
        return c

闭包可以用来维持状态(与类作用不同),在一些简单的情况下,还可以提供一种简洁性与可读性比类更强的解决方案,我们使用tech_pro中的一个日志例子来说明这一点。假设一个非常简单的日志API,它使用基于类的面向对象思想,并可以在不同级别上打印日志:

Python

1
2
3
4
5
6
7
8
9
10
class Log:
    def __init__(self, level):
        self._level = level
 
    def __call__(self, message):
        print(“{}: {}”.format(self._level, message))
 
log_info = Log(“info”)
log_warning = Log(“warning”)
log_error = Log(“error”)

相同的功能也可以使用闭包来实现,如下所示:

Python

1
2
3
4
5
6
7
8
def make_log(level):
    def _(message):
        print(“{}: {}”.format(level, message))
    return _
 
log_info = make_log(“info”)
log_warning = make_log(“warning”)
log_error = make_log(“error”)

可以看出,即使两个版本都实现了相同的功能,但基于闭包的版本更简洁、可读性更好。闭包在一个主要的Python函数“函数修饰符”中也扮演着很重要的角色,这是使用非常广泛的功能,我们将在接下来的教程讲解。

如果文中你发现了任何错误、问题或者你有更好的话题想让我写出来,可以在Twitter上联系我(@obi_inc)。

扩展阅读

1
4 收藏

1 评论





关于作者:PyPer


一名就读于羊城某高校的学生,主要关注 Python、Perl 、PowerShell等脚本技术,新浪微博:http://weibo.com/LIwianwpIO。


个人主页 ·
我的文章

· 11

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

发表评论

电子邮件地址不会被公开。 必填项已用*标注