Python 函数

Posted daidechong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 函数相关的知识,希望对你有一定的参考价值。

给函数编写文档 

要给函数编写文档,以确保其他人能够理解,可添加注释(以#打头的内容)。还有另一种
编写注释的方式,就是添加独立的字符串。在有些地方,如def语句后面,添加这样的字符串很有用。
放在函数开头的字符串称为文档字符串(docstring),将作为函数的一部分存储起来。
下面的代码演示了如何给函数添加文档字符串:
def square(x):
‘Calculates the square of the number x.‘
return x * x
可以像下面这样访问文档字符串:
>>> square.__doc__
‘Calculates the square of the number x.‘
注意 __doc__是函数的一个属性。
特殊的内置函数help很有用。在交互式解释器中,可使用它获取有关函数的信息,其中包含
函数的文档字符串。
>>> help(square)
Help on function square in module __main__:


 

关于函数返回值  

数学意义上的函数总是返回根据参数计算得到的结果。在Python中,有些函数什么都不返回。
在诸如Pascal等的语言中,这样的函数可能另有其名(如过程),但在Python中,函数就是函数,
即使它严格来说并非函数。什么都不返回的函数不包含return语句,或者包含return语句,但没
有在return后面指定值。
def test():
print(‘This is printed‘)
return
print(‘This is not‘)
这里使用return语句只是为了结束函数。
>>> x = test()
This is printed
如你所见,跳过了第二条print语句。(这有点像在循环中使用break,但跳出的是函数。)既
然test什么都不返回,那么x指向的是什么呢?下面就来看看:
>>> x
>>>
什么都没有。再仔细地看看。
>>> print(x)
None
这是一个你熟悉的值: None。由此可知,所有的函数都返回值。如果你没有告诉它们该返回
什么,将返回None。
警告 不要让这种默认行为带来麻烦。如果你在if之类的语句中返回值,务必确保其他分支也
返回值,以免在调用者期望函数返回一个序列时(举个例子),不小心返回了None。


 

参数魔法 

  在def语句中,位于函数名后面的变量通常称为形参,而调用函数时提供的值称为实参。  

技术分享图片
函数通过参数获得了一系列的值,你能对其进行修改吗?如果这样做,结果将如何?参数不
        过是变量而已,行为与你预期的完全相同。在函数内部给参数赋值对外部没有任何影响。
        >>> def try_to_change(n):
        ... n = Mr. Gumby
        ...
        >>> name = Mrs. Entity
        >>> try_to_change(name)
        >>> name
        Mrs. Entity
        在try_to_change内,将新值赋给了参数n,但如你所见,这对变量name没有影响。说到底,
        这是一个完全不同的变量。传递并修改参数的效果类似于下面这样:
        >>> name = Mrs. Entity
        >>> n = name # 与传递参数的效果几乎相同
        >>> n = Mr. Gumby # 这是在函数内进行的
        >>> name
        Mrs. Entity
        这里的结果显而易见:变量n变了,但变量name没变。同样,在函数内部重新关联参数(即
        给它赋值)时,函数外部的变量不受影响。
        字符串(以及数和元组)是不可变的(immutable),这意味着你不能修改它们(即只能替换
        为新值)。因此这些类型作为参数没什么可说的。但如果参数为可变的数据结构(如列表)呢?
        >>> def change(n):
        ... n[0] = Mr. Gumby
        ...
        >>> names = [Mrs. Entity, Mrs. Thing]
        >>> change(names)
        >>> names
        [Mr. Gumby, Mrs. Thing]
        在这个示例中,也在函数内修改了参数,但这个示例与前一个示例之间存在一个重要的不同。
        在前一个示例中,只是给局部变量赋了新值,而在这个示例中,修改了变量关联到的列表。这很
        奇怪吧?其实不那么奇怪。下面再这样做一次,但这次不使用函数调用。
        >>> names = [Mrs. Entity, Mrs. Thing]
        >>> n = names # 再次假装传递名字作为参数
        >>> n[0] = Mr. Gumby # 修改列表
        >>> names
        [Mr. Gumby, Mrs. Thing]
        这样的情况你早就见过。将同一个列表赋给两个变量时,这两个变量将同时指向这个列表。
        就这么简单。要避免这样的结果,必须创建列表的副本。对序列执行切片操作时,返回的切片都
        是副本。因此,如果你创建覆盖整个列表的切片,得到的将是列表的副本。
        >>> names = [Mrs. Entity, Mrs. Thing]
        >>> n = names[:]
        现在n和names包含两个相等但不同的列表。
        >>> n is names
        False
        >>> n == names
        True
        现在如果(像在函数change中那样)修改n,将不会影响names。
        >>> n[0] = Mr. Gumby
        >>> n
        [Mr. Gumby, Mrs. Thing]
        >>> names
        [Mrs. Entity, Mrs. Thing]
        下面来尝试结合使用这种技巧和函数change。
        >>> change(names[:])
        >>> names
        [Mrs. Entity, Mrs. Thing]
        注意到参数n包含的是副本,因此原始列表是安全的。    
函数参数 
技术分享图片
前面使用的参数都是位置参数,因为它们的位置至关重要——事实上比名称还重要。本节介
        绍的技巧让你能够完全忽略位置。要熟悉这种技巧需要一段时间,但随着程序规模的增大,你很
        快就会发现它很有用。
        请看下面两个函数:
        def hello_1(greeting, name):
        print({}, {}!.format(greeting, name))
        def hello_2(name, greeting):
        print({}, {}!.format(name, greeting))
        这两个函数的功能完全相同,只是参数的排列顺序相反。
        >>> hello_1(Hello, world)
        Hello, world!
        >>> hello_2(Hello, world)
        Hello, world!
        有时候,参数的排列顺序可能难以记住,尤其是参数很多时。为了简化调用工作,可指定参
        数的名称。
        >>> hello_1(greeting=Hello, name=world)
        Hello, world!
        在这里,参数的顺序无关紧要。
        >>> hello_1(name=world, greeting=Hello)
        Hello, world!
        不过名称很重要(你可能猜到了)。
        >>> hello_2(greeting=Hello, name=world)
        world, Hello!
        像这样使用名称指定的参数称为关键字参数,主要优点是有助于澄清各个参数的作用。这样,
        函数调用不再像下面这样怪异而神秘:
        >>> store(Mr. Brainsample, 10, 20, 13, 5)
        可以像下面这样做:
        >>> store(patient=Mr. Brainsample, hour=10, minute=20, day=13, month=5)
        虽然这样做的输入量多些,但每个参数的作用清晰明了。另外,参数的顺序错了也没关系。
        然而,关键字参数最大的优点在于,可以指定默认值。
        def hello_3(greeting=Hello, name=world):
        print({}, {}!.format(greeting, name))
        像这样给参数指定默认值后,调用函数时可不提供它!可以根据需要,一个参数值也不提供、
        提供部分参数值或提供全部参数值。
        >>> hello_3()
        Hello, world!
        >>> hello_3(Greetings)
        Greetings, world!
        >>> hello_3(Greetings, universe)
        Greetings, universe!
        如你所见,仅使用位置参数就很好,只不过如果要提供参数name,必须同时提供参数
        greeting。如果只想提供参数name,并让参数greeting使用默认值呢?相信你已猜到该怎么做了。
        >>> hello_3(name=Gumby)
        Hello, Gumby!
        很巧妙吧?还不止这些。你可结合使用位置参数和关键字参数,但必须先指定所有的位置参
        数,否则解释器将不知道它们是哪个参数(即不知道参数对应的位置)。
        注意:通常不应结合使用位置参数和关键字参数,除非你知道这样做的后果。一般而言,除非
            必不可少的参数很少,而带默认值的可选参数很多,否则不应结合使用关键字参数和位
            置参数。
            例如,函数hello可能要求必须指定姓名,而问候语和标点是可选的。
            def hello_4(name, greeting=Hello, punctuation=!):
            print({}, {}{}.format(greeting, name, punctuation))
            调用这个函数的方式很多,下面是其中的一些:
            >>> hello_4(Mars)
            Hello, Mars!
            >>> hello_4(Mars, Howdy)
            Howdy, Mars!
            >>> hello_4(Mars, Howdy, ...)
            Howdy, Mars...
            >>> hello_4(Mars, punctuation=.)
            Hello, Mars.
            >>> hello_4(Mars, greeting=Top of the morning to ya)
            Top of the morning to ya, Mars!
            >>> hello_4()
            Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
            TypeError: hello_4() missing 1 required positional argument: name
            注意 如果给参数name也指定了默认值,最后一个调用就不会引发异常。
关键字参数和默认值 
技术分享图片
有时候,允许用户提供任意数量的参数很有用。例如,在本章前面的姓名存储示例中(参见
        6.4.2节),每次只能存储一个姓名。如果能够像下面这样同时存储多个姓名就好了:
        >>> store(data, name1, name2, name3)
        为此,应允许用户提供任意数量的姓名。实际上,这实现起来并不难。
        请尝试使用下面这样的函数定义:
        def print_params(*params):
        print(params)
        这里好像只指定了一个参数,但它前面有一个星号。这是什么意思呢?尝试使用一个参数来
        调用这个函数,看看结果如何。
        >>> print_params(Testing)
        (Testing,)
        注意到打印的是一个元组,因为里面有一个逗号。这么说,前面有星号的参数将被放在元组
        中?复数params应该提供了线索。
        >>> print_params(1, 2, 3)
        (1, 2, 3)
        参数前面的星号将提供的所有值都放在一个元组中,也就是将这些值收集起来。这样的行为
        我们在5.2.1节见过:赋值时带星号的变量收集多余的值。它收集的是列表而不是元组中多余的值,
        但除此之外,这两种用法很像。下面再来编写一个函数:
        def print_params_2(title, *params):
        print(title)
        print(params)
        并尝试调用它:
        >>> print_params_2(Params:, 1, 2, 3)
        Params:
        (1, 2, 3)
        因此星号意味着收集余下的位置参数。如果没有可供收集的参数, params将是一个空元组。
        >>> print_params_2(Nothing:)
        Nothing:
        ()
        与赋值时一样,带星号的参数也可放在其他位置(而不是最后),但不同的是,在这种情况
        下你需要做些额外的工作:使用名称来指定后续参数。
        >>> def in_the_middle(x, *y, z):
        ... print(x, y, z)
        ...
        >>> in_the_middle(1, 2, 3, 4, 5, z=7)
        1 (2, 3, 4, 5) 7
        >>> in_the_middle(1, 2, 3, 4, 5, 7)
        Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        TypeError: in_the_middle() missing 1 required keyword-only argument: z
        星号不会收集关键字参数。
        >>> print_params_2(Hmm..., something=42)
        Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        TypeError: print_params_2() got an unexpected keyword argument something
        要收集关键字参数,可使用两个星号。
        >>> def print_params_3(**params):
        ... print(params)
        ...
        >>> print_params_3(x=1, y=2, z=3)
        {z: 3, x: 1, y: 2}
        如你所见,这样得到的是一个字典而不是元组。可结合使用这些技术。
        def print_params_4(x, y, z=3, *pospar, **keypar):
        print(x, y, z)
        print(pospar)
        print(keypar)
        其效果与预期的相同。
        >>> print_params_4(1, 2, 3, 5, 6, 7, foo=1, bar=2)
        1 2 3
        (5, 6, 7)
        {foo: 1, bar: 2}
        >>> print_params_4(1, 2)
        1 2 3
        ()
        {}
        通过结合使用这些技术,可做的事情很多。如果你想知道结合方式的工作原理(或是否可以
        这样结合),动手试一试即可!在下一节你将看到,不管在函数定义中是否使用了*和**,都可在
        函数调用中使用它们。
        现在回到最初的问题:如何在姓名存储示例中使用这种技术?解决方案如下:
        def store(data, *full_names):
        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):
        people = lookup(data, label, name)
        if people:
        people.append(full_name)
        else:
        data[label][name] = [full_name]
        这个函数调用起来与只接受一个姓名的前一版一样容易。
        >>> d = {}
        >>> init(d)
        >>> store(d, Han Solo)
        但现在你也可以这样做:
        >>> store(d, Luke Skywalker, Anakin Skywalker)
        >>> lookup(d, last, Skywalker)
        [Luke Skywalker, Anakin Skywalker]
收集参数
技术分享图片
前面介绍了如何将参数收集到元组和字典中,但用同样的两个运算符(*和**)也可执行相
        反的操作。与收集参数相反的操作是什么呢?假设有如下函数:
        def add(x, y):
        return x + y
        同时假设还有一个元组,其中包含两个你要相加的数。
        params = (1, 2)
        这与前面执行的操作差不多是相反的:不是收集参数,而是分配参数。这是通过在调用函数
        (而不是定义函数)时使用运算符*实现的。
        >>> add(*params)
        3
        这种做法也可用于参数列表的一部分,条件是这部分位于参数列表末尾。通过使用运算符**,
        可将字典中的值分配给关键字参数。如果你像前面那样定义了函数hello_3,就可像下面这样做:
        >>> params = {name: Sir Robin, greeting: Well met}
        >>> hello_3(**params)
        Well met, Sir Robin!
        如果在定义和调用函数时都使用*或**,将只传递元组或字典。因此还不如不使用它们,还
        可省却些麻烦。
        >>> def with_stars(**kwds):
        ... print(kwds[name], is, kwds[age], years old)
        ...
        >>> def without_stars(kwds):
        ... print(kwds[name], is, kwds[age], years old)
        ...
        >>> args = {name: Mr. Gumby, age: 42}
        >>> with_stars(**args)
        Mr. Gumby is 42 years old
        >>> without_stars(args)
        Mr. Gumby is 42 years old
        如你所见,对于函数with_stars,我在定义和调用它时都使用了星号,而对于函数without_
        stars,我在定义和调用它时都没有使用,但这两种做法的效果相同。因此,只有在定义函数(允
        许可变数量的参数) 或调用函数时(拆分字典或序列)使用,星号才能发挥作用。
            提示:使用这些拆分运算符来传递参数很有用,因为这样无需操心参数个数之类的问题,如下所示:使用这些拆分运算符来传递参数很有用,因为这样无需操心参数个数之类的问题,如下所示:
            def foo(x, y, z, m=0, n=0):
            print(x, y, z, m, n)
            def call_foo(*args, **kwds):
            print("Calling foo!")
            foo(*args, **kwds)
            这在调用超类的构造函数时特别有用
分配参数
技术分享图片
变量到底是什么呢?可将其视为指向值的名称。因此,执行赋值语句x = 1后,名称x指向值
    1。这几乎与使用字典时一样(字典中的键指向值),只是你使用的是“看不见”的字典。实际上,
    这种解释已经离真相不远。有一个名为vars的内置函数,它返回这个不可见的字典:
    >>> x = 1
    >>> scope = vars()
    >>> scope[x]
    1
    >>> scope[x] += 1
    >>> x
    2
    这种“看不见的字典”称为命名空间或作用域。那么有多少个命名空间呢?除全局作用域外,
    每个函数调用都将创建一个。
    >>> def foo(): x = 42
    ...
    >>> x = 1
    >>> foo()
    >>> x
    1
    在这里,函数foo修改(重新关联)了变量x,但当你最终查看时,它根本没变。这是因为调
    用foo时创建了一个新的命名空间,供foo中的代码块使用。赋值语句x = 42是在这个内部作用域
    (局部命名空间)中执行的,不影响外部(全局)作用域内的x。在函数内使用的变量称为局部变
    量(与之相对的是全局变量)。参数类似于局部变量,因此参数与全局变量同名不会有任何问题。
    >>> def output(x): print(x)
    ...
    >>> x = 1
    >>> y = 2
    >>> output(y)
    2
    到目前为止一切顺利。但如果要在函数中访问全局变量呢?如果只是想读取这种变量的值
    (不重新关联它),通常不会有任何问题。
    >>> def combine(parameter): print(parameter + external)
    ...
    >>> external = berry
    >>> combine(Shrub)
    Shrubberry    
    读取全局变量的值通常不会有问题,但还是存在出现问题的可能性。如果有一个局部
    变量或参数与你要访问的全局变量同名,就无法直接访问全局变量,因为它被局部变量遮
    住了。
    如果需要,可使用函数globals来访问全局变量。这个函数类似于vars,返回一个包含全
    局变量的字典。(locals返回一个包含局部变量的字典。)
    例如,在前面的示例中,如果有一个名为parameter的全局变量,就无法在函数combine
    中访问它,因为有一个与之同名的参数。然而,必要时可使用globals()[parameter]来访
    问它。
    >>> def combine(parameter):
    ... print(parameter + globals()[parameter])
    ...
    >>> parameter = berry
    >>> combine(Shrub)
    Shrubberry
    重新关联全局变量(使其指向新值)是另一码事。在函数内部给变量赋值时,该变量默认为
    局部变量,除非你明确地告诉Python它是全局变量。那么如何将这一点告知Python呢?
    >>> x = 1
    >>> def change_global():
    ... global x
    ... x = x + 1
    ...
    >>> change_global()
    >>> x
    2
作用域
技术分享图片
Python函数可以嵌套,即可将一个函数放在另一个函数内,如下所示:
    def foo():
    def bar():
    print("Hello, world!")
    bar()
    嵌套通常用处不大,但有一个很突出的用途:使用一个函数来创建另一个函数。这意味
    着可像下面这样编写函数:
    def multiplier(factor):
    def multiplyByFactor(number):
    return number * factor
    return multiplyByFactor
    在这里,一个函数位于另一个函数中,且外面的函数返回里面的函数。也就是返回一个
    函数,而不是调用它。重要的是,返回的函数能够访问其定义所在的作用域。换而言之,它
    携带着自己所在的环境(和相关的局部变量)!
    每当外部函数被调用时,都将重新定义内部的函数,而变量factor的值也可能不同。由
    于Python的嵌套作用域,可在内部函数中访问这个来自外部局部作用域(multiplier)的变
    量,如下所示:
    >>> double = multiplier(2)
    >>> double(5)
    10
    >>> triple = multiplier(3)
    >>> triple(3)
    9
    >>> multiplier(5)(4)
    20
    像multiplyByFactor这样存储其所在作用域的函数称为闭包。
    通常,不能给外部作用域内的变量赋值,但如果一定要这样做,可使用关键字nonlocal。
    这个关键字的用法与global很像,让你能够给外部作用域(非全局作用域)内的变量赋值。
作用域嵌套

 


递归 

前面深入介绍了如何创建和调用函数。你知道,函数可调用其他函数,但可能让你感到惊讶
的是,函数还可调用自己。
如果你以前没有遇到这种情况,可能想知道递归是什么意思。 简单地说,递归意味着引用(这
里是调用)自身。下面是一个常见的递归定义(但必须承认,这种定义很愚蠢):
递归[名词]:参见“递归”。
如果你在网上搜索“递归”,将看到类似的定义。
递归式定义(包括递归式函数定义)引用了当前定义的术语。递归可能难以理解,也可能非
常简单,这取决于你对它的熟悉程度。要更深入地认识递归,可能应该参阅优秀的计算机教材,
但尝试Python解释器也大有裨益。
一般而言,你不想要递归式定义(像前面的“递归”那样),因为这毫无意义:你查找“递
归”,它告诉你去查找“递归”,如此这般没完没了。下面是一个递归式函数定义:
def recursion():
return recursion()
这个定义显然什么都没有做,与刚才的“递归”定义一样傻。如果你运行它,结果将如何呢?
你将发现运行一段时间后,这个程序崩溃了(引发异常)。从理论上说,这个程序将不断运行下
去,但每次调用函数时,都将消耗一些内存。因此函数调用次数达到一定的程度(且之前的函数
调用未返回)后,将耗尽所有的内存空间,导致程序终止并显示错误消息“超过最大递归深度”。
这个函数中的递归称为无穷递归(就像以while True打头且不包含break和return语句的循环
被称为无限循环一样),因为它从理论上说永远不会结束。你想要的是能对你有所帮助的递归函
数,这样的递归函数通常包含下面两部分。
? 基线条件(针对最小的问题):满足这种条件时函数将直接返回一个值。
? 递归条件:包含一个或多个调用,这些调用旨在解决问题的一部分。
这里的关键是,通过将问题分解为较小的部分,可避免递归没完没了,因为问题终将被分解
成基线条件可以解决的最小问题。
那么如何让函数调用自身呢?这没有看起来那么难懂。前面说过,每次调用函数时,都将为
此创建一个新的命名空间。这意味着函数调用自身时,是两个不同的函数[更准确地说,是不同
版本(即命名空间不同)的同一个函数]在交流。你可将此视为两个属于相同物种的动物在彼此
交流。


 

函数式编程 

至此,你可能习惯了像使用其他对象(字符串、数、序列等)一样使用函数:将其赋
给变量,将其作为参数进行传递,以及从函数返回它们。在有些语言(如 scheme 和 Lisp)
中,几乎所有的任务都是以这种方式使用函数来完成的。在 Python 中,通常不会如此倚
重函数(而是创建自定义对象,这将在下一章详细介绍),但完全可以这样做。
Python提供了一些有助于进行这种函数式编程的函数: map、 filter和reduce。在较新的
Python版本中,函数map和filter的用途并不大,应该使用列表推导来替代它们。你可使用map
将序列的所有元素传递给函数。
>>> list(map(str, range(10))) # 与[str(i) for i in range(10)]等价
[‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘]
你可使用filter根据布尔函数的返回值来对元素进行过滤。
>>> def func(x):
... return x.isalnum()
...
>>> seq = ["foo", "x41", "?!", "***"]
>>> list(filter(func, seq))
[‘foo‘, ‘x41‘]
就这个示例而言,如果转而使用列表推导,就无需创建前述自定义函数。
>>> [x for x in seq if x.isalnum()]
[‘foo‘, ‘x41‘]
实际上, Python提供了一种名为lambda表达式①的功能,让你能够创建内嵌的简单函数
(主要供map、 filter和reduce使用)。
>>> filter(lambda x: x.isalnum(), seq)
[‘foo‘, ‘x41‘]
然而,使用列表推导的可读性不是更高吗?
要使用列表推导来替换函数reduce不那么容易,而这个函数提供的功能即便能用到,也
用得不多。它使用指定的函数将序列的前两个元素合二为一,再将结果与第3个元素合二为
一,依此类推,直到处理完整个序列并得到一个结果。例如,如果你要将序列中的所有数相
加,可结合使用reduce和lambda x, y: x+y②。
>>> numbers = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
>>> from functools import reduce
>>> reduce(lambda x, y: x + y, numbers)
1161
当然,就这个示例而言,还不如使用内置函数sum。


 

map(func, seq[, seq, ...]) 对序列中的所有元素执行函数
filter(func, seq) 返回一个列表,其中包含对其执行函数时结果为真的所有元素
reduce(func, seq[, initial]) 等价于 func(func(func(seq[0], seq[1]), seq[2]), ...)
sum(seq) 返回 seq 中所有元素的和
apply(func[, args[, kwargs]]) 调用函数(还提供要传递给函数的参数)

 

  

 




































































































以上是关于Python 函数的主要内容,如果未能解决你的问题,请参考以下文章

python使用上下文对代码片段进行计时,非装饰器

python 元组有用的函数,方法和片段。

Python代码阅读(第26篇):将列表映射成字典

VSCode自定义代码片段——声明函数

Python学习 :函数

VSCode自定义代码片段8——声明函数