Python之函数


什么是函数

所谓函数,就是把重复的代码单独的分离出来,放在⼀个公共的地⽅,以后可以⼀直的进⾏调⽤,这样就可以解决多次重复来编写,并且可以通过输入的参数值,返回需要的结果。

函数实际就是相当于一个具有某个/些功能的工具,先定义好,然后在需要的时候进行调用执行。

为什么要引入函数?

没有使用函数时的代码会有以下不足:

  • 组织结构不清晰,可读性差;
  • 代码冗余(同一个功能可能需要在多个地方使用);
  • 可维护性及扩展性差(同一个功能在多个地方写了多次,需求变更时也需要更改多个地方的代码);

函数的定义及调用

函数的定义

  • def开头,代表定义函数;
  • def和函数名中间要敲一个空格;
  • 函数名,这个名字是我们自己命名的,;
  • 函数名后跟圆括号(),代表定义的是函数,里边可加参数/参数列表;
  • 圆括号()后一定要加冒号: ;
  • 代码块部分,是由语句组成,要有缩进;
  • 有的函数有返回值,有的函数没有返回值;

定义函数时:

  • 申请内存空间保存函数体代码;
  • 将上述内存地址绑定函数名;
  • 定义函数不会执行函数体代码,但会检测函数体语法;

调用函数时:

  • 直接使用函数名调用,获得的是函数的内存地址;
  • 通过函数名()带上括号调用,将执行函数体代码,返回执行结果;

所以,函数定义阶段只检测语法,不会执行代码内容,而调用函数时才会执行函数体的代码。

定义函数的两种形式

无参函数

无参函数即函数名右侧的括号内为空。(函数体代码的多少无关)

def func():
	print("hello python")

有参函数

有参函数即函数名右侧的括号内有参数(变量)

x = 20
y = 200
result = x + y
print(result)

将上面的代码写成函数:

首先,写定义函数的关键字def跟上函数名,将上述代码缩进。如下:

def sum():
	x = 20
	y = 200
	result = x + y
	print(result)

然后,查看函数体代码中是否有变量被写死了,比如这里的x和y被写死为20和200,将x和y提取到参数中

def sum(x,y):
	result = x + y
	return result

最后通过sum(20,200)就可以调用执行sum函数了。

后续内容会详细介绍有参函数

函数调用的三种形式

语句形式

直接调用函数

def sum(x,y):
	result = x + y
	return result
  
sum(20,200)

表达式形式

函数执行结果赋值给某个变量,或参与与其他变量/数据的处理计算

def sum(x,y):
	result = x + y
	return result
  
result1 = sum(20,200)
result2 = sum(20,200) * π
print(result1,result2)

函数当做参数调用

执行上面的函数sum(20,200)的返回结果是220

那么sum(220,100)就可以改写成sum(sum(20,200),100),即sum(sum(x,y),z)

函数的返回值

return是函数结束的标志,即函数体代码一旦运行到return会立刻终止函数的运行,并且将return后面的值当做本次运行的结果返回;

返回None

  • 函数体内没有return
  • return
  • return None

返回一个值

return result

返回多个值

用逗号分隔开多个值,会被return返回成元组

return result1,result2,result3

详解有参函数

形参和实参

形参:在定义函数阶段定义的参数,称之为形式参数,简称形参,相当于变量名;

def sum(x,y):
	return x+y

实参:在调用函数阶段传入的值称之为实际参数,简称实参,相当于变量值;

sum(10,20)

在函数调用阶段,实参会绑定给形参,这种绑定关系会在函数调用时生效,在函数体代码中进行使用,调用结束后解除绑定关系。

位置参数

按照从左到右的顺序依次定义的参数称之为位置参数。

位置形参:按照从左到右的顺序直接定义形参名,必须被传值,个数一致,不能多也不能少;

def sum(x,y,z):
	return x+y+z

位置实参:按照从左到右的顺序依次传入值,与形参的顺序一一对应;

传值顺序x,y,z分别对应1,2,3

def sum(x, y, z):
    print(x)
    print(y)
    print(z)
    return x + y + z
result = sum(1, 2, 3)
print(result)

1
2
3
6

传值顺序x,y,z分别对应3,2,1

result = sum(3, 2, 1)
print(result)

3
2
1
6

多传参数

result = sum(3, 2, 1, 5)
print(result)

Traceback (most recent call last):
  File "/Users/laobai/workspaces/git_study_02/1.py", line 8, in <module>
    result = sum(3, 2, 1, 5)
TypeError: sum() takes 3 positional arguments but 4 were given

少传参数

result = sum(1, 2)
print(result)

Traceback (most recent call last):
  File "/Users/laobai/workspaces/git_study_02/1.py", line 8, in <module>
    result = sum(1, 2)
TypeError: sum() missing 1 required positional argument: 'z'

关键字参数

关键字实参,在函数调用阶段,按照key=value的形式传入的值。指定某个形参进行传值。

def func(x,y):
	print(x,y)
  
func(y=10,x=1)

1 10

位置参数和关键字参数可以混用,但位置参数要在关键字参数之前。

func(10,y=1)

默认参数

在定义函数阶段,就已经被赋值的形参,称之为默认参数。

因为在定义阶段已经赋值,那么在调用阶段可以根据实际的情况,可以不用为其赋值,当然也可以进行赋值。

def func(x, y=100):
    print(x, y)

func(1)
func(1, 1)


1 100
1 1

位置形参和默认形参同时使用时,位置形参必须在默认形参的左边;

默认参数的值是在函数定义阶段被赋值的,赋予的是值的内存地址(有可能是变量如list等);

可变长度参数

可变长度指的是在调用函数时,传入的值的个数是不固定的。即实参个数不固定。

由于实参个数不固定,就需要有个形参来接收多出来的实参。

def sum(a,b):
    sum = a+b;
    return sum

sum(1,2)
sum(1,2,3)
sum(1,2,3,4)

sum(a=1,b=2)
sum(a=1,b=2,c=3)
sum(a=1,b=2,c=3,d=4)

可变长度的位置参数可以用*args来接收并组成元组;可变长度的关键字参数可以使用**kwargs来接收并组成字典。其中args和kwargs都是变量名,可以换成a,b,x,y等都可以,只是python的规范要求用args和kwargs两个固定的变量名称。理解了args和kwags只是可以被替换成别的变量名称的代号,那么能够实现接收可变长度参数的关键就是***这两个的作用。

sum(a,b,*args)sum(a,b,*c)是完全一样的;sum(a,b,**kwargs)sum(a,b,**c)是完全一样的。

上面的例子可以改写成如下:

def sum1(*args):
    sum=0
    for i in args:
        sum += i
    print(sum)

sum1(1,2)
sum1(1,2,3)
sum1(1,2,3,4)

def sum2(**kwargs):
    sum=0
    for k,v in kwargs.items():
        sum += v
    print(sum)

sum2(a=1,b=2)
sum2(a=1,b=2,c=3)
sum2(a=1,b=2,c=3,d=4)

上面介绍的内容,*用在形参中,下面介绍*用在实参中的情况。

下面的这个例子,想把列表中的3个值传给形参x,y,z,但下面的写法并不可行

def sum(x, y, z):
    return x + y + z

res = sum(*[1, 2, 3])
print(res)


Traceback (most recent call last):
  File "/Users/laobai/workspaces/git_study_02/1.py", line 22, in <module>
    res = sum([1, 2, 3])
TypeError: sum() missing 2 required positional arguments: 'y' and 'z'

会将列表的内容当做一个值传给x,而y和z没有接收到值。

[1,2,3]的前面加上*,即*[1,2,3],就可以将列表中的每个元素都分解出来作为一个值进行传递

def sum(x, y, z):
    print(x)
    print(y)
    print(z)
    return x + y + z

res = sum(*[1, 2, 3])
print(res)


1
2
3
6

实参和形参中都带有*的情况

def func(a, b, *args):
    print(a, b, args)

func(1, 2, [3, 4, 5, 6])
func(1, 2, *[3, 4, 5, 6])


1 2 ([3, 4, 5, 6],)
1 2 (3, 4, 5, 6)

同样**也可以存在在实参中,以及形参和实参都存在的情况

def func(a, b, c):
    print(a, b, c)

func(**{'a': 1, 'b': 2, 'c': 3})


1 2 3
def func(a, b, **kwargs):
    print(a, b, kwargs)

func(**{'d': 4, 'c': 3, 'b': 2, 'a': 1})


1 2 {'d': 4, 'c': 3}

注意:

*args必须写在**kwargs之前

def func(*args, **kwargs):
    print(args)
    print(kwargs)

func(1, 2, 3, 4, 5, 6, a=7, b=8, c=9)


(1, 2, 3, 4, 5, 6)
{'a': 7, 'b': 8, 'c': 9}

名称空间

名称空间(namespaces),存放名称的地方,是对栈区的划分。有了名称空间,就可以在栈区中存放相同的名字。名称空间分为三种:内置名称空间(built-in names)、全局名称空间(global names)和局部名称空间(local names)

内置名称空间

  1. 存放的名字:存放的python解释器内置的名字;

    >>> print
    <built-in function print>
    >>> input
    <built-in function input>
    >>> open
    <built-in function open>
  2. 存活生命周期:Python解释器启动时创建,解释器关闭时销毁;

全局名称空间

  1. 存放的名字:不是函数内定义的,也不是内置的,剩下的都是全局名称;

    import pytest
    
    a = 1
    
    for i in ragne(5):
      print(i)
      if i = 2:
        b = 100
        
    def func():
      pass
    
    class Test:
      pass
  2. 存活生命周期:被Python解释器读入时创建(即模块被执行时),文件执行完成后被销毁

局部名称空间

  1. 存放的名字:在调用函数时,运行函数体代码过程中产生的函数内的名字;
def sum(x, y, z):
    print(x)
    print(y)
    print(z)
    return x + y + z
  1. 存活生命周期:调用函数时创建,函数调用完毕后销毁;

名称空间的加载顺序

内置名称空间 > 全局名称空间 > 局部名称空间

名称空间的查找顺序

当前所在的位置向上一层一层的查找:

局部名称空间 > 全局名称空间 > 内置名称空间

例子:

  1. 先从函数的局部名称空间查找input:

    input = 123
    
    def func():
        input = 456
        print(input)
    
    func()
    
    456
  2. 先从函数的局部名称空间查找input,查找不到,在全局名称空间查找input:

    input = 123
    
    def func():
        print(input)
    
    func()
    
    123
  3. 先从函数的局部名称空间查找,再从全局名称空间查找,都查不到,再从内置名称空间查找input:

    def func():
        print(input)
    
    func()
    
    <built-in function input>

名称空间在函数代码定义时就已经确定了的,跟执行时的环境无关。

a = 1
def func1():
    print(a)

def func2():
    a = 100
    func1()

func2()


1

作用域

  • 全局范围作用域
    • 全局作用域,即全局作用空间,包括内置名称空间和全局名称空间。
    • 全局作用域,全局有效,被模块中的所有函数共享
  • 局部范围作用域
    • 局部作用域,即局部作用空间,只包含局部命名空间。
    • 局部作用域,局部有效,只能在函数内使用。
# Built-in  (内置)

# Global    (全局)

def func1():
    # Enclosing (函数嵌套)
    def func2():
        # Enclosing (函数嵌套)
        def func3():
            # Local (局部)
            pass

global关键字:在局部想要修改全局的名字对应的值(不可变类型),就需要用global进行声明

a=11
def func():
    global a
    a=22
    
func()
print(a)


22

函数对象

函数对象,就是将函数当做变量去用。

a.将函数当做变量去用:
函数名=内存地址,单独使用函数的名称func实际上是在调用func的内存地址,而加上括号的func()调用的是函数func的执行结果。

def func():
    return "hello python"

f1 = func()
f2 = func
print("f1:", f1)
print("f2:", f2)


f1: hello python
f2: <function func at 0x109928160>

也就是func实际就是内存地址0x109928160f2=func就是将0x109928160赋值给了f2,那么当f2加上括号f2()就相当于在执行func()了。

b.将函数当做参数传给另外一个函数:

def func1():
    print("hello python")
def func2(x):
    print(x)
    x()
func2(func1)


<function func1 at 0x1068a6160>
hello python

上面的例子就是将func1这个函数名作为参数传递给了func2(x)这个函数,并在func2(x)函数内func1加上了括号func1()执行这个函数。

c.将函数当做另外一个函数的返回值:

def func1():
    print("hello python")
def func2(x):
    return x
res = func2(func1)
res()


hello python

d.将函数当做容器类型的一个元素:

def func():
    print("hello python")
list1 = [func,111,'abc']
print(list1)
print(list1[0])
list1[0]()


[<function func at 0x10e086160>, 111, 'abc']
<function func at 0x10e086160>
hello python

上面的例子是将函数func的名字当做一个元素存放在了列表中,可以通过下标取得函数名,也可以再加上括号list1[0]()来执行函数。

同样,将函数名存入元组、字典等容器都是可以的。

函数嵌套

函数的嵌套调用,在调用一个函数的过程中又调用其他函数

def a():
    pass
def b():
    pass
def c():
    pass
def func():
    a()
    b()
    c()

在函数内定义新的函数

def func():  
    def a():
        pass   
    def b():
        pass   
    return a(),b()

闭包函数

闭包函数是基于名称空间与作用域、函数对象和函数嵌套的综合应用。

什么是闭包函数

​ “闭”指的是该函数定义在一个函数内的函数,即内嵌函数;
​ “包”指的是该函数包含对外层函数作用域名字的引用(不是全局作用域)

def func1():
    x = 1
    def func2():    # 这里函数func2()即为闭函数,func2()是func1()函数的内置函数
        print(x)    # 函数func2()的x引用的值是外层函数的值,即为包函数
    return func2    # 将内置的闭包函数的函数名在外层函数进行return(是函数名,不是函数名加括号)		
x=1111
f=func1()
print(f)
f()


<function func1.<locals>.func2 at 0x10c29ea60>
1

名字的查找关系是以函数定义阶段为准

闭包函数的应用场景

实现另外一种传递参数的方式

def func1():
    a = 1
    def func2():
        print(a)
    return func2

func3 =func1()
func3()

将上方代码中的变量a通过参数传递

def func1(a):
    # a = 1
    def func2():
        print(a)
    return func2

func3 =func1(1)
func3()

文章作者: 老百
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 老百 !
 上一篇
Python之迭代器和生成器 Python之迭代器和生成器
迭代器(iterator)是访问集合内元素的一种方式,提供了一种遍历类序列对象的方法。从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。对于字典、文件对象、自定义对象类型等,可以自定义迭代方式,从而实现对这些对象的遍历。
2022-10-09
下一篇 
Python之文件操作 Python之文件操作
当我们要读取或者写入文件时,需要先打开文件;在文件操作完毕时,需要关闭文件,以便释放和文件操作相关的系统资源。
2022-10-06
  目录