函数的初识

一 、初识函数

大家都有没有玩过陌陌,探探这种软件?这种软件是专门为没有对象的男士设置的,这是一款合理合法的约x,哦不对,交友软件,那么现在我们用Python代码简单模拟一下:

print("拿出手机")

print("打开陌陌")

print('左滑一下')

print('右滑一下')

print("找个漂亮的妹子")

print("问她,约不约啊!")

print("ok 走起")

​ ok so easy我们已经完成了我们模拟探探的需求,其实我们做的这个就是一个功能,什么功能呢?就是交友的功能,那么问题来了, 我还想在约一次整么搞,是不是就是在写一遍

# 约一次

pint("拿出手机")

print("打开陌陌")

print('左滑一下')

print('右滑一下')

print("找个漂亮的妹子")

print("问她,约不约啊!")

print("ok 走起")


# 再约一次

pint("拿出手机")

print("打开陌陌")

print('左滑一下')

print('右滑一下')

print("找个漂亮的妹子")

print("问她,约不约啊!")

print("ok 走起")

​ 那么这样写好么? 当然不好了,为什么呢? 重复代码太多了。 所以我们能否将这些代码放到一个地方,想用这些代码了,我就通过一个指令,调用过来,不想用就不写这个指令就行了,这样就能极大限度的减少代码的重复率,那么咱们看下面:

def date():
    print("拿出手机")
    print("打开陌陌")
    print('左滑一下')
    print('右滑一下')
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")

​ 那么这里,我写了一个约会的功能,我将上面的那些重复代码封装到这个所谓的函数中,这样,我什么时候需要使用这个功能,我通过一个指令调用即可。

def date():
    print("拿出手机")
    print("打开陌陌")
    print('左滑一下')
    print('右滑一下')
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")


date()  # 调用函数

上面这个就是一个函数,那么接下来我们就来研究一下这个函数。

从上面的对比我们看一下函数的优势:

​ 1,减少代码的重复性。

​ 2,使代码可读性更好。

二、函数的结构与调用

2.1函数的结构

首先咱们先看一下函数的结构:

def 函数名(形式参数):
    函数体
    return 数据
v = func(实际参数)

def 关键词开头,空格之后接函数名称和圆括号(),最后还有一个”:”。

def 是固定的,不能变,他就是定义函数的关键字。

空格 为了将def关键字和函数名分开,必须空(四声),当然你可以空2格、3格或者你想空多少都行,但正常人还是空1格。

函数名:函数名只能包含字符串、下划线和数字且不能以数字开头。虽然函数名可以随便起,但我们给函数起名字还是要尽量简短,并且要具有可描述性

括号:是必须加的,括号里面写形式参数。参数后续会讲到。

下面的函数体一定全部都要缩进,这代表是这个函数的代码。

我们现在就来实现刚刚我们说的定义一个约会功能:

def date():
    print("拿出手机")
    print("打开陌陌")
    print('左滑一下')
    print('右滑一下')
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")

哦了,这就定义完了,但是这个时候我们去执行,会发生什么现象? 什么都没有发生.因为我只定义了一个函数.但是还没有执行过这个函数.

2.2函数的调用

使用函数名加小括号就可以调用了 写法:函数名() 这个时候函数的函数体会被执行

通过上面的动态图大家可知,只有解释器读到函数名() 时,才会执行此函数,如果没有这条指令,函数里面即使有10万行代码也是不执行的。

而且是这个指令你写几次,函数里面的代码就运行几次,就好比你在部队,我喊你名字,喊几次,你就得 到 几次,这就是指令。

直接调用定义好的函数就可以了 咱们约完之后的得有个结果吧, 比如是约的萝莉,还是大妈啊总得有个结果,那么这个结果

怎么来描述和获得呢? 这就涉及到函数的返回值啦

三、 函数的返回值

​ 一个函数就是封装一个功能,这个功能一般都会有一个最终结果的,比如你写一个登录函数,最终登录成功与否是不是需要返回你一个结果?还有咱们是不是都用过len这个函数,他是获取一个对象的元素的总个数,最终肯定会返回一个元素个数这样的结果:

s1 = 'abfdas'
print(len(s1))  # 6
那么这个返回值如何设置呢?这就得用到python中的一个关键字:return

def date():
    print("拿出手机")
    print("打开陌陌")
    print('左滑一下')
    print('右滑一下')
    print("找个漂亮的妹子")
    return
    print("问她,约不约啊!")
    print("ok 走起")
  1. 函数中遇到return,此函数结束.不在继续执行

那么函数的返回值,既然叫做返回值,他就是返回一些数据,那么返回给谁呢?

跟我们之前使用的len一样,函数的返回值返回给了 函数名() 这个整体,也就是这个执行者。

  1. return 会给函数的执行者返回值。

当然,也可以返回多个值。

如果返回多个值,是以元组的形式返回的。

总结一下:

  1.遇到return,函数结束,return下面的(函数内)的代码不会执行。

  2.return 会给函数的执行者返回值。

      如果return后面什么都不写,或者函数中没有return,则返回的结果是None

      如果return后面写了一个值,返回给调用者这个值

      如果return后面写了多个结果,,返回给调用者一个tuple(元组),调用者可以直接使用元组的解构获取多个变量。

def date():
    print("拿出手机")
    print("打开陌陌")
    print('左滑一下')
    print('右滑一下')
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")
    return '漂亮的妹子','小萝莉', '成熟女性'
g1,g2,g3 = date()
print(g1, g2, g3)  # 漂亮的妹子 小萝莉 成熟女性

四.、函数的参数

​ 我们上面研究了,函数的结构,函数的执行,以及函数的返回值。对函数有一个初步的了解,那么接下来就是一个非常重要的知识点,函数的参数。函数是以功能为导向的,上面我们写的函数里面的代码都是写死的,也就是说,这个函数里面的更改起来很麻烦,试想一下,我们使用探探,陌陌等软件,可不可以进行筛选,比如选择性别,年龄等,导出结果? 再拿我们之前学过的len 这个len是不是可以获取字符串的总个数?是不是可以获取列表的总个数?你更改了len函数内部的代码了?没有吧?你看下面的例子:

s1 = 'sfdas'
l1 = [1, 3, 7]
print(len(s1))  # 5
print(len(l1))  # 3

那么我们写的函数也是可以将一些数据传到函数里面的,然后让里面的代码利用上这个数据产生我们想要的结果,在返回。

举个例子:

def date(sex):
    print("拿出手机")
    print("打开陌陌")
    print('设置筛选条件:性别: %s' %sex)
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")

date('女')

上面就是函数传参的示例,函数的参数可以从两个角度划分:

  1.形参

    写在函数声明的位置的变量叫形参,形式上的一个完整.表示这个函数需要xxx

  2.实参

    在函数调用的时候给函数传递的值.加实参,实际执行的时候给函数传递的信息.表示给函数xxx

 函数的传参就是函数将实际参数交给形式参数的过程.

def date(sex):  # 函数定义时(参数) 这个就是形参
    print("拿出手机")
    print("打开陌陌")
    print('设置筛选条件:性别: %s' %sex)
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")

date('女')  # 函数执行时(参数) 这个就是实参
# 这个过程就是:代码运行到date('女')开始执行此函数同时将字符串'女'这个数据传递给变量sex,然后执行函数中的代 # 码。如果遇到sex,其实就是使用'女'这个数据。

我们先从实参角度学习函数的参数。

实参角度

1,.位置参数

位置参数就是从左至右,实参与形参一一对应。

def date(sex, age, hobby):
    print("拿出手机")
    print("打开陌陌")
    print('设置筛选条件:性别: %s,年龄:%s,爱好:%s' %(sex, age, hobby))
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")
date('女','25~30','唱歌')
date('人妖','20~25','萝莉音')

练习

编写函数,给函数传递两个参数a,b a,b相加 返回a参数和b参数相加的和

def f(a,b):
    c = a+b
    return c

num_sum = f(5,8)
print(num_sum)
结果: 13

编写函数,给函数传递两个参数a,b 比较a,b的大小 返回a,b中最大的那个数

def f(a,b):

    if a>b:

        return a

    else:

        return b

num_sum = f(5,8)

print(num_sum)
结果:8

比较大小的这个写法有点麻烦,我们在这里学一个三元运算符

def f(a,b):

    c = a if a > b else b  #当a>b就把a赋值给c,否则就把b赋值给c

    return c

msg = f(5,7)

print(msg)

结果:7

2,.关键字参数 

位置参数好不好呢? 如果是少量的参数还算OK, 没有问题. 但是如果函数在定义的时候参数非常多怎么办? 程序员必须记住, 函数有哪些参数, 而且还有记住每个参数的位置, 否则函数就不能正常调用了. 那则么办呢? python提出了一种叫做关键字参数. 我们不需要记住每个参数的位置. 只要记住每个参数的名字就可以了

def date(sex, age, hobby):
    print("拿出手机")
    print("打开陌陌")
    print('设置筛选条件:性别: %s,年龄:%s,爱好:%s' %(sex, age, hobby))
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")
date(hobby='唱歌',sex='女',age='25~30',)

搞定, 这样就不需要记住繁琐的参数位置了.

3. 混合参数

可以把上面两种参数混合着使用. 也就是说在调用函数的时候即可以给出位置参数, 也可以指定关键字参数.

混合参数一定要记住:关键字参数一定在位置参数后面。
def date(sex, age, hobby):
    print("拿出手机")
    print("打开陌陌")
    print('设置筛选条件:性别: %s,年龄:%s,爱好:%s' %(sex, age, hobby))
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")
date('女',hobby='唱歌',age='25~30',)

4.万能参数(打散)

  • *args

    可以接收任意个数的位置参数,并将参数转换为元组

    • 调用函数时,实际参数有*

      def func(*args):
      print(args)
      func(*(1, 2, 3, 4))    # 输出结果:(1, 2, 3, 4)
      func(*[1, 2, 3, 4])    # 输出结果:(1, 2, 3, 4)
    • 调用函数时,实际参数无*

      def func(*args):
      print(args)
      func((1, 2, 3, 4))   # 输出结果:((1, 2, 3, 4),)

      只能用位置传参

  • **kwargs

    可以接收任意个数的关键字参数,并将参数转换为字典

    • 调用函数时,实际参数有**

      def func(**kwargs):
      print(kwargs)
      func(**{'k1':'1','k2':'william'})    # 输出结果:{'k1': '1', 'k2': 'william'}

 

     调用函数时,实际参数无**

    def func(**kwargs):
   print(kwargs)
    func(k1=1,k2='william')    # 输出结果:{'k1': 1, 'k2': 'william'}

    只能用关键字传参

参数顺序:位置参数,* args,默认参数,* *kwargs

综上: 在实参的⾓角度来看参数分为四种:

1. 位置参数
2. 关键字参数
3. 混合参数,  位置参数必须在关键字参数前面
4.万能参数

接下来我们从形参角度分析,函数的参数

形参角度

1, 位置参数

  位置参数其实与实参角度的位置参数是一样的,就是按照位置从左至右,一一对应

def date(sex, age, hobby):
    print("拿出手机")
    print("打开陌陌")
    print('设置筛选条件:性别: %s,年龄:%s,爱好:%s' %(sex, age, hobby))
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")
date('女','25~30','唱歌')

2, 默认值参数

  在函数声明的时候, 就可以给出函数参数的默认值. 默认值参数一般是这个参数使用率较高,才会设置默认值参数,可以看看open函数的源码,mode=‘r’就是默认值参数. 比如, 我们录入咱们班学生的基本信息. 通过调查发现. 我们班大部分学生都是男生. 这个时 候就可以给出⼀一个sex=’男’的默认值.

def stu_info(name, age, sex='男'):   

    print("录入学生信息")

    print(name, age, sex)   

    print("录入完毕")



stu_info("张强", 18)

注意:必须先声明在位置参数,才能声明关键字参数

综上:在形参的角度来看

  1. 位置参数
  2. 默认认值参数(大多数传进来的参数都是一样的, 一般用默认参数

python函数进阶

名称空间,作用域

名称空间:

接下来的内容,理论性的偏多,就是从空间角度,内存级别去研究python。首先什么是全局名称空间:

​ 在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间. 随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空.

​ 我们首先回忆一下Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟里一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读如内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。

​ 等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量回储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

我们给这个‘存放名字与值的关系’的空间起了一个名字——-命名空间。

代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间;

在函数的运行中开辟的临时的空间叫做局部命名空间也叫做临时名称空间。

现在我们知道了,py文件中,存放变量与值的关系的一个空间叫做全局名称空间,而当执行一个函数时,内存中会临时开辟一个空间,临时存放函数中的变量与值的关系,这个叫做临时名称空间,或者局部名称空间。

​ 其实python还有一个空间叫做内置名称空间:内置名称空间存放的就是一些内置函数等拿来即用的特殊的变量:input,print,list等等,所以,我们通过画图捋一下:

img

那么这就是python中经常提到的三个空间。

总结:

​ \1. 全局命名空间–> 我们直接在py文件中, 函数外声明的变量都属于全局命名空间

​ \2. 局部命名空间–> 在函数中声明的变量会放在局部命名空间

​ \3. 内置命名空间–> 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间

加载顺序:

​ 所谓的加载顺序,就是这三个空间加载到内存的先后顺序,也就是这个三个空间在内存中创建的先后顺序,你想想他们能是同时创建么?肯定不是的,那么谁先谁后呢?我们捋顺一下:在启动python解释器之后,即使没有创建任何的变量或者函数,还是会有一些函数直接可以用的比如abs(-1),max(1,3)等等,在启动Python解释器的时候,就已经导入到内存当中供我们使用,所以肯定是先加载内置名称空间,然后就开始从文件的最上面向下一行一行执行,此时如果遇到了初始化变量,就会创建全局名称空间,将这些对应关系存放进去,然后遇到了函数执行时,在内存中临时开辟一个空间,加载函数中的一些变量等等。所以这三个空间的加载顺序为:内置命名空间(程序运行伊始加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载。

取值顺序:

​ 取值顺序就是引用一个变量,先从哪一个空间开始引用。这个有一个关键点:从哪个空间开始引用这个变量。我们分别举例说明:

# 如果你在全局名称空间引用一个变量,先从全局名称空间引用,全局名# 称空间如果没有,才会向内置名称空间引用。
input = 666
print(input) # 666
# 如果你在局部名称空间引用一个变量,先从局部名称空间引用,
# 局部名称空间如果没有,才会向全局名称空间引用,全局名称空间在没有,就会向内置名称空间引用。
input = 666
print(input) # 666
input = 666
def func():
    input = 111
    print(input) # 111
func()

所以空间的取值顺序与加载顺序是相反的,取值顺序满足的就近原则,从小范围到大范围一层一层的逐步引用。

img

作用域

作用域就是作用范围, 按照生效范围来看分为全局作用域和局部作用域

全局作用域: 包含内置命名空间和全局命名空间. 在整个文件的任何位置都可以使用(遵循 从上到下逐⾏执行).

局部作用域: 在函数内部可以使用.

作⽤域命名空间:

​ 1. 全局作用域: 全局命名空间 + 内置命名空间

​ 2. 局部作⽤域: 局部命名空间

内置函数globals(),locals()

这两个内置函数放在这里讲是在合适不过的,他们就直接可以反映作用域的内容,有助于我们理解作用域的范围。

globals(): 以字典的形式返回全局作用域所有的变量对应关系。

locals(): 以字典的形式返回当前作用域的变量的对应关系。

这里一个是全局作用域,一个是当前作用域,一定要分清楚,接下来,我们用代码验证:

# 在全局作用域下打印,则他们获取的都是全局作用域的所有的内容。
a = 2
b = 3
print(globals())
print(locals())
'''
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001806E50C0B8>, 
'__spec__': None, '__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>, 
'__file__': 'D:/lnh.python/py project/teaching_show/day09~day15/function.py',
'__cached__': None, 'a': 2, 'b': 3}
'''

# 在局部作用域中打印。
a = 2
b = 3
def foo():
    c = 3
    print(globals()) # 和上面一样,还是全局作用域的内容
    print(locals()) # {'c': 3}
foo()

 高阶函数(函数的嵌套)

​ 其实我们见到了嵌套这个词不陌生,之前我们讲过列表的嵌套,列表的嵌套就是一个列表中还有列表,可能那个列表中还有列表……那么顾名思义,函数的嵌套,就是一个函数中,还有函数。

​ 想要玩明白函数的嵌套,关键点:只要遇见了函数名+()就是函数的调用. 如果没有就不是函数的调用,吃透这一点就算明白了。那么我们举例练习:找同学依次说出下面代码的执行顺序

# 例1:
def func1():
    print('in func1')
    print(3)
def func2():
    print('in func2')
    print(4)
func1()
print(1)
func2()
print(2)

# 例2:
def func1():
    print('in func1')
    print(3)
def func2():
    print('in func2')
    func1()
    print(4)
print(1)
func2()
print(2)
# 例3:
def fun2(): 
    print(2) 
    def fun3(): 
        print(6) 
    print(4) 
    fun3() 
    print(8)
print(3)
fun2()
print(5)

2.4 关键字:global、nonlocal

global

讲这个关键字之前,先给大家看一个现象:

a = 1
def func():
    print(a)
func()
a = 1
def func():
    a += 1 # 报错
func()

​ 局部作用域对全局作用域的变量(此变量只能是不可变的数据类型)只能进行引用,而不能进行改变,只要改变就会报错,但是有些时候,我们程序中会遇到局部作用域去改变全局作用域的一些变量的需求,这怎么做呢?这就得用到关键字global:
global第一个功能:在局部作用域中可以更改全局作用域的变量。

count = 1
def search():
    global count
    count = 2
search()
print(count)

利用global在局部作用域也可以声明一个全局变量。

def func():
    global a
    a = 3
func()
print(a)

所以global关键字有两个作用:

1,声明一个全局变量。

2,在局部作用域想要对全局作用域的全局变量进行修改时,需要用到 global(限于字符串,数字)。

nonlocal

nonlocal是python3x新加的功能,与global用法差不多,就是在局部作用域如果想对父级作用域的变量进行改变时,需要用到nonlocal,当然这个用的不是很多,了解即可。

def add_b():
    b = 42
    def do_global():
        b = 10
        print(b)
        def dd_nonlocal():
            nonlocal b
            b = b + 20
            print(b)
        dd_nonlocal()
        print(b)
    do_global()
    print(b)
add_b()

nonlocal关键字举例

nonlocal的总结:

1,不能更改全局变量。

2,在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。

版权声明:本文为william0709原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/william0709/p/16367440.html