Python函数基础[通俗易懂]

Python函数基础[通俗易懂]函数声明、调用、返回基础Python中使用def关键字来声明函数,声明函数的格式为:有3个需要注意的地方:1.函数名后面必须加冒号2.如果函数体和def不在同一行,则必须缩进3.ret

大家好,又见面了,我是你们的朋友全栈君。

函数声明、调用、返回基础

Python中使用def关键字来声明函数,声明函数的格式为:

def func_name(args):
    ...body...
    [return ...]

有3个需要注意的地方:

  1. 函数名后面必须加冒号
  2. 如果函数体和def不在同一行,则必须缩进
  3. return指定函数返回值,用来结束函数
    • 但return语句是可有可无的,如果不给return,则等价于加上了return None,即函数默认返回None结构

如果函数体body语句只有一行,或者可以简写为一行,则可以写在def的同行。例如:

def myfunc(x,y,z): print(x+y+z)

函数声明好之后,就可以执行函数,执行函数也称为调用函数,方式为func_name(args),例如:

myfunc(1,2,3)

函数中往往会包含一个return或多个return语句,它可以出现在函数中的任意位置处,它用来结束函数的执行,并返回给定的值。例如:

def func(x):
    return x+5

表示返回x+5的值,返回值是一种值类型,所以可以赋值给变量、可以输出、可以操作等等。例如:

print(func(3))    # 输出返回值

a=func(4)         # 赋值给变量
print(a)

print(func(5)+3)  # 数值操作

return语句是可选的,如果函数中不指定return语句,则默认返回None,即类似于return None

关于函数参数

函数的参数其实也是变量,只不过这些变量是独属于函数的本地变量,函数外部无法访问。在函数调用的时候,会将给定的值传递给函数的参数,这实际上是变量赋值的过程。

def myfunc(x,y,z):
    print(x,y,z)

myfunc(1,2,3)

def首先声明好函数,然后到了myfunc(1,2,3)时,表示调用函数(执行函数),调用函数时会将给定的值1,2,3传递给函数的参数x,y,z,其实就是变量赋值x=1,y=2,z=3,然后使用print输出它们。

由于python是动态语言,无需先声明变量,也无需指定变量的类型,所以python的函数参数和返回值非常的灵活。任何类型的变量或数据结构都可以传递给参数,这实际上是变量赋值的过程。例如:

myfunc(1,2,3)
myfunc("abc",2,"def")
myfunc([1,2,3],4,5)

上面几个函数调用语句中,赋值给参数的值可以是数值类型,可以是字符串类型,可以是列表类型,也可以是其它任何数据类型。

python函数的参数相比其它语言要复杂一些,意味着要灵活很多,短短一个小节的篇幅完全没法解释清楚,关于参数细节,详细内容见后面的文章

函数声明、调用的过程详述

def用来声明一个函数,python的函数包括函数名称、参数、函数体、函数体中涉及到的变量、返回值。

实际上,函数名称其实是一个变量名,def表示将保存在某块内存区域中的函数代码体赋值给函数名变量。例如:

def myfunc(x,y,z):
    ...CODE...

上面表示将函数体赋值给变量名myfunc。如下图:

Python函数基础[通俗易懂]

既然是变量,就可以进行输出:

def myfunc(x):
    return x+5

print(myfunc)

输出结果:

<function myfunc at 0x032EA6F0>

由于python是解释型语言,所以必须先定义函数,才能调用函数。

如果导入一个模块文件,导入的时候会解释、执行文件中的代码,包括def语句,也就是说,导入文件时会先声明好函数。

函数变量的细节

请一定理解本节内容,也许细节方面可能会有些不准确,但对于深入理解函数来说(不仅限于python语言),是非常有帮助的,特别是理解作用域规则的时候。

python是解释性语言,读一行解释一行,解释一行忘记一行。而函数是一种代码块,代码块是一个解释单元,是一个整体。在代码块范围内不会忘记读取过的行,也不会读一行就立即解释一行,而是读取完所有代码块内的行,然后统筹安排地进行解释。关于这一点,在后面的文章代码块详述中有非常详细的解释,建议一读。

当python读取到def所在行的时候,知道这是一个函数声明语句,它有一个属于自己的代码块范围,于是会读完整个代码块,然后解释这个代码块。在这个解释过程中,会记录好变量以及该变量的所属作用域(是全局范围内的变量还是函数的本地变量),但一定注意,def声明函数的过程中不会进行变量的赋值(参数默认值除外,见下文),只有在函数调用的时候才会进行变量赋值。换句话说,在def声明函数的过程中,在函数被调用之前,函数所记录的变量一直都是变量的地址,或者通俗一点理解为记录变量的名称,而不会进行变量的赋值替换

实际上,变量的明确的值会当作常量被记录起来。如a=10的10被作为常量,而变量a赋值给变量b时b=a,a显然不会作为常量。

如下函数:

x=3
def myfunc(a,b):
    c=10
    print(x,a,b,c)

myfunc(5,6)

输出结果为:”3 5 6 10″。

上面的函数涉及到了4个变量:a、b、c、x。其中:

  • 全局变量x
  • 本地变量a、b、c,其中本地变量a和b是函数的参数

在def的过程中,会完完整整地记录好这些变量以及所属作用域,但只会记录,不会进行变量的赋值。如下图:

Python函数基础[通俗易懂]

然后函数被调用,这时候才会开始根据记录的作用域搜索变量是否存在,是否已经赋值(非本地变量),并对需要赋值的变量赋值:

  • 查找全局变量变量x,它在全局作用域内已经赋值过了,所以只需找到这个全局变量即可
  • 查找本地变量a、b、c,它们是属于函数myfunc的本地变量,而a和b是参数变量,所以最先对它们进行赋值a=5,b=6,然后赋值普通的本地变量c=10

如图:

Python函数基础[通俗易懂]

最后执行print(x,a,b,c)输出这些变量的值。

还需注意,python是读一行解释一行的,在函数调用过程中,因为c=10print()的前面,所以是先赋值c=10,再执行print,如果print在c=10前面,则先执行print,再赋值,这显然是错误的,因为print()中使用了变量c,但目前还没有对其赋值。这和其它语言可能有些不同(特别是编译型语言),它们可能会无视变量赋值以及变量使用的位置前后关系。

如果上面的示例中,函数myfunc调用之前,将变量x赋值为另一个值:

x=3
def myfunc(a,b):
    c=10
    print(x,a,b,c)

x=33
myfunc(5,6)

这时将输出:”33 5 6 10″。因为x是全局变量,只有在函数调用的时候才会去找到变量x对应的值,而这时全局变量的值已经是33。

匿名函数lambda

匿名函数是指没有名称的函数,任何编程语言中,匿名函数都扮演着重要角色,它的功能非常灵活,但是匿名函数中的逻辑一般很简单,否则直接使用命名函数更好,匿名函数常用于回调函数、闭包等等。

在python中使用lambda关键字声明匿名函数,python中的lambda是一个表达式而不是一个语句,这意味着某些语句环境下可能无法使用def声明函数,但却可以使用lambda声明匿名函数。当然,匿名函数能实现的功能,命名函数也以一样都能实现,只不过有时候可能会比较复杂,可读性会更差。

lambda声明匿名函数的方式很简单,lambda关键字后面跟上参数列表,然后一个冒号,冒号后跟一个表达式。

lambda argl, arg2,... argN :expression statement

lambda表达式返回一个匿名函数,这个匿名函数可以赋值给一个变量

例如:

# 声明匿名函数,并赋值给变量f
f = lambda x,y,z: x+y+z

print(f)

输出结果:

<function <lambda> at 0x027EA6F0>

既然匿名函数赋值给了变量,这个函数就像是命名变量一样,可以通过这个变量去调用这个匿名函数。当然,它毕竟还是匿名函数,正如上面输出的结果中function <lambda>所示。而且,匿名函数并非一定要赋值给变量。

# 调用匿名函数
print(f(2,3,4))  # 输出9

匿名函数的返回值是冒号后面的表达式计算得到的结果。对于上面的示例,它等价于return x+y+z

因为lambda是一个表达式,所以可以写在任何表达式可以出现的位置处,而某些语句上下文环境中,并不能直接使用def来声明。例如,将函数保存到一个列表中:

L=[ lambda x: x * 2,
    lambda x: x * 3,
    lambda x: x * 4 ]

print(L[0](2))
print(L[1](2))
print(L[2](2))

上面的lambda出现在列表的内部,且这里面的匿名函数并赋值给某个变量。像def语句就无法出现在这样的环境中,如果真要使用def来声明函数,并保存到列表中,只能在L的外部使用def定义,然后将函数名来保存。

def f1(x): return x * 2
def f2(x): return x * 3
def f3(x): return x * 4

L=[f1,f2,f3]

print(L[0](2))
print(L[1](2))
print(L[2](2))

看上去没什么问题,但函数定义的位置和列表L定义的位置可能会相差甚远,可读性可能会非常差。

同理的,还可以将匿名函数保存在字典的value位置上:

key='four'
print(
    {
        'two':(lambda x: x * 2),
        'three':(lambda x: x * 3),
        'four':(lambda x: x * 4)
    }[key](2)
)

函数嵌套

函数内部可以嵌套函数。一般来说,在函数嵌套时,内层函数会作为外层函数的返回值(当然,并非必须)。既然内层函数要作为返回值,这个嵌套的内层函数更可能会是lambda匿名函数。

例如:

def f(x):
    y=10
    def g(z):
        return x+y+z
    return g

上面的函数f()中嵌套了一个g(),并返回这个g()。其实上面示例中的g()是一个闭包函数。

既然f()返回的是函数,这个函数可以赋值给其它变量,也可以直接调用:

# 将嵌套的函数赋值给变量myfunc
# 这时myfunc()和g()是等价的
myfunc = f(3)
print( myfunc(5) )

# 直接调用g()
print( f(3)(5) )

当然,嵌套lambda匿名函数也可以,且更常见:

def f(x):
    y=10
    return lambda z: x+y+z

嵌套在循环内部的函数

看下面嵌套在循环内部的函数,在每个迭代过程中都声明一个匿名函数,这个匿名函数返回循环控制变量i,同时将声明的匿名函数保存到列表L中。

def f():
    L=[]
    for i in range(5):
        L.append( lambda : i )
    return L

但如果调用该函数f(),并调用保存在列表中的每个匿名函数,会发现它们的值完全相同,且都是循环迭代的最后一个元素值i=4

List = f()
print(List[0]())
print(List[1]())
print(List[2]())
print(List[3]())
print(List[4]())

执行结果:

4
4
4
4
4

为什么会如此?为什么循环迭代过程中的i没有影响到匿名函数的返回值?这是一个非常值得思考的问题,如果不理解结果,请仔细回顾前文函数变量的细节。如果还是不理解,请阅读Python作用域详述

嵌套函数的作用域

此处给几个示例,这些示例的结果对于只学过python的人来说,可能会很容易理解,但对于学过其它语言的人来说,很容易混淆出错。

此处并不会对这些示例的结果进行解释,因为只要理解了前文函数变量的细节,这几个示例的结果很容易理解。

同样,更详细的内容参见Python作用域详述

如下示例:

x=3
def f():
    x=4
    g()
    print("f:",x)

def g():
    print("g:",x)

f()

输出:

g: 3
f: 4

如果在调用函数前,修改全局变量x的值:

x=3
def f():
    x=4
    g()
    print("f:",x)

def g():
    print("g:",x)

x=6
f()

输出:

g: 6
f: 4

如果把g()的声明放在f()的内部呢?

x=3
def f():
    x=4
    def g():
        print("g:",x)
    print("f:",x)
    x=5
    return g

f()()

输出:

f: 4
g: 5
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/156013.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月5日 下午4:36
下一篇 2022年7月5日 下午4:46


相关推荐

  • windows虚拟内存机制

    windows虚拟内存机制

    2022年3月6日
    68
  • shuffleNet_shuffer

    shuffleNet_shuffer论文:ShuffleNet:AnExtremelyEfficientConvolutionalNeuralNetworkforMobileDevices论文提到模型加速的方法为:1) 修剪网络,减少分支(pruningnetworkconnections)。2) 对于一个训练好的网络(pre-trainedmodel),在性能不下降的情况下减少冗余的分支。3) 量化(qua…

    2025年10月16日
    5
  • SPI协议学习笔记

    SPI协议学习笔记SPI优点与缺点优点:支持全双工,通信简单,数据传输速率快缺点:在数据可靠性上存在一定的缺陷,因为它不像I2C一样有ACK应答机制SPI总线的构成及信号类型SPI总线只需四条线(如图1所示)就可以完成MCU与各种外围器件的通讯:1)MOSI–Master数据输出,Slave数据输入2)MISO–Master数据输入,Slave数

    2022年10月10日
    3
  • Git合并分支

    Git合并分支合并分支操作如下 1 首先切换到 master 分支上 gitcheckoutm 确保 master 代码是最新的代码 gitpullorigi 然后我们把 dev 分支的代码合并到 master 上 gitmergedev4 然后查看状态及执行提交命令 gitstatusOnb origin master by12commits use gitpush topublish

    2026年3月19日
    1
  • 2026年Midjourney AI 图像生成器使用教程详解

    2026年Midjourney AI 图像生成器使用教程详解

    2026年3月16日
    2
  • malloc、calloc、realloc的区别

    malloc、calloc、realloc的区别1 C 语言跟内存分配方式从静态存储区域分配 nbsp nbsp nbsp nbsp nbsp nbsp 内存在程序编译的时候就已经分配好 这块内存在程序的整个运行期间都存在 例如全局变量 static 变量 在栈上创建 nbsp nbsp nbsp nbsp nbsp nbsp 在执行函数时 函数内局部变量的存储单元都可以在栈上创建 函数执行结束时这些存储单元自动被释放 栈内存分配运算内置于处理器的指令集中 效率很高 但是分配的内存容量有限 从堆上分配 亦称动态内存分配 nbsp nbsp nbsp nbsp nbsp nbsp 程序在运行的时候用 malloc 或 new 申请任意多少的内存 程序员自己负责在何时用 free 或 delete 释放内存 动态

    2026年3月17日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号