【Python学习教程】Python函数和lambda表达式_6(1)

虽然 Python 语言允许定义个空函数,但空函数本身并没有实际意义。

另外值得一提的是,函数中的 return 语句可以直接返回一个表达式的值,例如修改上面的 str_max() 函数:

def str\_max(str1,str2):
    return str1 if str1 > str2 else str2

该函数的功能,和上面的 str_max() 函数是完全一样的,只是省略了创建 str 变量,因此函数代码更加简洁。

Python函数的调用

调用函数也就是执行函数。如果把创建的函数理解为一个具有某种用途的工具,那么调用函数就相当于使用该工具。

函数调用的基本语法格式如下所示:

[返回值] = 函数名([形参值])

其中,函数名即指的是要调用的函数的名称;形参值指的是当初创建函数时要求传入的各个形参的值。如果该函数有返回值,我们可以通过一个变量来接收该值,当然也可以不接受。

需要注意的是,创建函数有多少个形参,那么调用时就需要传入多少个值,且顺序必须和创建函数时一致。即便该函数没有参数,函数名后的小括号也不能省略。

例如,我们可以调用上面创建的 pass_dis() 和 str_max() 函数:

pass_dis()
strmax = str_max("http://c.biancheng.net/python","http://c.biancheng.net/shell");
print(strmax)

首先,对于调用空函数来说,由于函数本身并不包含任何有价值的执行代码,也没有返回值,应该调用空函数不会有任何效果。

其次,对于上面程序中调用 str_max() 函数,由于当初定义该函数为其设置了 2 个参数,因此这里在调用该参数,就必须传入 2 个参数。同时,由于该函数内部还使用了 return 语句,因此我们可以使用 strmax 变量来接收该函数的返回值。

因此,程序执行结果为:

http://c.biancheng.net/shell

为函数提供说明文档

前面章节讲过,通过调用 Python 的 help() 内置函数或者 doc 属性,我们可以查看某个函数的使用说明文档。事实上,无论是 Python 提供给我们的函数,还是自定义的函数,其说明文档都需要设计该函数的程序员自己编写。

其实,函数的说明文档,本质就是一段字符串,只不过作为说明文档,字符串的放置位置是有讲究的,函数的说明文档通常位于函数内部、所有代码的最前面。

以上面程序中的 str_max() 函数为例,下面演示了如何为其设置说明文档:

#定义一个比较字符串大小的函数
def str\_max(str1,str2):
    '''
 比较 2 个字符串的大小
 '''
    str = str1 if str1 > str2 else str2
    return str
help(str_max)
#print(str\_max.\_\_doc\_\_)

程序执行结果为:

Help on function str_max in module main:

str_max(str1, str2)
比较 2 个字符串的大小

上面程序中,还可以使用 doc 属性来获取 str_max() 函数的说明文档,即使用最后一行的输出语句,其输出结果为:

比较 2 个字符串的大小

Python函数值传递和引用传递(包括形式参数和实际参数的区别)

通常情况下,定义函数时都会选择有参数的函数形式,函数参数的作用是传递数据给函数,令其对接收的数据做具体的操作处理。

在使用函数时,经常会用到形式参数(简称“形参”)和实际参数(简称“实参”),二者都叫参数,之间的区别是:

  • 形式参数:在定义函数时,函数名后面括号中的参数就是形式参数,例如:
#定义函数时,这里的函数参数 obj 就是形式参数
def demo(obj):
    print(obj)

  • 实际参数:在调用函数时,函数名后面括号中的参数称为实际参数,也就是函数的调用者给函数的参数。例如:
a = "C语言中文网"
#调用已经定义好的 demo 函数,此时传入的函数参数 a 就是实际参数
demo(a)

实参和形参的区别,就如同剧本选主角,剧本中的角色相当于形参,而演角色的演员就相当于实参。

明白了什么是形参和实参后,再来想一个问题,那就是实参是如何传递给形参的呢?

Python 中,根据实际参数的类型不同,函数参数的传递方式可分为 2 种,分别为值传递和引用(地址)传递:

  1. 值传递:适用于实参类型为不可变类型(字符串、数字、元组);
  2. 引用(地址)传递:适用于实参类型为可变类型(列表,字典);

值传递和引用传递的区别是,函数参数进行值传递后,若形参的值发生改变,不会影响实参的值;而函数参数继续引用传递后,改变形参的值,实参的值也会一同改变。

例如,定义一个名为 demo 的函数,分别为传入一个字符串类型的变量(代表值传递)和列表类型的变量(代表引用传递):

def demo(obj) :
    obj += obj
    print("形参值为:",obj)
print("-------值传递-----")
a = "C语言中文网"
print("a的值为:",a)
demo(a)
print("实参值为:",a)
print("-----引用传递-----")
a = [1,2,3]
print("a的值为:",a)
demo(a)
print("实参值为:",a)

运行结果为:

-------值传递-----
a的值为: C语言中文网
形参值为: C语言中文网C语言中文网
实参值为: C语言中文网
-----引用传递-----
a的值为: [1, 2, 3]
形参值为: [1, 2, 3, 1, 2, 3]
实参值为: [1, 2, 3, 1, 2, 3]

分析运行结果不难看出,在执行值传递时,改变形式参数的值,实际参数并不会发生改变;而在进行引用传递时,改变形式参数的值,实际参数也会发生同样的改变。

对于初学者来说,本节只需要了解形参和实参,值传递和引用传递的区别即可。对于函数参数的传递方法,如果读者想深入探究其原因,可阅读《Python函数参数传递机制》一节。

Python函数参数传递机制(超级详细)

通过学习《Python函数值传递和引用传递》一节我们知道,根据实际参数的类型不同,函数参数的传递方式分为值传递和引用传递(又称为地址传递),Python 底层是如何实现它们的呢?Python 中函数参数由实参传递给形参的过程,是由参数传递机制来控制的。

本节将围绕值传递和引用传递,深度剖析它们的底层实现。

Python函数参数的值传递机制

Python 函数参数的值传递,其本质就是将实际参数值复制一份,将其副本传给形参。这意味着,采用值传递方式的函数中,无论其内部对参数值进行如何修改,都不会影响函数外部的实参。

值传递的方式,类似于《西游记》里的孙悟空,它复制一个假孙悟空,假孙悟空具有的能力和真孙悟空相同,可除妖或被砍头。但不管这个假孙悟空遇到什么事,真孙悟空都不会受到任何影响。与此类似,传入函数的是实际参数值的复制品,不管在函数中对这个复制品如何操作,实际参数值本身不会受到任何影响。

下面程序演示了函数参数进行值传递的效果:

def swap(a , b) :
    '''下面代码实现a、b变量的值交换'''
    a, b = b, a
    print("swap函数里,a =", a, " b =", b)
a = 6
b = 9
swap(a , b)
print("函数外部 a =", a ," b =", b)

运行上面程序,将看到如下运行结果:

swap函数里,a = 9 b = 6
函数外部 a = 6 b = 9

从上面的运行结果来看,在 swap() 函数里,经过交换形参 a 和 b 的值,它们的值分别变成了 9 和 6,但函数外部变量 a 和 b 的值依然是 6 和 9。这也证实了,swap() 函数的参数传递机制,采用的是值传递,函数内部使用的形参 a 和 b,和实参 a、b 没有任何关系。

swap() 函数中形参 a 和 b,各自分别是实参 a、b 的复制品。

如果读者依旧不是很理解,下面通过示意图来说明上面程序的执行过程。

上面程序开始定义了 a、b 两个局部变量,这两个变量在内存中的存储示意图如图 1 所示。

主栈区中 a、b 变量存储示意图
图 1 主栈区中 a、b 变量存储示意图

当程序执行 swap() 函数时,系统进入 swap() 函数,并将主程序中的 a、b 变量作为参数值传入 swap() 函数,但传入 swap() 函数的只是 a、b 的副本,而不是 a、b 本身。进入 swap() 函数后,系统中产生了 4 个变量,这 4 个变量在内存中的存储示意图如图 2 所示。

主栈区的变量作为参数值传入swap()函数后存储示意图
图 2 主栈区的变量作为参数值传入 swap() 函数后存储示意图

当在主程序中调用 swap() 函数时,系统分别为主程序和 swap() 函数分配两块栈区,用于保存它们的局部变量。将主程序中的 a、b 变量作为参数值传入 swap() 函数,实际上是在 swap() 函数栈区中重新产生了两个变量 a、b,并将主程序栈区中 a、b 变量的值分别赋值给 swap() 函数栈区中的 a、b 参数(就是对 swap() 函数的 a、b 两个变量进行初始化)。此时,系统存在两个 a 变量、两个 b 变量,只是存在于不同的栈区中而己。

程序在 swap() 函数中交换 a、b 两个变量的值,实际上是对图 2 中灰色区域的 a、b 变量进行交换。交换结束后,输出 swap() 函数中 a、b 变量的值,可以看到 a 的值为 9,b 的值为 6,此时在内存中的存储示意图如图 3 所示。

swap()函数中a、b 交换之后的存储示意图
图 3 swap() 函数中 a、b 交换之后的存储示意图

对比图 3 与图 1,可以看到两个示意图中主程序栈区中 a、b 的值并未有任何改变,程序改变的只是 swap() 函数栈区中 a、b 的值。这就是值传递的实质:当系统开始执行函数时,系统对形参执行初始化,就是把实参变量的值赋给函数的形参变量,在函数中操作的并不是实际的实参变量。

Python函数参数的引用传递

如果实际参数的数据类型是可变对象(列表、字典),则函数参数的传递方式将采用引用传递方式。

下面程序示范了引用传递参数的效果:

def swap(dw):
    # 下面代码实现dw的a、b两个元素的值交换
    dw['a'], dw['b'] = dw['b'], dw['a']
    print("swap函数里,a =", dw['a'], " b =", dw['b'])
dw = {'a': 6, 'b': 9}
swap(dw)
print("外部 dw 字典中,a =", dw['a']," b =",dw['b'])

运行上面程序,将看到如下运行结果:

swap 函数里,a = 9 b = 6
外部 dw 字典中,a = 9 b = 6

从上面的运行结果来看,在 swap() 函数里,dw 字典的 a、b 两个元素的值被交换成功。不仅如此,当 swap() 函数执行结束后,主程序中 dw 字典的 a、b 两个元素的值也被交换了。

注意,这里这很容易造成一种错觉,读者可能认为,在此 swap() 函数中,使用 dw 字典,就是外界的 dw 字典本身,而不是他的复制品。这只是一种错觉,实际上,引用传递的底层实现,依旧使用的是值传递的方式。下面还是结合示意图来说明程序的执行过程。

程序开始创建了一个字典对象,并定义了一个 dw 引用变量(其实就是一个指针)指向字典对象,这意味着此时内存中有两个东西:对象本身和指向该对象的引用变量。此时在系统内存中的存储示意图如图 4 所示:

主程序创建了字典对象后存储示意图
图 4 主程序创建了字典对象后存储示意图

接下来主程序开始调用 swap() 函数,在调用 swap() 函数时,dw 变量作为参数传入 swap() 函数,这里依然采用值传递方式:把主程序中 dw 变量的值赋给 swap() 函数的 dw 形参,从而完成 swap() 函数的 dw 参数的初始化。值得指出的是,主程序中的 dw 是一个引用变量(也就是一个指针),它保存了字典对象的地址值,当把 dw 的值赋给 swap() 函数的 dw 参数后,就是让 swap() 函数的 dw 参数也保存这个地址值,即也会引用到同一个字典对象。图 5 显示了 dw 字典传入 swap() 函数后的存储示意图。

dw字典传入swap()函数后存储示意图
图 5 dw 字典传入 swap() 函数后存储示意图

从图 5 来看,这种参数传递方式是不折不扣的值传递方式,系统一样复制了dw 的副本传入 swap() 函数。但由于 dw 只是一个引用变量,因此系统复制的是 dw 变量,并未复制字典本身。

当程序在 swap() 函数中操作 dw 参数时,由于 dw 只是一个引用变量,故实际操作的还是字典对象。此时,不管是操作主程序中的 dw 变量,还是操作 swap() 函数里的 dw 参数,其实操作的都是它们共同引用的字典对象,它们引用的是同一个字典对象。因此,当在 swap() 函数中交换 dw 参数所引用字典对象的 a、b 两个元素的值后,可以看到在主程序中 dw 变量所引用字典对象的 a、b 两个元素的值也被交换了。

为了更好地证明主程序中的 dw 和 swap() 函数中的 dw 是两个变量,在 swap() 函数的最后一行增加如下代码:

#把dw 直接赋值为None,让它不再指向任何对象
dw = None

运行上面代码,结果是 swap() 函数中的 dw 变量不再指向任何对象,程序其他地方没有任何改变。主程序调用 swap() 函数后,再次访问 dw 变量的 a、b 两个元素,依然可以输出 9、6。可见,主程序中的 dw 变量没有受到任何影响。实际上,当在 swap() 函数中增加“dw =None”代码后,在内存中的存储示意图如图 6 所示。

将swap()函数中的dw赋值为None 后存储示意图
图 6 将 swap() 函数中的 dw 赋值为 None 后存储示意图

从图 6 来看,把 swap() 函数中的 dw 赋值为 None 后,在 swap() 函数中失去了对字典对象的引用,不可再访问该字典对象。但主程序中的 dw 变量不受任何影响,依然可以引用该字典对象,所以依然可以输出字典对象的 a、b 元素的值。

通过上面介绍可以得出如下两个结论:

  1. 不管什么类型的参数,在 Python 函数中对参数直接使用“=”符号赋值是没用的,直接使用“=”符号赋值并不能改变参数。
  2. 如果需要让函数修改某些数据,则可以通过把这些数据包装成列表、字典等可变对象,然后把列表、字典等可变对象作为参数传入函数,在函数中通过列表、字典的方法修改它们,这样才能改变这些数据。

什么是位置参数,Python位置参数

位置参数,有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中,换句话说,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。

实参和形参数量必须一致

在调用函数,指定的实际参数的数量,必须和形式参数的数量一致(传多传少都不行),否则 Python 解释器会抛出 TypeError 异常,并提示缺少必要的位置参数。

例如:

def girth(width , height):
    return 2 \* (width + height)
#调用函数时,必须传递 2 个参数,否则会引发错误
print(girth(3))

运行结果为:

Traceback (most recent call last):
File “C:\Users\mengma\Desktop\1.py”, line 4, in
print(girth(3))
TypeError: girth() missing 1 required positional argument: ‘height’

可以看到,抛出的异常类型为 TypeError,具体是指 girth() 函数缺少一个必要的 height 参数。

同样,多传参数也会抛出异常:

def girth(width , height):
    return 2 \* (width + height)
#调用函数时,必须传递 2 个参数,否则会引发错误
print(girth(3,2,4))

运行结果为:

Traceback (most recent call last):
File “C:\Users\mengma\Desktop\1.py”, line 4, in
print(girth(3,2,4))
TypeError: girth() takes 2 positional arguments but 3 were given

通过 TypeErroe 异常信息可以知道,girth() 函数本只需要 2 个参数,但是却传入了 3 个参数。

实参和形参位置必须一致

在调用函数时,传入实际参数的位置必须和形式参数位置一一对应,否则会产生以下 2 种结果:

  1. 抛出 TypeError 异常

当实际参数类型和形式参数类型不一致,并且在函数种,这两种类型之间不能正常转换,此时就会抛出 TypeError 异常。

例如:

def area(height,width):
    return height\*width/2
print(area("C语言中文网",3))

输出结果为:

Traceback (most recent call last):
File “C:\Users\mengma\Desktop\1.py”, line 3, in
print(area(“C语言中文网”,3))
File “C:\Users\mengma\Desktop\1.py”, line 2, in area
return height*width/2
TypeError: unsupported operand type(s) for /: ‘str’ and ‘int’

以上显示的异常信息,就是因为字符串类型和整形数值做除法运算。
2. 产生的结果和预期不符

调用函数时,如果指定的实际参数和形式参数的位置不一致,但它们的数据类型相同,那么程序将不会抛出异常,只不过导致运行结果和预期不符。

例如,设计一个求梯形面积的函数,并利用此函数求上底为 4cm,下底为 3cm,高为 5cm 的梯形的面积。但如果交互高和下低参数的传入位置,计算结果将导致错误:

def area(upper_base,lower_bottom,height):
    return (upper_base+lower_bottom)\*height/2
print("正确结果为:",area(4,3,5))
print("错误结果为:",area(4,5,3))

运行结果为:

正确结果为: 17.5
错误结果为: 13.5

因此,在调用函数时,一定要确定好位置,否则很有可能产生类似示例中的这类错误,还不容易发现。

Python函数关键字参数及用法

目前为止,我们使用函数时所用的参数都是位置参数,即传入函数的实际参数必须与形式参数的数量和位置对应。而本节将介绍的关键字参数,则可以避免牢记参数位置的麻烦,令函数的调用和参数传递更加灵活方便。

关键字参数是指使用形式参数的名字来确定输入的参数值。通过此方式指定函数实参时,不再需要与形参的位置完全一致,只要将参数名写正确即可。

因此,Python 函数的参数名应该具有更好的语义,这样程序可以立刻明确传入函数的每个参数的含义。

例如,在下面的程序中就使用到了关键字参数的形式给函数传参:

def dis\_str(str1,str2):
    print("str1:",str1)
    print("str2:",str2)
#位置参数
dis_str("http://c.biancheng.net/python/","http://c.biancheng.net/shell/")
#关键字参数
dis_str("http://c.biancheng.net/python/",str2="http://c.biancheng.net/shell/")
dis_str(str2="http://c.biancheng.net/python/",str1="http://c.biancheng.net/shell/")

程序执行结果为:

str1: http://c.biancheng.net/python/
str2: http://c.biancheng.net/shell/
str1: http://c.biancheng.net/python/
str2: http://c.biancheng.net/shell/
str1: http://c.biancheng.net/shell/
str2: http://c.biancheng.net/python/

可以看到,在调用有参函数时,既可以根据位置参数来调用,也可以使用关键字参数(程序中第 8 行)来调用。在使用关键字参数调用时,可以任意调换参数传参的位置。

当然,还可以像第 7 行代码这样,使用位置参数和关键字参数混合传参的方式。但需要注意,混合传参时关键字参数必须位于所有的位置参数之后。也就是说,如下代码是错误的:

# 位置参数必须放在关键字参数之前,下面代码错误
dis_str(str1="http://c.biancheng.net/python/","http://c.biancheng.net/shell/")

Python 解释器会报如下错误:

SyntaxError: positional argument follows keyword argument

Python函数默认参数设置(超级详细)

我们知道,在调用函数时如果不指定某个参数,Python 解释器会抛出异常。为了解决这个问题,Python 允许为参数设置默认值,即在定义函数时,直接给形式参数指定一个默认值。这样的话,即便调用函数时没有给拥有默认值的形参传递参数,该参数可以直接使用定义函数时设置的默认值。

Python 定义带有默认值参数的函数,其语法格式如下:

def 函数名(…,形参名,形参名=默认值):
代码块

注意,在使用此格式定义函数时,指定有默认值的形式参数必须在所有没默认值参数的最后,否则会产生语法错误。

下面程序演示了如何定义和调用有默认参数的函数:

#str1没有默认参数,str2有默认参数
def dis\_str(str1,str2 = "http://c.biancheng.net/python/"):
    print("str1:",str1)
    print("str2:",str2)
dis_str("http://c.biancheng.net/shell/")
dis_str("http://c.biancheng.net/java/","http://c.biancheng.net/golang/")

运行结果为:

str1: http://c.biancheng.net/shell/
str2: http://c.biancheng.net/python/
str1: http://c.biancheng.net/java/
str2: http://c.biancheng.net/golang/

上面程序中,dis_str() 函数有 2 个参数,其中第 2 个设有默认参数。这意味着,在调用 dis_str() 函数时,我们可以仅传入 1 个参数,此时该参数会传给 str1 参数,而 str2 会使用默认的参数,如程序中第 6 行代码所示。

当然在调用 dis_str() 函数时,也可以给所有的参数传值(如第 7 行代码所示),这时即便 str2 有默认值,它也会优先使用传递给它的新值。

同时,结合关键字参数,以下 3 种调用 dis_str() 函数的方式也是可以的:

dis_str(str1 = "http://c.biancheng.net/shell/")
dis_str("http://c.biancheng.net/java/",str2 = "http://c.biancheng.net/golang/")
dis_str(str1 = "http://c.biancheng.net/java/",str2 = "http://c.biancheng.net/golang/")

再次强调,当定义一个有默认值参数的函数时,有默认值的参数必须位于所有没默认值参数的后面。因此,下面例子中定义的函数是不正确的:

#语法错误
def dis\_str(str1="http://c.biancheng.net/python/",str2,str3):
    pass

显然,str1 设有默认值,而 str2 和 str3 没有默认值,因此 str1 必须位于 str2 和 str3 之后。

有读者可能会问,对于自己自定义的函数,可以轻易知道哪个参数有默认值,但如果使用 Python 提供的内置函数,又或者其它第三方提供的函数,怎么知道哪些参数有默认值呢?

Pyhton 中,可以使用“函数名.defaults”查看函数的默认值参数的当前值,其返回值是一个元组。以本节中的 dis_str() 函数为例,在其基础上,执行如下代码:

print(dis_str.__defaults__)

程序执行结果为:

(‘http://c.biancheng.net/python/’,)

Python函数可变参数(*args,**kwargs)详解

Python 在定义函数时也可以使用可变参数,即允许定义参数个数可变的函数。这样当调用该函数时,可以向其传入任意多个参数。

可变参数,又称不定长参数,即传入函数中的实际参数可以是任意多个。Python 定义可变参数,主要有以下 2 种形式。

1) 可变参数:形参前添加一个 ‘*’

此种形式的语法格式如下所示:

*args

args 表示创建一个名为 args 的空元组,该元组可接受任意多个外界传入的非关键字实参。

下面程序演示了如何定义一个参数可变的函数:

# 定义了支持参数收集的函数
def dis\_str(home, \*str) :
    print(home)
    # 输出str元组中的元素
    print("str=",str)
    for s in str :
        print(s)
#可传入任何多个参数
dis_str("http://c.biancheng.net","http://c.biancheng.net/python/","http://c.biancheng.net/shell/")

程序执行结果为:

http://c.biancheng.net
str= (‘http://c.biancheng.net/python/’, ‘http://c.biancheng.net/shell/’)
http://c.biancheng.net/python/
http://c.biancheng.net/shell/

上面程序中,dis_str() 函数的最后一个参数就是 str 元组,这样在调用该函数时,除了前面位置参数接收对应位置的实参外,其它非关键字参数都会由 str 元组接收。

当然,可变参数并不一定必须为最后一个函数参数,例如修改 dis_str() 函数为:

# 定义了支持参数收集的函数
def dis\_str(\*str,home) :
    print(home)
    # 输出str元组中的元素
    print("str=",str)
    for s in str :
        print(s)
dis_str("http://c.biancheng.net","http://c.biancheng.net/python/",home="http://c.biancheng.net/shell/")

可以看到,str 可变参数作为 dis_str() 函数的第一个参数。但需要注意的是,在调用该函数时,必须以关键字参数的形式给普通参数传值,否则 Python 解释器会把所有参数都优先传给可变参数,如果普通参数没有默认值,就会报错。

也就是说,下面代码调用上面的 dia_str() 函数,是不对的:

dis_str("http://c.biancheng.net","http://c.biancheng.net/python/","http://c.biancheng.net/shell/")

Python 解释器会提供如下报错信息:

TypeError: dis_str() missing 1 required keyword-only argument: ‘home’

翻译过来就是我们没有给 home 参数传值。当然,如果 home 参数有默认参数,则此调用方式是可行的。

2) 可变参数:形参前添加两个’*’

这种形式的语法格式如下:

**kwargs

**kwargs 表示创建一个名为 kwargs 的空字典,该字典可以接收任意多个以关键字参数赋值的实际参数。

例如如下代码:

# 定义了支持参数收集的函数
def dis\_str(home,\*str,\*\*course) :
    print(home)
    print(str)
    print(course)
#调用函数
dis_str("C语言中文网",\
        "http://c.biancheng.net",\
        "http://c.biancheng.net/python/",\
        shell教程="http://c.biancheng.net/shell/",\
        go教程="http://c.biancheng.net/golang/",\
        java教程="http://c.biancheng.net/java/")

程序输出结果为:

C语言中文网
(‘http://c.biancheng.net’, ‘http://c.biancheng.net/python/’)
{‘shell教程’: ‘http://c.biancheng.net/shell/’, ‘go教程’: ‘http://c.biancheng.net/golang/’, ‘java教程’: ‘http://c.biancheng.net/java/’}

上面程序在调用 dis_str() 函数时,第 1 个参数传递给 home 参数,第 2、3 个非关键字参数传递给 str 元组,最后 2 个关键字参数将由 course 字典接收。

注意,*args 可变参数的值默认是空元组,**kwargs 可变参数的值默认是空字典。因此,在调用具有可变参数的函数时,不一定非要给它们传值。以调用 dis_str(home, *str, **course) 为例,下面的调用方式也是正确的:

dis_str(home="http://c.biancheng.net/shell/")

程序执行结果为:

http://c.biancheng.net/shell/
()
{}

Python逆向参数收集详解(进阶必读)

前面章节中介绍了,Python 支持定义具有可变参数的函数,即该函数可以接收任意多个参数,其中非关键字参数会集中存储到元组参数(*args)中,而关键字参数则集中存储到字典参数(**kwargs)中,这个过程可称为参数收集。

不仅如此,Python 还支持逆向参数收集,即直接将列表、元组、字典作为函数参数,Python 会将其进行拆分,把其中存储的元素按照次序分给函数中的各个形参。

在以逆向参数收集的方式向函数参数传值时,Pyhon 语法规定,当传入列表或元组时,其名称前要带一个 * 号,当传入字典时,其名称前要带有 2 个 * 号。

举个例子:

def dis\_str(name,add) :
    print("name:",name)
    print("add",add)
data = ["Python教程","http://c.biancheng.net/python/"]
#使用逆向参数收集方式传值
dis_str(\*data)

程序执行结果为:

name: Python教程
add http://c.biancheng.net/python/

再举个例子:

def dis\_str(name,add) :
    print("name:",name)
    print("add:",add)
data = {'name':"Python教程",'add':"http://c.biancheng.net/python/"}
#使用逆向参数收集方式传值
dis_str(\*\*data)

程序执行结果为:

name: Python教程
add: http://c.biancheng.net/python/

此外,以逆向参数收集的方式,还可以给拥有可变参数的函数传参,例如:

def dis\_str(name,\*add) :
    print("name:",name)
    print("add:",add)
data = ["http://c.biancheng.net/python/",\
        "http://c.biancheng.net/shell/",\
        "http://c.biancheng.net/golang/"]
#使用逆向参数收集方式传值
dis_str("Python教程",\*data)

程序执行结果为:

name: Python教程
add: (‘http://c.biancheng.net/python/’, ‘http://c.biancheng.net/shell/’, ‘http://c.biancheng.net/golang/’)

上面程序中,也同样可以用逆向参数收集的方式给 name 参数传值,只需要将 “python教程” 放到 data 列表中第一个位置即可。也就是说,上面程序中,以下面代码调用 dis_str() 函数的方式也是可行的:

data = ["Python教程",\
        "http://c.biancheng.net/python/",\
        "http://c.biancheng.net/shell/",\
        "http://c.biancheng.net/golang/"]
#使用逆向参数收集方式传值
dis_str(\*data)

执行此程序,会发现其输出结果和上面一致。

再次强调,如果使用逆向参数收集的方式,必须注意 * 号的添加。以逆向收集列表为例,如果传参时其列表名前不带 * 号,则 Python 解释器会将整个列表作为参数传递给一个参数。例如:

def dis\_str(name,\*add) :
    print("name:",name)
    print("add:",add)
data = ["Python教程",\
        "http://c.biancheng.net/python/",\
        "http://c.biancheng.net/shell/",\
        "http://c.biancheng.net/golang/"]
dis_str(data)

程序执行结果为:

name: [‘Python教程’, ‘http://c.biancheng.net/python/’, ‘http://c.biancheng.net/shell/’, ‘http://c.biancheng.net/golang/’]
add: ()

Python None(空值)及用法

Python 中,有一个特殊的常量 None(N 必须大写)。和 False 不同,它不表示 0,也不表示空字符串,而表示没有值,也就是空值。

这里的空值并不代表空对象,即 None 和 []、“” 不同:

None is []
False
None is “”
False

None 有自己的数据类型,我们可以在 IDLE 中使用 type() 函数查看它的类型,执行代码如下:

type(None)
<class ‘NoneType’>

可以看到,它属于 NoneType 类型。

需要注意的是,None 是 NoneType 数据类型的唯一值(其他编程语言可能称这个值为 null、nil 或 undefined),也就是说,我们不能再创建其它 NoneType 类型的变量,但是可以将 None 赋值给任何变量。如果希望变量中存储的东西不与任何其它值混淆,就可以使用 None。

除此之外,None 常用于 assert、判断以及函数无返回值的情况。举个例子,在前面章节中我们一直使用 print() 函数输出数据,其实该函数的返回值就是 None。因为它的功能是在屏幕上显示文本,根本不需要返回任何值,所以 print() 就返回 None。

spam = print(‘Hello!’)
Hello!
None == spam
True

另外,对于所有没有 return 语句的函数定义,Python 都会在末尾加上 return None,使用不带值的 return 语句(也就是只有 return 关键字本身),那么就返回 None。

Python return函数返回值详解

到目前为止,我们创建的函数都只是对传入的数据进行了处理,处理完了就结束。但实际上,在某些场景中,我们还需函数将处理的结果反馈回来,就好像主管向下级员工下达命令,让其去打印文件,员工打印好文件后并没有完成任务,还需要将文件交给主管。

Python中,用 def 语句创建函数时,可以用 return 语句指定应该返回的值,该返回值可以是任意类型。需要注意的是,return 语句在同一函数中可以出现多次,但只要有一个得到执行,就会直接结束函数的执行。

函数中,使用 return 语句的语法格式如下:

return [返回值]

其中,返回值参数可以指定,也可以省略不写(将返回空值 None)。

【例 1】

def add(a,b):
    c = a + b
    return c
#函数赋值给变量
c = add(3,4)
print(c)
#函数返回值作为其他函数的实际参数
print(add(3,4))

运行结果为:

7
7

本例中,add() 函数既可以用来计算两个数的和,也可以连接两个字符串,它会返回计算的结果。

通过 return 语句指定返回值后,我们在调用函数时,既可以将该函数赋值给一个变量,用变量保存函数的返回值,也可以将函数再作为某个函数的实际参数。

【例 2】

def isGreater0(x):
    if x > 0:
        return True
    else:
        return False
print(isGreater0(5))
print(isGreater0(0))

运行结果为:

True
False

可以看到,函数中可以同时包含多个 return 语句,但需要注意的是,最终真正执行的做多只有 1 个,且一旦执行,函数运行会立即结束。

以上实例中,我们通过 return 语句,都仅返回了一个值,但其实通过 return 语句,可以返回多个值,读者可以阅读《Python函数返回多个值》一节做详细了解。

Python函数返回多个值的方法(入门必读)

通常情况下,一个函数只有一个返回值,实际上 Python 也是如此,只不过 Python 函数能以返回列表或者元组的方式,将要返回的多个值保存到序列中,从而间接实现返回多个值的目的。

因此,实现 Python 函数返回多个值,有以下 2 种方式:

  1. 在函数中,提前将要返回的多个值存储到一个列表或元组中,然后函数返回该列表或元组;
  2. 函数直接返回多个值,之间用逗号( , )分隔,Python 会自动将多个值封装到一个元组中,其返回值仍是一个元组。

下面程序演示了以上 2 种实现方法:

def retu\_list() :
    add = ["http://c.biancheng.net/python/",\
            "http://c.biancheng.net/shell/",\
            "http://c.biancheng.net/golang/"]
    return add
def retu\_tuple() :
    return "http://c.biancheng.net/python/",\
           "http://c.biancheng.net/golang/",\
           "http://c.biancheng.net/golang/"
print("retu\_list = ",retu_list())
print("retu\_tuple = ",retu_tuple())

程序执行结果为:

retu_list = [‘http://c.biancheng.net/python/’, ‘http://c.biancheng.net/shell/’, ‘http://c.biancheng.net/golang/’]
retu_tuple = (‘http://c.biancheng.net/python/’, ‘http://c.biancheng.net/golang/’, ‘http://c.biancheng.net/golang/’)

在此基础上,我们可以利用 Python 提供的序列解包功能,之间使用对应数量的变量,直接接收函数返回列表或元组中的多个值。这里以 retu_list() 为例:

def retu\_list() :
    add = ["http://c.biancheng.net/python/",\
            "http://c.biancheng.net/shell/",\
            "http://c.biancheng.net/golang/"]
    return add
pythonadd,shelladd,golangadd = retu_list()
print("pythonadd=",pythonadd)
print("shelladd=",shelladd)
print("golangadd=",golangadd)

程序执行结果为:

pythonadd= http://c.biancheng.net/python/
shelladd= http://c.biancheng.net/shell/
golangadd= http://c.biancheng.net/golang/

Python函数递归(带实例演示)

一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。

递归函数不是 Python 语言的专利,C/C++、Java、C#、JavaScript、PHP 等其他编程语言也都支持递归函数。

下面我们通过一个实例,看看递归函数到底是如何运作的。

有这样一个数学题。己知有一个数列:f(0) = 1,f(1) = 4,f(n + 2) = 2*f(n+ 1) +f(n),其中 n 是大于 0 的整数,求 f(10) 的值。这道题可以使用递归来求得。下面程序将定义一个 fn() 函数,用于计算 f(10) 的值。

def fn(n) :
    if n == 0 :
        return 1
    elif n == 1 :
        return 4
    else :
        # 函数中调用它自身,就是函数递归
        return 2 \* fn(n - 1) + fn(n - 2)
# 输出fn(10)的结果
print("fn(10)的结果是:", fn(10))

在上面的 fn() 函数体中再次调用了 fn() 函数,这就是函数递归。注意在 fn() 函数体中调用 fn 的形式:

return 2 * fn(n - 1) + fn(n - 2)

对于 fn(10),即等于 2fn(9)+fn(8),其中 fn(9) 又等于 2fn(8)+fn(7)……依此类推,最终会计算到 fn(2) 等于 2*fn(1)+fn(0),即 fn(2) 是可计算的,这样递归带来的隐式循环就有结束的时候,然后一路反算回去,最后就可以得到 fn(10) 的值。

仔细看上面递归的过程,当一个函数不断地调用它自身时,必须在某个时刻函数的返回值是确定的,即不再调用它自身:否则,这种递归就变成了无穷递归,类似于死循环。因此,在定义递归函数时有一条最重要的规定: 递归一定要向已知方向进行。

例如,如果把上面数学题改为如此。己知有一个数列:f(20)=1,f(21)=4,f(n + 2)=2*f(n+1)+f(n),其中 n 是大于 0 的整数,求 f(10) 的值。那么 f(10) 的函数体应该改为如下形式:

def fn(n) :
    if n == 20 :
        return 1
    elif n == 21 :
        return 4
    else :
        # 函数中调用它自身,就是函数递归
        return fn(n + 2) - 2\*fn(n + 1)

从上面的 fn() 函数来看,当程序要计算 fn(10) 的值时,fn(10) 等于 fn(12)-2fn(11),而 fn(11) 等于 fn(13)-2fn(12)……依此类推,直到 fn(19) 等于 fn(21)-2fn(20),此时就可以得到 fn(19) 的值,然后依次反算到 fn(10) 的值。这就是递归的重要规则:对于求 fn(10) 而言,如果 fn(0) 和 fn(1) 是已知的,则应该采用 fn(n)=2fn(n-1)+fn(n-2) 的形式递归,因为小的一端已知;如果 fn(20) 和 fn(21) 是已知的,则应该采用 fn(n)=fn(n+2)-2*fn(n+1) 的形式递归,因为大的一端已知。

递归是非常有用的,例如程序希望遍历某个路径下的所有文件,但这个路径下的文件夹的深度是未知的,那么就可以使用递归来实现这个需求。系统可定义一个函数,该函数接收一个文件路径作为参数,该函数可遍历出当前路径下的所有文件和文件路径,即在该函数的函数体中再次调用函数自身来处理该路径下的所有文件路径。

总之,只要在一个函数的函数体中调用了函数自身,就是函数递归。递归一定要向已知方向进行。

Python变量作用域(全局变量和局部变量)

所谓作用域(Scope),就是变量的有效范围,就是变量可以在哪个范围以内使用。有些变量可以在整段代码的任意位置使用,有些变量只能在函数内部使用,有些变量只能在 for 循环内部使用。

变量的作用域由变量的定义位置决定,在不同位置定义的变量,它的作用域是不一样的。本节我们只讲解两种变量,局部变量和全局变量。

Python局部变量

在函数内部定义的变量,它的作用域也仅限于函数内部,出了函数就不能使用了,我们将这样的变量称为局部变量(Local Variable)。

要知道,当函数被执行时,Python 会为其分配一块临时的存储空间,所有在函数内部定义的变量,都会存储在这块空间中。而在函数执行完毕后,这块临时存储空间随即会被释放并回收,该空间中存储的变量自然也就无法再被使用。

举个例子:

def demo():    add = "http://c.biancheng.net/python/"    print("函数内部 add =",add)demo()print("函数外部 add =",add)

程序执行结果为:

函数内部 add = http://c.biancheng.net/python/
Traceback (most recent call last):
File “C:\Users\mengma\Desktop\file.py”, line 6, in
print(“函数外部 add =”,add)
NameError: name ‘add’ is not defined

可以看到,如果试图在函数外部访问其内部定义的变量,Python 解释器会报 NameError 错误,并提示我们没有定义要访问的变量,这也证实了当函数执行完毕后,其内部定义的变量会被销毁并回收。

值得一提的是,函数的参数也属于局部变量,只能在函数内部使用。例如:

def demo(name,add):    print("函数内部 name =",name)    print("函数内部 add =",add)demo("Python教程","http://c.biancheng.net/python/")print("函数外部 name =",name)print("函数外部 add =",add)

程序执行结果为:

函数内部 name = Python教程
函数内部 add = http://c.biancheng.net/python/
Traceback (most recent call last):
File “C:\Users\mengma\Desktop\file.py”, line 7, in
print(“函数外部 name =”,name)
NameError: name ‘name’ is not defined

由于 Python 解释器是逐行运行程序代码,由此这里仅提示给我“name 没有定义”,实际上在函数外部访问 add 变量也会报同样的错误。

Python全局变量

除了在函数内部定义变量,Python 还允许在所有函数的外部定义变量,这样的变量称为全局变量(Global Variable)。

和局部变量不同,全局变量的默认作用域是整个程序,即全局变量既可以在各个函数的外部使用,也可以在各函数内部使用。

定义全局变量的方式有以下 2 种:

  • 在函数体外定义的变量,一定是全局变量,例如:
add = "http://c.biancheng.net/shell/"
def text():
    print("函数体内访问:",add)
text()
print('函数体外访问:',add)

运行结果为:

函数体内访问: http://c.biancheng.net/shell/
函数体外访问: http://c.biancheng.net/shell/

  • 在函数体内定义全局变量。即使用 global 关键字对变量进行修饰后,该变量就会变为全局变量。例如:
def text():
    global add
    add= "http://c.biancheng.net/java/"
    print("函数体内访问:",add)
text()
print('函数体外访问:',add)

运行结果为:

函数体内访问: http://c.biancheng.net/java/
函数体外访问: http://c.biancheng.net/java/

注意,在使用 global 关键字修饰变量名时,不能直接给变量赋初值,否则会引发语法错误。

获取指定作用域范围中的变量

在一些特定场景中,我们可能需要获取某个作用域内(全局范围内或者局部范围内)所有的变量,Python 提供了以下 3 种方式:

1) globals()函数

globals() 函数为 Python 的内置函数,它可以返回一个包含全局范围内所有变量的字典,该字典中的每个键值对,键为变量名,值为该变量的值。

举个例子:

#全局变量
Pyname = "Python教程"
Pyadd = "http://c.biancheng.net/python/"
def text():
    #局部变量
    Shename = "shell教程"
    Sheadd= "http://c.biancheng.net/shell/"
print(globals())

程序执行结果为:

{ … , ‘Pyname’: ‘Python教程’, ‘Pyadd’: ‘http://c.biancheng.net/python/’, …}

注意,globals() 函数返回的字典中,会默认包含有很多变量,这些都是 Python 主程序内置的,读者暂时不用理会它们。

可以看到,通过调用 globals() 函数,我们可以得到一个包含所有全局变量的字典。并且,通过该字典,我们还可以访问指定变量,甚至如果需要,还可以修改它的值。例如,在上面程序的基础上,添加如下语句:

print(globals()['Pyname'])
globals()['Pyname'] = "Python入门教程"
print(Pyname)

程序执行结果为:

Python教程
Python入门教程

2) locals()函数

locals() 函数也是 Python 内置函数之一,通过调用该函数,我们可以得到一个包含当前作用域内所有变量的字典。这里所谓的“当前作用域”指的是,在函数内部调用 locals() 函数,会获得包含所有局部变量的字典;而在全局范文内调用 locals() 函数,其功能和 globals() 函数相同。

举个例子:

#全局变量
Pyname = "Python教程"
Pyadd = "http://c.biancheng.net/python/"
def text():
    #局部变量
    Shename = "shell教程"
    Sheadd= "http://c.biancheng.net/shell/"
    print("函数内部的 locals:")
    print(locals())
text()
print("函数外部的 locals:")
print(locals())

程序执行结果为:

函数内部的 locals:
{‘Sheadd’: ‘http://c.biancheng.net/shell/’, ‘Shename’: ‘shell教程’}
函数外部的 locals:
{… , ‘Pyname’: ‘Python教程’, ‘Pyadd’: ‘http://c.biancheng.net/python/’, … }

当使用 locals() 函数获取所有全局变量时,和 globals() 函数一样,其返回的字典中会默认包含有很多变量,这些都是 Python 主程序内置的,读者暂时不用理会它们。

注意,当使用 locals() 函数获得所有局部变量组成的字典时,可以向 globals() 函数那样,通过指定键访问对应的变量值,但无法对变量值做修改。例如:

#全局变量
Pyname = "Python教程"
Pyadd = "http://c.biancheng.net/python/"
def text():
    #局部变量
    Shename = "shell教程"
    Sheadd= "http://c.biancheng.net/shell/"
    print(locals()['Shename'])
    locals()['Shename'] = "shell入门教程"
    print(Shename)
text()

程序执行结果为:

shell教程
shell教程

显然,locals() 返回的局部变量组成的字典,可以用来访问变量,但无法修改变量的值。

3) vars(object)

vars() 函数也是 Python 内置函数,其功能是返回一个指定 object 对象范围内所有变量组成的字典。如果不传入object 参数,vars() 和 locals() 的作用完全相同。

由于目前读者还未学习 Python 类和对象,因此初学者可先跳过该函数的学习,等学完 Python 类和对象之后,再回过头来学习该函数。

举个例子:

 #全局变量
Pyname = "Python教程"
Pyadd = "http://c.biancheng.net/python/"
class Demo:
    name = "Python 教程"
    add = "http://c.biancheng.net/python/"
print("有 object:")
print(vars(Demo))
print("无 object:")
print(vars())

程序执行结果为:

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

一、Python所有方向的学习路线

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

二、学习软件

工欲善其事必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。

三、全套PDF电子书

书籍的好处就在于权威和体系健全,刚开始学习的时候你可以只看视频或者听某个人讲课,但等你学完之后,你觉得你掌握了,这时候建议还是得去看一下书籍,看权威技术书籍也是每个程序员必经之路。

四、入门学习视频

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。

五、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

六、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里无偿获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值