十六、自定义函数
除了《第六章内置函数详解》所讲得内置函数外,Python 还支持自定义函数,即将一段有规律的、可重复使用的代码定义成函数,从而达到一次编写、多次调用的目的。
16.1 Python函数
举个例子,前面学习了 len() 函数,通过它我们可以直接获得一个字符串的长度。我们不妨设想一下,如果没有 len() 函数,要想获取一个字符串的长度,该如何实现呢?请看下面的代码:
n=0
for c in "https://editor.csdn.net/md?articleId=125357340":
n = n + 1
print(n)
程序执行结果为:
46
要知道,获取一个字符串长度是常用的功能,一个程序中就可能用到很多次,如果每次都写这样一段重复的代码,不但费时费力、容易出错,而且交给别人时也很麻烦。
所以 Python 提供了一个功能,即允许我们将常用的代码以固定的格式封装(包装)成一个独立的模块,只要知道这个模块的名字就可以重复使用它,这个模块就叫做函数(Function)。
比如,在程序中定义了一段代码,这段代码用于实现一个特定的功能。问题来了,如果下次需要实现同样的功能,难道要把前面定义的代码复制一次?如果这样做实在太傻了,这意味着每次当程序需要实现该功能时,都要将前面定义的代码复制一次。正确的做法是,将实现特定功能的代码定义成一个函数,每次当程序需要实现该功能时,只要执行(调用)该函数即可。
其实,函数的本质就是一段有特定功能、可以重复使用的代码,这段代码已经被提前编写好了,并且为其起一个“好听”的名字。在后续编写程序过程中,如果需要同样的功能,直接通过起好的名字就可以调用这段代码。
下面演示了如何将我们自己实现的 len() 函数封装成一个函数:
#自定义 len() 函数
def my_len(str):
length = 0
for c in str:
length = length + 1
return length
#调用自定义的 my_len() 函数
length = my_len("https://editor.csdn.net/md?articleId=125357340")
print(length)
#再次调用 my_len() 函数
length = my_len("https://editor.csdn.net/")
print(length)
程序执行结果为:
46
24
如果读者接触过其他编程语言中的函数,以上对于函数的描述,肯定不会陌生。但需要注意的一点是,和其他编程语言中函数相同的是,Python 函数支持接收多个( ≥0 )参数,不同之处在于,Python 函数还支持返回多个( ≥0 )值。
比如,上面程序中,我们自己封装的 my_len(str) 函数,在定义此函数时,我们为其设置了 1 个 str 参数,同时该函数经过内部处理,会返回给我们 1 个 length 值。
通过分析 my_len() 函数这个实例不难看出,函数的使用大致分为 2 步,分别是定义函数和调用函数。接下来一一为读者进行详细的讲解。
16.1.1 Python函数的定义
定义函数,也就是创建一个函数,可以理解为创建一个具有某些用途的工具。定义函数需要用 def 关键字实现,具体的语法格式如下:
def 函数名(参数列表):
//实现特定功能的多行代码
[return [返回值]]
其中,用 [] 括起来的为可选择部分,即可以使用,也可以省略。
此格式中,各部分参数的含义如下:
- 函数名:其实就是一个符合 Python 语法的标识符,但不建议读者使用 a、b、c 这类简单的标识符作为函数名,函数名最好能够体现出该函数的功能(如上面的 my_len,即表示我们自定义的 len() 函数)。
- 形参列表:设置该函数可以接收多少个参数,多个参数之间用逗号( , )分隔。
- [return [返回值] ]:整体作为函数的可选参数,用于设置该函数的返回值。也就是说,一个函数,可以用返回值,也可以没有返回值,是否需要根据实际情况而定。
注意,在创建函数时,即使函数不需要参数,也必须保留一对空的“()”,否则 Python 解释器将提示“invaild syntax”错误。另外,如果想定义一个没有任何功能的空函数,可以使用 pass 语句作为占位符。
例如,下面定义了 2 个函数:
#定义个空函数,没有实际意义
def pass_dis():
pass
#定义一个比较字符串大小的函数
def str_max(str1,str2):
str = str1 if str1 > str2 else str2
return str
虽然 Python 语言允许定义个空函数,但空函数本身并没有实际意义。
另外值得一提的是,函数中的 return 语句可以直接返回一个表达式的值,例如修改上面的 str_max() 函数:
def str_max(str1,str2):
return str1 if str1 > str2 else str2
该函数的功能,和上面的 str_max() 函数是完全一样的,只是省略了创建 str 变量,因此函数代码更加简洁。
16.1.2 Python函数的调用
调用函数也就是执行函数。如果把创建的函数理解为一个具有某种用途的工具,那么调用函数就相当于使用该工具。
函数调用的基本语法格式如下所示:
[返回值] = 函数名([形参值])
其中,函数名即指的是要调用的函数的名称;形参值指的是当初创建函数时要求传入的各个形参的值。如果该函数有返回值,我们可以通过一个变量来接收该值,当然也可以不接受。
需要注意的是,创建函数有多少个形参,那么调用时就需要传入多少个值,且顺序必须和创建函数时一致。即便该函数没有参数,函数名后的小括号也不能省略。
例如,我们可以调用上面创建的 pass_dis() 和 str_max() 函数:
pass_dis()
strmax = str_max("https://editor.csdn.net/md?articleId=125357340","https://editor.csdn.net/");
print(strmax)
首先,对于调用空函数来说,由于函数本身并不包含任何有价值的执行代码,也没有返回值,应该调用空函数不会有任何效果。
其次,对于上面程序中调用 str_max() 函数,由于当初定义该函数为其设置了 2 个参数,因此这里在调用该参数,就必须传入 2 个参数。同时,由于该函数内部还使用了 return 语句,因此我们可以使用 strmax 变量来接收该函数的返回值。
因此,程序执行结果为:
https://editor.csdn.net/md?articleId=125357340
16.1.3 为函数提供说明文档
前面章节讲过,通过调用 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 个字符串的大小
16.2 函数参数
通常情况下,定义函数时都会选择有参数的函数形式,函数参数的作用是传递数据给函数,令其对接收的数据做具体的操作处理。
在使用函数时,经常会用到形式参数(简称“形参”)和实际参数(简称“实参”),二者都叫参数,之间的区别是:
- 形式参数:在定义函数时,函数名后面括号中的参数就是形式参数,例如:
#定义函数时,这里的函数参数 obj 就是形式参数
def demo(obj):
print(obj)
- 实际参数:在调用函数时,函数名后面括号中的参数称为实际参数,也就是函数的调用者给函数的参数。例如:
a = "python学习"
#调用已经定义好的 demo 函数,此时传入的函数参数 a 就是实际参数
demo(a)
实参和形参的区别,就如同剧本选主角,剧本中的角色相当于形参,而演角色的演员就相当于实参。
明白了什么是形参和实参后,再来想一个问题,那就是实参是如何传递给形参的呢?
Python 中,根据实际参数的类型不同,函数参数的传递方式可分为 2 种,分别为值传递和引用(地址)传递:
- 值传递:适用于实参类型为不可变类型(字符串、数字、元组);
- 引用(地址)传递:适用于实参类型为可变类型(列表,字典);
值传递和引用传递的区别是,函数参数进行值传递后,若形参的值发生改变,不会影响实参的值;而函数参数继续引用传递后,改变形参的值,实参的值也会一同改变。
例如,定义一个名为 demo 的函数,分别为传入一个字符串类型的变量(代表值传递)和列表类型的变量(代表引用传递):
def demo(obj) :
obj += obj
print("形参值为:",obj)
print("-------值传递-----")
a = "python学习"
print("a的值为:",a)
demo(a)
print("实参值为:",a)
print("-----引用传递-----")
a = [1,2,3]
print("a的值为:",a)
demo(a)
print("实参值为:",a)
运行结果为:
-------值传递-----
a的值为: python学习
形参值为: python学习python学习
实参值为: python学习
-----引用传递-----
a的值为: [1, 2, 3]
形参值为: [1, 2, 3, 1, 2, 3]
实参值为: [1, 2, 3, 1, 2, 3]
分析运行结果不难看出,在执行值传递时,改变形式参数的值,实际参数并不会发生改变;而在进行引用传递时,改变形式参数的值,实际参数也会发生同样的改变。
16.2.1 参数类型
16.2.1.1 位置参数
位置参数,有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中,换句话说,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。
实参和形参数量必须一致
在调用函数,指定的实际参数的数量,必须和形式参数的数量一致(传多传少都不行),否则 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 <module>
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 <module>
print(girth(3,2,4))
TypeError: girth() takes 2 positional arguments but 3 were given
通过 TypeErroe 异常信息可以知道,girth() 函数本只需要 2 个参数,但是却传入了 3 个参数。
实参和形参位置必须一致
在调用函数时,传入实际参数的位置必须和形式参数位置一一对应,否则会产生以下 2 种结果:
抛出 TypeError 异常
当实际参数类型和形式参数类型不一致,并且在函数中,这两种类型之间不能正常转换,此时就会抛出 TypeError 异常。
例如:
def area(height,width):
return height*width/2
print(area("python学习",3))
输出结果为:
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\1.py", line 3, in <module>
print(area("python学习",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'
以上显示的异常信息,就是因为字符串类型和整形数值做除法运算。
产生的结果和预期不符
调用函数时,如果指定的实际参数和形式参数的位置不一致,但它们的数据类型相同,那么程序将不会抛出异常,只不过导致运行结果和预期不符。
例如,设计一个求梯形面积的函数,并利用此函数求上底为 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
因此,在调用函数时,一定要确定好位置,否则很有可能产生类似示例中的这类错误,还不容易发现。
16.2.1.2 关键字参数
目前为止,我们使用函数时所用的参数都是位置参数,即传入函数的实际参数必须与形式参数的数量和位置对应。而本节将介绍的关键字参数,则可以避免牢记参数位置的麻烦,令函数的调用和参数传递更加灵活方便。
关键字参数是指使用形式参数的名字来确定输入的参数值。通过此方式指定函数实参时,不再需要与形参的位置完全一致,只要将参数名写正确即可。
因此,Python 函数的参数名应该具有更好的语义,这样程序可以立刻明确传入函数的每个参数的含义。
例如,在下面的程序中就使用到了关键字参数的形式给函数传参:
def dis_str(str1,str2):
print("str1:",str1)
print("str2:",str2)
#位置参数
dis_str("https://editor.csdn.net/md?articleId=125357340","https://editor.csdn.net/")
#关键字参数
dis_str("https://editor.csdn.net/md?articleId=125357340",str2="https://editor.csdn.net/")
dis_str(str2="https://editor.csdn.net/",str1="https://editor.csdn.net/md?articleId=125357340")
程序执行结果为:
str1: https://editor.csdn.net/md?articleId=125357340
str2: https://editor.csdn.net/
str1:https://editor.csdn.net/md?articleId=125357340
str2: https://editor.csdn.net/
str1: https://editor.csdn.net/md?articleId=125357340
str2: https://editor.csdn.net/
可以看到,在调用有参函数时,既可以根据位置参数来调用,也可以使用关键字参数(程序中第 8 行)来调用。在使用关键字参数调用时,可以任意调换参数传参的位置。
当然,还可以像第 7 行代码这样,使用位置参数和关键字参数混合传参的方式。但需要注意,混合传参时关键字参数必须位于所有的位置参数之后。也就是说,如下代码是错误的:
# 位置参数必须放在关键字参数之前,下面代码错误
dis_str(str1="https://editor.csdn.net/md?articleId=125357340","https://editor.csdn.net/")
Python 解释器会报如下错误:
SyntaxError: positional argument follows keyword argument
16.2.1.3 默认参数
我们知道,在调用函数时如果不指定某个参数,Python 解释器会抛出异常。为了解决这个问题,Python 允许为参数设置默认值,即在定义函数时,直接给形式参数指定一个默认值。这样的话,即便调用函数时没有给拥有默认值的形参传递参数,该参数可以直接使用定义函数时设置的默认值。
Python 定义带有默认值参数的函数,其语法格式如下:
def 函数名(...,形参名,形参名=默认值):
代码块
注意,在使用此格式定义函数时,指定有默认值的形式参数必须在所有没默认值参数的最后,否则会产生语法错误。
下面程序演示了如何定义和调用有默认参数的函数:
#str1没有默认参数,str2有默认参数
def dis_str(str1,str2 = "csdn."):
print("str1:",str1)
print("str2:",str2)
dis_str("https://editor.csdn.net/md?articleId=125357340")
dis_str("https://editor.csdn.net/md?articleId=125357340","https://editor.csdn.net/")
运行结果为:
str1: https://editor.csdn.net/md?articleId=125357340
str2: csdn.
str1: https://editor.csdn.net/md?articleId=125357340
str2: https://editor.csdn.net/
上面程序中,dis_str() 函数有 2 个参数,其中第 2 个设有默认参数。这意味着,在调用 dis_str() 函数时,我们可以仅传入 1 个参数,此时该参数会传给 str1 参数,而 str2 会使用默认的参数,如程序中第 6 行代码所示。
当然在调用 dis_str() 函数时,也可以给所有的参数传值(如第 7 行代码所示),这时即便 str2 有默认值,它也会优先使用传递给它的新值。
同时,结合关键字参数,以下 3 种调用 dis_str() 函数的方式也是可以的:
dis_str(str1 = “https://editor.csdn.net/md?articleId=125357340”)
dis_str(“https://editor.csdn.net/md?articleId=125357340”,str2 = “https://editor.csdn.net//”)
dis_str(str1 = “https://editor.csdn.net/md?articleId=125357340”,str2 = “https://editor.csdn.net/”)
再次强调,当定义一个有默认值参数的函数时,有默认值的参数必须位于所有没默认值参数的后面。因此,下面例子中定义的函数是不正确的:
#语法错误
def dis_str(str1="https://editor.csdn.net/md?articleId=125357340",str2,str3):
pass
显然,str1 设有默认值,而 str2 和 str3 没有默认值,因此 str1 必须位于 str2 和 str3 之后。
有读者可能会问,对于自己自定义的函数,可以轻易知道哪个参数有默认值,但如果使用 Python 提供的内置函数,又或者其它第三方提供的函数,怎么知道哪些参数有默认值呢?
Pyhton 中,可以使用“函数名.defaults”查看函数的默认值参数的当前值,其返回值是一个元组。以本节中的 dis_str() 函数为例,在其基础上,执行如下代码:
print(dis_str.__defaults__)
程序执行结果为:
('csdn.',)
16.2.1.4 可变数量形参(*args,**kwargs)
很多编程语言都允许定义个数可变的参数,这样可以在调用函数时传入任意多个参数。Python也不例外。下面讲解将多个实参传给一个形参。
- 元组参数()
Python允许在形参前面添加一个星号(),这样解释器会理解为此参数可以接受多个参数值,多个参数值会被当成元组传入。
def text(a,*langs):
print(langs)
for lang in langs:
print(lang)
print(a)
text(3,"C","python","C++")
运行结果:
("C","python","C++")
C
python
C++
3
从上面的运行结果可以看出,传入的多个参数被当成了一个元组。
需要这注意的是:多参数形参可以处于参数的任意位置,但是一旦处于靠前位置,后面的形参要以关键字参数的形式传入,避免引起歧义,系统无法分辨哪些参数传给元组。
2. 字典参数(**)
前面我们使用 星号收集多个单值参数,单值参数被看做了元组;python也允许同时传入多个关键字参数,这些参数将被看作字典。形参定义为双星号( * * )的形式。
例如:
def text(a,*langs,**scores):
print(langs)
print(scores)
print(a)
text(3,"C","python","C++",语文=99,数学=87)
运行结果:
("C","python","C++")
{'语文':99,'数学':87}
3
16.2.1.5 逆向参数收集
所谓逆向参数收集就是将一个实参(元组,列表,字典等)一次性传递给所有形参。
此过程正好与上一节所讲的可变参数相反,需要在实参前面加星号或双星号。
例如:
- 序列的元素正好是形参数量
def text(user,password):
print("用户是:",user)
print("密码是:",password)
my_list = ['猴哥','huaguoshan']
text(*my_list) # 添加*号代表序列传递参数
- 序列传给可变形参
def text(user,*scores):
print("用户是:",user)
print("考试分数是:",password)
my_scores = [35,45,65]
text('猴哥',*my_scores) # 添加*号代表序列传递参数
输出结果:
用户是:猴哥
考试分数是:(35,45,65)
也可以直接使用序列传递参数:
def text(user,*scores):
print("用户是:",user)
print("考试分数是:",password)
my_scores = [35,45,65]
text(*my_scores) # 添加*号代表序列传递参数
输出结果:
用户是:35
考试分数是:(45,65)
以上结果显示,序列的第一个参数传递给了user,剩余的参数传递给了可变形参*scores。
需要注意的是,如果不适用逆向收集,整个序列将作为一个参数传递给形参。
text(my_scores)
运行结果:
用户是:(35,45,65)
考试分数是:()
- 字典作为实参传递函数
与可变数量形似一下,可以在实参中使用双星号传递字典参数。
def text(user,语文,数学,物理):
print("用户是:",user)
print("语文考试分数是:",语文)
print("数学考试分数是:",语文)
print("物理考试分数是:",语文)
my_scores = {'user':'猴哥','语文':35,'数学':45,'物理':65]
text(**my_scores) # 添加**号代表字典传递参数
输出结果:
用户是:35
语文考试分数是:35
数学考试分数是:45
物理考试分数是:65
16.2.2 参数传递机制
Python的参数值是如何传入函数的呢?这是由Python的参数传递机制来控制的。
Python的参数传递机制是”值传递“。所谓值传递,就是将实际参数值的副本(复制品)传入函数,而参数本身不会受到影响。
def swap(a,b):
a,b=b,a
print("swap中,a=",a,";b=",b)
a = 6
b = 9
swap(a,b)
print("swap后,a=",a,";b=",b)
输出结果:
swap中,a=9;b=6
swap后,a=6;b=9
可以看出a ,b 的数值实际没有变化。
如果参数本身是一个可变对象(如列表/字典等),此时Python虽然也是采用值传递方式,但是结果是不同的,例如:
def swap(dw):
dw['a'],dw['b']=dw['b],dw['a']
print("swap中,a=",dw['a'],";b=",dw['b'])
dw = {'a':6,'b':9}
swap(dw)
print("swap后,a=",dw['a'],";b=",dw['b'])
输出结果:
swap中,a=9;b=6
swap后,a=9;b=6
可以看到自定义函数内a,b的值被交换了,同时实参的a,b值也被交换了。
实际上,这也是值传递的一种形式,实参dw是一个指针,指向的是字典存储位置,将dw的值的副本传递给函数,实质是将内存地址传递给函数。
如果我们将swap函数中的形参dw赋值为none(此时无法引用函数运算结果),同样不会影响实参dw的数据。
16.3 变量作用域
程序中定义一个变量有其作用范围,被称为它得作用域。根据其作用范围可分为两种:
- 局部变量。在函数中定义的变量(包括参数)
- 全局变量。在函数外,全局范围内定义的变量。
要知道,当函数被执行时,Python 会为其分配一块临时的存储空间,所有在函数内部定义的变量,都会存储在这块空间中。而在函数执行完毕后,这块临时存储空间随即会被释放并回收,该空间中存储的变量自然也就无法再被使用。
举个例子:
def demo():
add = "https://editor.csdn.net/md?articleId=125357340"
print("函数内部 add =",add)
demo()
print("函数外部 add =",add)
程序执行结果为:
函数内部 add = https://editor.csdn.net/md?articleId=125357340
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\file.py", line 6, in <module>
print("函数外部 add =",add)
NameError: name 'add' is not defined
可以看到,如果试图在函数外部访问其内部定义的变量,Python 解释器会报 NameError 错误,并提示我们没有定义要访问的变量,这也证实了当函数执行完毕后,其内部定义的变量会被销毁并回收。
值得一提的是,函数的参数也属于局部变量,只能在函数内部使用。例如:
def demo(name,add):
print("函数内部 name =",name)
print("函数内部 add =",add)
demo("Python教程","https://editor.csdn.net/md?articleId=125357340")
print("函数外部 name =",name)
print("函数外部 add =",add)
程序执行结果为:
函数内部 name = Python教程
函数内部 add = https://editor.csdn.net/md?articleId=125357340
Traceback (most recent call last):
File "C:\Users\mengma\Desktop\file.py", line 7, in <module>
print("函数外部 name =",name)
NameError: name 'name' is not defined
由于 Python 解释器是逐行运行程序代码,由此这里仅提示给我“name 没有定义”,实际上在函数外部访问 add 变量也会报同样的错误。
除了在函数内部定义变量,Python 还允许在所有函数的外部定义变量,这样的变量称为全局变量(Global Variable)。
和局部变量不同,全局变量的默认作用域是整个程序,即全局变量既可以在各个函数的外部使用,也可以在各函数内部使用。
定义全局变量的方式有以下 2 种:
- 在函数体外定义的变量,一定是全局变量,例如:
add = "https://editor.csdn.net/md?articleId=125357340"
def text():
print("函数体内访问:",add)
text()
print('函数体外访问:',add)
运行结果为:
函数体内访问: https://editor.csdn.net/md?articleId=125357340
函数体外访问: https://editor.csdn.net/md?articleId=125357340
- 在函数体内定义全局变量。即使用 global 关键字对变量进行修饰后,该变量就会变为全局变量。例如:
def text():
global add
add= "https://editor.csdn.net/md?articleId=125357340"
print("函数体内访问:",add)
text()
print('函数体外访问:',add)
运行结果为:
函数体内访问: https://editor.csdn.net/md?articleId=125357340
函数体外访问: https://editor.csdn.net/md?articleId=125357340
注意,在使用 global 关键字修饰变量名时,不能直接给变量赋初值,否则会引发语法错误。
获取指定作用域范围中的变量
不管是局部变量还是全局变量,都可能存在很多变量,每个变量’持有‘该变量的值。从这个角度看,这些变量可以理解为一个’看不见‘的字典,其中变量名就是key,变量值就value。
在一些特定场景中,我们可能需要获取某个作用域内(全局范围内或者局部范围内)所有变量的字典,Python 提供了以下 3 种方式:
- globals()函数
globals() 函数为 Python 的内置函数,它可以返回一个包含全局范围内所有变量的字典,该字典中的每个键值对,键为变量名,值为该变量的值。
举个例子:
#全局变量
Pyname = "Python教程"
Pyadd = "https://editor.csdn.net/md?articleId=125357340"
def text():
#局部变量
Shename = "shell教程"
Sheadd= "https://editor.csdn.net/"
print(globals())
程序执行结果为:
{ ...... , 'Pyname': 'Python教程', 'Pyadd': 'https://editor.csdn.net/md?articleId=125357340', ......}
注意,globals() 函数返回的字典中,会默认包含有很多变量,这些都是 Python 主程序内置的,读者暂时不用理会它们。
可以看到,通过调用 globals() 函数,我们可以得到一个包含所有全局变量的字典。并且,通过该字典,我们还可以访问指定变量,甚至如果需要,还可以修改它的值。但是不建议这么做。例如,在上面程序的基础上,添加如下语句:
print(globals()['Pyname'])
globals()['Pyname'] = "Python入门教程"
print(Pyname)
程序执行结果为:
Python教程
Python入门教程
- locals()函数
locals() 函数也是 Python 内置函数之一,通过调用该函数,我们可以得到一个包含当前作用域内所有变量的字典。这里所谓的“当前作用域”指的是,在函数内部调用 locals() 函数,会获得包含所有局部变量的字典;而在全局范文内调用 locals() 函数,其功能和 globals() 函数相同。
举个例子:
#全局变量
Pyname = "Python教程"
Pyadd = "https://editor.csdn.net/md?articleId=125357340"
def text():
#局部变量
Shename = "csdn教程"
Sheadd= "https://editor.csdn.net/"
print("函数内部的 locals:")
print(locals())
text()
print("函数外部的 locals:")
print(locals())
程序执行结果为:
函数内部的 locals:
{'Sheadd': 'https://editor.csdn.net/', 'Shename': 'csdn教程'}
函数外部的 locals:
{...... , 'Pyname': 'Python教程', 'Pyadd': 'https://editor.csdn.net/md?articleId=125357340', ...... }
当使用 locals() 函数获取所有全局变量时,和 globals() 函数一样,其返回的字典中会默认包含有很多变量,这些都是 Python 主程序内置的,读者暂时不用理会它们。
注意,当使用 locals() 函数获得所有局部变量组成的字典时,可以向 globals() 函数那样,通过指定键访问对应的变量值,但无法对变量值做修改。例如:
#全局变量
Pyname = "Python教程"
Pyadd = "https://editor.csdn.net/md?articleId=125357340"
def test():
#局部变量
Shename = "csdn教程"
Sheadd= "https://editor.csdn.net/"
print(locals()['Shename'])
locals()['Shename'] = "csdn入门教程"
print(Shename)
test()
程序执行结果为:
csdn教程
csdn教程
显然,locals() 返回的局部变量组成的字典,可以用来访问变量,但无法修改变量的值。
3. vars(object)
vars() 函数也是 Python 内置函数,其功能是返回一个指定 object 对象范围内所有变量组成的字典。如果不传入object 参数,vars() 和 locals() 的作用完全相同。
举个例子:
#全局变量
Pyname = "Python教程"
Pyadd = "https://editor.csdn.net/md?articleId=125357340"
class Demo:
name = "Python 教程"
add = "https://editor.csdn.net/"
print("有 object:")
print(vars(Demo))
print("无 object:")
print(vars())
程序执行结果为:
有 object:
{...... , 'name': 'Python 教程', 'add': 'https://editor.csdn.net/md?articleId=125357340', ......}
无 object:
{...... , 'Pyname': 'Python教程', 'Pyadd': 'https://editor.csdn.net/', ...... }
全局变量默认可以在所有函数内部被访问,但是如果在函数中定义了与全局变量同名的变量,此时就会发生局部变量遮蔽(hide)全局变量的情形,例如:
name = '猴哥'
def test():
print(name) #直接访问全局变量,
test() #猴哥
print(name) #猴哥
name = '猴哥'
def test():
print(name) #直接访问全局变量,
name = '孙悟空' #局部变量遮蔽全局变量,因此上一条语句会报错
test() #猴哥
print(name) #猴哥
那么怎么解决以上问题呢?有两种方式:
- 访问被遮蔽的全局变量
通过globals()函数访问全局变量,例如:
name = '猴哥'
def test():
print(globals()['name']) #函数内部访问全局变量,输出:'猴哥'
name = '孙悟空' #局部变量遮蔽全局变量,
print(name) #'孙悟空'
test() #猴哥
print(name) #猴哥
- 函数中声明全局变量
name = '猴哥'
def test():
global name #声明使用全局变量name
print(name) #函数内部访问全局变量,输出:'猴哥'
name = '孙悟空' #操作对象也是全局变量
print(name) #'孙悟空'
test() #猴哥
print(name) #孙悟空
16.4 函数返回值(return)
16.4.1 返回单值
到目前为止,我们创建的函数都只是对传入的数据进行了处理,处理完了就结束。但实际上,在某些场景中,我们还需函数将处理的结果反馈回来,就好像主管向下级员工下达命令,让其去打印文件,员工打印好文件后并没有完成任务,还需要将文件交给主管。
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 语句,可以返回多个值。
16.4.2 返回多值
如果程序需要返回多个值,可以将多个值封装成列表后返回,本质上也是返回一个值,也可以直接返回多个值。
如果Python函数直接返回多个值,Python会自动将多个返回值封装成元组。
def example(list):
sum = 0
count = 0
for e in list:
if isinstance(e,int) or isinstance(e,float)
count+=1
sum+=e
return sum,count,sum/count
my_list = [1,2,3,4,5]
tp = example(my_list)
print(tp)
上述程序example返回了3个值:sum,count,sum/count,这3个值将被封装成元组后赋值给tp。
运行结果:
(15,5,3)
此外,也可以直接将返回值赋值给多个变量,这是Python提供的学列解包功能,例如:
s,c,avg = example(my_list)
print(s)
print(c)
print(avg)
运行结果:
15
5
3
16.5 函数高级用法
16.5.1 函数变量
在Python中’一切皆对象‘,我们又用到了这句话,其实Python的函数也是一种值:所有函数都是function对象,这意味着可以把函数本身赋值给变量,就像把整数、浮点数、列表、元组赋值给变量一样。还可以把函数传递给其他函数,或者作为其他函数的返回对象。
当把函数赋值给变量后,接下来的程序也可通过该变量来调用函数。例如:
def pow(base,exponent):
result = 1
for i in range (1,exponent+1)
result *= base
return result
my_fun = pow
print(my_fun(3,2)) #输出9
可以看到我们使用my_fun()调用了pow()函数。
16.5.2 函数形参
有时候需要定义一个函数,该函数的大部分计算逻辑都能确定,但是某些处理逻辑暂时无法确定,这意味着某些程序代码需要动态改变,如果希望调用函数时能动态传入代码,那么就需要在函数中定义函数形参,这样即可在调用函数时传入不同的函数作为参数,从而动态改变这段代码。例如:
def map (data,fun):
result = []
for e in data:
result . append(fun(e))
return result
def square(n):
return n*n
def cube(n):
return n^3
data = [ 2,3,4 ]
print("源数据:",data) #输出[ 2,3,4 ]
print("计算平方:",map(data,square)) #输出[4,9,16]
print("计算立方:",map(data,cube)) #输出[8,27,64]
运行过程中,我们动态改变了map()函数内部的代码,通过传如不同的函数实参,得到不同的结果。
实际上,在前面章节内置函数中,也同样提供了map()函数,它利用的就是函数形参功能。
16.5.3 返回函数(闭包函数)
前面已经提到,Python还支持使用函数作为其他函数的返回值。例如:
def get_math_func(type):
def square(n):
return n*n
def cube (n):
return n^3
if type == "square":
return square
if type == "cube":
return cube
my_func = get_math_func(square) #得到求平方函数
print(my_func(5)) #输出25
my_func = get_math_func(cube) #得到求立方函数
print(my_func(5)) #输出125
上面程序通过传入不同的子函数代码,达到返回不同的子函数,类似于一个工具箱get_math_func里面有锤子、扳手、电钻等工具,当我们拆卸一个门的时候,我们拿出了电钻(my_func = get_math_func(square) ),然后给电钻接通电源(my_func(5)这个5就是我们给它的电源),就可以完成我们的工作了。