python(V)python函数的学习经验与知识总结,Python,学习心得,和,五


2020年4月13日10:12:26


提及函数,对于熟悉C语言的人而言 过程化的编程语言里面的函数简直就是灵魂般的存在。函数是一段可以重复使用,完成一个功能(或者一个任务)的程序代码块。函数的作用如下:

1、代码复用
2、代码一致性:只要修改函数的代码内容,该函数所有的调用处 都会被影响。因此函数功能的分割要尽量简单 单一 明确。

而函数的编写和调用,也是非常简单的。主要包括: 代码封装、函数调用、传递参数和函数返回等部分。


函数的概念和分类

1、一个程序由一个个小的子任务组成:一个个的函数就可以代表这些任务或者功能
2、函数是代码复用的通用机制

在Python里面,函数主要是分为以下几类:

  1. 内置函数:可以拿来直接使用的,例如:str()、list()、len()等
  2. 标准库函数:使用import语句 导入这些库,直接使用其中定义好的函数
  3. 第三方库函数:下载这些库之后,也是通过import来导入
  4. 用户自定义函数:为了完成自身某项需求的自定义函数

前面也说了,在Python里面 一切皆对象。 当然这里的函数 也是对象。执行完成def之后,系统新创建的函数对象由前面的函数名变量来引用。
在这里插入图片描述
上面例子的整个过程如下:

1、def 首先在堆区里面 创建一个函数对象(包含了函数的参数信息等)
2、在栈区一个叫做_myfunc的变量保存了 上面对象的地址
3、同样栈区的变量func也保存了 上面对象的地址(值的拷贝操作)
4、 当使用上面两个变量()的时候,会被系统认为 调用函数对象
5、下面就是具体调用函数对象的操作了

函数的定义和调用

在Python里面,定义一个函数的语法规则如下:

def functionname (【参数列表】):
----函数体(几条语句)

注意事项如下:

1、当使用def来定义函数的时候,紧接着是一个空格和functionname 。具体的动作:Python执行def时,创建一个函数对象,然后将其绑定到functionname 变量上面。函数对象是“function”类型
2、【参数列表】:无参数时,也得有小括弧;有形参时,之间用逗号隔开。形参不需要声明类型,也不需要指定函数返回值类型。但是形参必须和实参一一对应。
3、return 返回值:若是函数体里面有return 语句,则函数结束 执行return(返回 值);若是没有,则返回NONE值
4、调用肯定在定义之后(即:先调用def创建函数对象)。对于内置函数对象 会自动创建;标准库或者第三方库,通过import导入模块时,会执行模块里面的def语句

实例1:
在这里插入图片描述

函数的形参和实参

形式参数是在定义函数时使用,其命名只要符合“标识符命名规则”即可(使用在函数体里面,相当于局部变量)。实参是在调用该函数的时候,传递的参数。实例如下:
在这里插入图片描述
上面就是在函数定义之后(def的下面第一行),使用三个 双引号or单引号,即:文档字符串来进行函数的注释说明。

若是我们想看一下某个函数的文档字符串是怎么说明函数的,如下:

def _myadd(a,b):
    """ 
    这是注释:下面的函数实现 两个整数相加 
    可以是多行 or 一行
    """
    c=a+b
    return c


print(_myadd(1,1))
print('******************')

for i in range(4):
    c=_myadd(i,1)
    print(c)

help(_myadd.__doc__)#传递函数名(也就是变量),其__doc__属性保存文档字符串

上面的help函数 就可以实现,如下:
在这里插入图片描述

函数的返回值说明

函数的返回值是使用return来完成的,主要注意事项有下面几点:

1、若是函数体里面包含return语句,则由其 结束函数 并返回值
2、若是没有,则函数的返回值是None值
3、若是返回多个返回值,使用列表、元组、字典和集合将多个值“存起来”即可

在这里插入图片描述

Python变量作用域

说到变量的作用域:也就是在说 全局变量和局部变量。变量起作用的范围称之为变量作用域,在不同的作用域内 即使同名的变量也互不影响。

全局变量:

1、在函数和类定义 外面声明or定义的变量。作用域:自开始定义的地方直到模块结束
2、应避免全局变量的使用:因为降低函数的通用性和可读性
3、在Python里面,全局变量一般作为常量使用(供其他函数的使用)
4、若是需要在函数内改变全局变量的值,需要使用global声明一下

局部变量:

1、在函数体里面(包括形参)声明or定义的变量
2、要优先使用局部变量:因为其引用比全局变量更快
3、若是局部变量和全局变量同名,在函数内则 隐藏全局变量,而是使用这个局部变量(就近原则)

在这里插入图片描述
注:在第一次调用函数之后,全局变量的值就被改变了。所有第二次调用 就不能匹配成功了。

下面我们也可以输出一下 局部变量和全局变量:
在这里插入图片描述
连同后面的两个函数名 他们也可以算为全局变量。

局部变量是存在于函数栈帧空间里的,其引用的效率是比全局变量的高
在这里插入图片描述

Python的参数传递

在Python里面,函数参数传递的过程就是:从实参到形参的 赋值 操作。 而Python一切皆对象的特点又决定了 所有的赋值操作都是“引用的赋值”。 因此Python的参数传递都是“引用传递”(地址的赋值操作),而非值传递。具体的操作分为以下两类:

1、对可变对象的“写操作”:直接作用在原对象本身。可变对象:字典、列表、集合和自定义的对象等
2、对不可变对象的“写操作”:产生一个新的“对象空间”,并用新的值来填充这块空间。不可变对象:数字、字符串、元组和函数等

实例如下:
一、即使是传递参数为可变对象,其实质上也是 传递的对象的引用(就是地址)。在函数中 不创建新的对象拷贝,而是直接修改该对象(直接在所传递的对象上面操作)。
在这里插入图片描述
二、若是传递不可变对象(int float 字符串 元组 布尔值),虽然无法修改对象,但是我们这里传递的依旧是对象的引用(这是浅拷贝)。只是我们在实际的、真正的“赋值操作”时候(要改动原对象),系统会自动创建一个新对象。
在这里插入图片描述
上面 实参到形参的时候,旧对象的地址的拷贝(此时还是指向同一个对象)。但是下面执行 +=操作的时候,所指的对象是不可变的,因此 要创建一个新的对象(里面存储计算结果),并把新对象的地址赋给b。

于是下面的b就指向新对象,其id值也就变化了。原对象还是照旧 没有变化。

之前我们在学习C++的时候,关于参数传递 一个非常让人头疼的问题:浅拷贝引发同一块内存在多次析构or释放的时候 出现问题。

下面就看一下在Python里面的这个深拷贝和浅拷贝的问题:
在Python里面有两个内置函数:

  1. copy() 浅拷贝函数
  2. deepcopy()深拷贝函数

浅拷贝:只是子对象的引用的拷贝,不拷贝子对象的内容。
在这里插入图片描述
如上图所示:下面来演示一下这个过程( 浅拷贝 )。
在这里插入图片描述

深拷贝:原对象内容的拷贝(全部拷贝一份),新旧对象修改 互不干扰
在这里插入图片描述
如上图所示:下面来演示一下这个过程(深拷贝)。

在执行深拷贝的时候,把原对象的所有的子对象 (子子对象等)全部拷贝一份新的出来,这个对象赋值给b。此后在a 或者 b上面的各自的操作 互不影响 了。(上面新加的内容,都加到新对象上面了)

若是传递的这个对象是不可变的,但是其内部有可变的子对象(其子对象是一个可变对象的引用)。那么这个参数传递之后 若是进行改变该可变的子对象 则做的是浅拷贝!(原对象也变了) 如下所示:
在这里插入图片描述
上面的参数传递,是做的一个浅拷贝。(对象地址的拷贝),传递的对象不可变(元组)但是里面子对象有个可变子对象的引用。

Python的参数类型

第一种:位置参数

具体的函数调用的时候,实参默认按照位置顺序传递。即:需要个数和形参的匹配,这种按照位置传递的参数 称为:位置参数(通过位置来确定参数)。 注:只要形参和实参个数对的上 就没有问题。这个比较简单,就不说了。

第二种:默认值参数

提前为某些参数设置一个默认值,这样这些参数在没有 传值的时候,也是可以的。 注:这点和C++里面一样,默认值参数必须位于位置参数的后面。

实例如下:
在这里插入图片描述
第三种:命名参数

顾名思义,即可以直接按照形参的名称来传递参数,它也被称为“关键字参数”。 (直接把实参的值交到形参手里面)
在这里插入图片描述
第四种:可变参数

可变参数指的是:参数的数量不固定。分为以下两种情况:

1、 * parameter(一 个※)表示:将多个参数收集到一个元组对象当中
2、** parameter(两个※)表示:将多个参数收集到一个字典对象当中

在这里插入图片描述
第五种:强制命名参数

在带星号(可变参数)的后面,定义的参数 必须是“强制命名参数”( 不是可变参数的,都需要使用名字来直接参数传递 )。实例如下:
在这里插入图片描述

Lambda表达式 匿名函数

Lambda表达式 它可以用来声明匿名函数。Lambda函数是一种简单的(一行就可以定义成功的)函数, 实质上它是生成了一个函数对象。

由于它简单的特性:Lambda表达式只可以包含一个表达式,不能包含复杂的语句。且表达式的计算结果就是函数的返回值。其语法格式如下:

lambda arg1.arg2,arg3 …… :expression
arg 这些相当于函数的参数;expression是函数体;函数返回值就是表达式结果

在这里插入图片描述
也是生成一个函数对象,然后是对象地址的赋值。下面的例子 是函数对象的引用作为列表对象的一个元素。

下面来看一下 eval()函数,功能:将字符串string当成有效的表达式来求值并返回计算结果。其语法格式如下所示:

eval(source[,globals[,locals]])->value
1、source:一个Python表达式or函数 compile()返回的代码对象
2、globals:可选的,必须是字典
3、locals:可选的,任意的映射对象

在这里插入图片描述

Python的递归函数

递归函数:在函数体内部直接or间接地自己调用自己。这个很简单我们就不说太多,每个递归函数必须包含以下两个部分:

1、结束条件:表示递归的结束,以及return 返回值
2、递归基:具体做的事

递归函数一般会创建大量的函数对象出来(栈空间的消耗),其对内存的使用也是非常大。(这也是我不推荐递归而是推荐递推式的原因)
在这里插入图片描述
上面的f2函数是计算阶乘的,我们考虑一下,若是结束条件改成:

def _func2(mu,a):
    if a!=1:
        mu*=a
        print(mu)
        _func2(mu,a-1)
    else:
        print("*"*15)
        print(mu)

_func2(1,4)

就在同样达成目的的时候,少了一个栈帧的开辟和回退。

好的下面我们不用递归来计算阶乘:
在这里插入图片描述
nonlocal关键字

前面我们也说过了可以使用global关键字 来 声明函数(一个函数的inner函数也可以)里面使用的是全局变量。这里的nonlocal关键字则是 用来声明外层的局部变量。 其使用场景如下:

在一个函数里面定义了一个局部变量,可是该函数里面的inner函数 想使用这个局部变量则就用到了nonlocal关键字。(之后就可以改变其值了)

在这里插入图片描述

Python的嵌套函数

在Python中,嵌套函数这一点和我最熟悉的C/C++是相当不一样的:C/C++不支持函数里面再行定义函数。

Python的嵌套函数,而我更喜欢称其为 内部函数
在这里插入图片描述
function2是只可以在f1里面 调用的,外面用不了

根据存在即合理的观点,这个看似多此一举的语法。也有其实用的地方:

1、封装(数据隐藏):外面无法访问 内部函数
2、在内部定义函数,就可以达到代码复用的目的。(避免冗余代码)
3、闭包

实例如下:
在这里插入图片描述
下面的一个函数实现了 上面两个函数的功能。且做到了底层细节的封装。(注:有小伙伴说我搞错了川普的名字 可是 who care?我又不是他爹)

Python的LEGB规则

它的用处主要是在: 查找名字的时候,所遵循的规则。 如下(依次向下):

1、 L ocal 指的是函数or类方法的 内部
2、 E nclosed 指的是嵌套函数(一个函数包含着另一个函数,即闭包)
3、 G lobal 指的是模块中的全局变量
4、 B uilt in 指的是Python自己保留的特殊名字

若是某个名字映射在局部命名空间(local)里面没有找到,则接着 按顺序依次向下 (在下面的命名空间里面寻找)。

需要注意的如下:

  1. 最后若是这个名字在所有的命名空间里面都没有找到,就会产生一个NameError
  2. 若是找的时候找到了 就直接用了。(哪怕还有下面的名字映射也不管了,覆盖作用)