目录

一,函数简介

1,函数的基本概念

2,函数的分类

二,函数的定义与调用

1,python函数的定义

2,函数的调用

3,return语句

三,变量的作用域(全局变量和局部变量)

1,全局变量

2,局部变量

四,参数的传递

1,传递可变和不可变对象的变量名(引用)

2,深拷贝和浅拷贝

五,lambda表达式与匿名函数

六,eavl()函数

七,递归函数

八,嵌套函数

1,嵌套函数

2,nonlocal关键字

九,LEGB原则


一,函数简介

        如数学上的函数一样,例如三角函数sin(),给一个角度值,他就会有一个结果。用计算器计算时,我们知道输入角度就会有结果,那么在编程时如何实现sin()这个函数的功能呢?

1,函数的基本概念

        函数也叫功能,它是对数据与代码的封装,实现了代码的复用。

        当我们在pycharm中写代码时,假如先写了一个功能的代码,我们可以右键进行运行;如果我们又写了一个功能代码,点击运行时,两个功能代码都会运行,这时就可以把各个功能的代码块进行封装起来,写成函数。下次想要用哪个功能就调哪个函数。

2,函数的分类

        python函数有四类,分别是:内置函数(builtin functions),标准库函数,第三方库函数,自定义函数。

        内置函数:python一经运行就加载到内存的,例如有list,len,str等函数

        标准库函数:需要用import语句进行导入,常见标准库有time,os等

        第三方库:需要另外下载到本地的库,例如opencv库,然后用import导入

        自定义函数:自己在模块里的写的函数

二,函数的定义与调用

1,python函数的定义

        定义语法如下:

def 函数名 (参数) :
    '''文档字符串'''
    函数体/若干语句
def func():
    """
    打印'人生苦短,我学python'
    """
    print('人生苦短,我学python')

        当python解释器遇到def时,他就会在内存里创建一块内存块来存储代码信息,即函数对象,然后将内存块的首地址给函数名称,实现变量名与函数对象的绑定。

        然而并不会执行,因为还没有调用。

2,函数的调用

        语法格式如下:

函数名()
def func():
    """
    打印'人生苦短,我学python'
    """
    print('人生苦短,我学python')


func()
# 人生苦短,我学python

        在函数名的后面加上英文小括号()即可。

        另外除了用函数名来绑定函数对象之外,也可以用其他变量名来绑定函数。

def func():
    """
    打印'人生苦短,我学python'
    """
    print('人生苦短,我学python')


func_copy = func  # func_copy也与函数对象进行了绑定
func_copy()
# 人生苦短,我学python

        函数对象也有三属性,也就是类型,id,值。

        类型就是函数类型;id就是函数对象的地址;值就是函数封装的各种数据和代码,但是利用print打印时只会打印出函数的id地址。

def func():
    """
    打印'人生苦短,我学python'
    """
    print('人生苦短,我学python')


func_copy = func
print(func_copy, 'and', type(func_copy), 'and', id(func_copy), 'and', id(func))
# <function func at 0x000002B644B11A68> and <class 'function'> and 2981859760744 and 2981859760744

3,return语句

        return语句用于将函数处理结果返回,或者返回一些其他数据。当return被执行,代表函数调用结束,也就是说return语句的作用之二就是结束函数的调用。

def maxab(a, b):
    '''
    比较两个整数的大小
    '''
    if type(a) == int and type(b) == int:
        return a if a >= b else b
    else:
        return '类型错误'


print(maxab(1, 2))
print(maxab(1, 'q'))
# 2
# 类型错误

        如果函数体里不写return,默认返回None。

def pr():
    print(666)


print(pr())  # 先调用pr,再打印出返回值
# 666
# None

        return可以返回任何东西。

def test():
    return [1, 2, 3, 4]  # 返回一个列表
print(test())

def test():
    return test  # 返回函数对象本身
print(test()())

def test():
    return range(5)  # 返回一个range对象
for itm in test():
    print(itm)
# [1, 2, 3, 4]
# <function test at 0x000001FF865119D8>
# 0
# 1
# 2
# 3
# 4

三,变量的作用域(全局变量和局部变量)

        变量起作用的范围称为变量的作用域,不同作用域内同名变量之间互不影响。变量分为:全局变量、局部变量。

        所谓起作用的范围就是,某些代码他隶属于不同的语句。例如定义了一个函数,函数体里的所有代码是属于这个函数的,因为缩进已经不同了。在函数体里的定义的变量在函数里面可以使用,但是在函数外边却用不了。

1,全局变量

        1 ,在函数和类定义之外声明的变量。全局变量的缩进为0,作用域为定义的模块,从定义位置开始直到模块结束。也就是说,全局变量即使没有定义在函数里边,但是在函数里边也可以使用,只是使用而已,修改的话需要作说明。这就是全局变量在整个.py文件里都可以访问使用的原因。

        2 ,全局变量降低了函数的通用性和可读性。应尽量避免全局变量的使用。

        3 ,要在函数内修改全局变量的值,使用 global 声明一下。

out = 520  # 全局变量
print(out, 'and id is ', id(out))


def test():
    out = 520  # 局部变量
    print(out, 'and id is ', id(out))


test()
# 520 and id is  2305420215504
# 520 and id is  2305450107984
# 明显两个id不同,因为在函数里面对全局变量进行修改,会隐藏全部变量,另外生成一个新对象

        两个out变量名字虽然相同,但不是绑定的同一个对象。但是当整数比较小时,由于整数缓存,他们都是同一个变量。

out = 520
print(out, 'and id is ', id(out))


def test():
    global out  # 用global声明out变量和全局变量out是同一个
    print(out, 'and id is ', id(out))


test()
# 520 and id is 2355312182480
# 520 and id is 2355312182480

        在函数里用global把同名变量声明为全局变量,则会修改函数外部的变量。

2,局部变量

        1 ,在函数体中声明的变量。(包括形参变量也是局部变量)。

        2 ,局部变量的引用比全局变量快,优先考虑使用。这里是说,在函数或者类里面操作自己的局部变量比操作外部变量快。

        3 ,如果局部变量和全局变量同名,如果对同名变量进行赋值操作,则在函数内隐藏全局变量,只使用同名的局部变量

        总结就是:全局变量在整个.py文件里的任何位置都可以访问使用,但是在函数里或者类里面对全部变量进行了修改(也就是定义了同名函数并赋值)则会隐藏外部变量。

四,参数的传递

1,传递可变和不可变对象的变量名(引用)

        函数参数传递的本质是用实参给形参赋值的操作。那么到底赋值的是实参对象的值还是实参对象的地址呢?实际上,在python中传的都是地址。

        当传给形参的对象是不可变的对象,例如元组,数字,字符串,函数。且要对这些不可变对象进行修改时,就会把这些不可变对象重新复制一份,然后对这个复制的对象进行修改,原来对象不会变。

        当传给形参的对象是可变的对象,例如列表,字典,集合等,由于传的是地址,如果进行修改,则会在原来的基础上进行修改。

a = 520
li = [1, 2, 3]
print(a, id(a))
print(li, id(li))
# 520 2370091074768
# [1, 2, 3] 2370091241992

def test(a, li: list):
    a = 520
    li.append(666)
    print(a, id(a))
    # 2370092721232  # 很明显不可变对象数字520的id已经改变了


test(a, li)
print(a, id(a))
print(li, id(li))
# 520 2370091074768
# [1, 2, 3, 666] 2370091241992  # 很明显,li列表虽然添加了一个值,但是id没有改变

2,深拷贝和浅拷贝

        浅拷贝大致理解就是一栋楼,把一栋楼的单元号拿走,利用单元号这个引用,来查询这栋楼里的各个房间。

        深拷贝大致理解为直接把一栋楼一模一样的再修一栋,而且里面的房间号的都不变,住的人也不变,但是单元号会改变。

        专业的说就是这样的:

        浅拷贝:拷贝对象,但不拷贝子对象的内容,只是拷贝子对象的引用。会改变源对象。

        深拷贝:拷贝对象,并且会连子对象的内存也全部(递归)拷贝一份,对子对象的修改不会影响源对象。

        可以利用copy模块的浅拷贝copy函数和深拷贝deepcopy函数来实现浅拷贝和深拷贝。

# 浅拷贝
import copy as ctrl_c

obj1 = [1, [2, 3, 4], (5, 6)]
print(obj1, 'and id is', id(obj1))
# [1, [2, 3, 4], (5, 6)] and id is 2402279456584
co1 = ctrl_c.copy(obj1)  # 浅拷贝复制
co1.append(7)  # 复制后进行改变对象,在对象尾部添加一个元素,不改变源对象
co1[1].append('python')  # 浅拷贝复制后进行改变对象,在第二个子对象列表后面添加一个元素,会改变obj1源对象
print(obj1, 'and id is', id(obj1))  # 复制前和复制后进行比较
# [1, [2, 3, 4, 'python'], (5, 6)] and id is 197379204698
print(co1, 'and id is', id(co1))
# [1, [2, 3, 4, 'python'], (5, 6), 7] and id is 1973792061384
# 深拷贝
import copy as ctrl_c

obj1 = [1, [2, 3, 4], (5, 6)]
print(obj1, 'and id is', id(obj1))
# [1, [2, 3, 4], (5, 6)] and id is 2077189504904
co1 = ctrl_c.deepcopy(obj1)  # 深拷贝复制
co1.append(7)  # 深拷贝复制后进行改变对象,在对象尾部添加一个元素,不改变源对象
co1[1].append('python')  # 复制后进行改变对象,在第二个子对象列表后面添加一个元素,由于是深拷贝,不会改变obj1源对象
print(obj1, 'and id is', id(obj1))  # 复制前和复制后进行比较
# [1, [2, 3, 4], (5, 6)] and id is 2077189504904
print(co1, 'and id is', id(co1))
# [1, [2, 3, 4, 'python'], (5, 6), 7] and id is 207718951930

        由此可见,深拷贝相比于浅拷贝,区别在于深拷贝把子对象的值都给复制过去了,而不是拿一个引用过去。

        最后,函数参数的传递实际上浅拷贝。也就是说如果序列类型(list,tuple,dict)里面仍然存在序列类型的元素,则里边的序列对象的值会发生改变。如li = [1,2,3,[4,5,6],7,8,9],里面的[4,5,6]属于外边列表li的一个元素,假如把li当成参数传出去,并且对li里面的[4,5,6]进行了修改,则li也会跟着变。原因就是li里面只是存的是[4,5,6]的地址,li将其地址暴露了。

五,lambda表达式与匿名函数

        lambda 表达式可以用来声明匿名函数。 lambda 函数是一种简单的、在同一行中定义函数的方法。 lambda 函数实际生成了一个函数对象。lambda 表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值。

        lambda 表达式的基本语法如下:

lambda  args1,args2,args3... :  <表达式>

        args1,args2,args3 为函数的参数。<表达式>相当于函数体。运算结果是:表达式的运算结果。

f = lambda a, b, c: a + b + c
print(f)
print(f(1, 2, 3))
# <function <lambda> at 0x00000127CC2180D8>
# 6

        lambda匿名函数适用于简单功能的函数。所谓匿名是指他不想一般定义函数的时候会给一个名字,lambda声明时没有给名字。

六,eavl()函数

        功能:将字符串 str 当成有效的表达式来求值并返回计算结果。

str1 = '1+1'
print(eval(str1))
# 2

七,递归函数

        递归(recursion)是一种常见的算法思路,在很多算法中都会用到。递归的基本思想就是“自己调用自己。关键在于什么时候停止调用自己并逐次返回。

        递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。每个递归函数必须包含两个部分:1 ,终止条件:表示递归什么时候结束。一般用于返回,不再调用自己。2 ,递归步骤:把第n步的值和第n-1步相关联。另外,递归函数由于会创建大量的函数对象、过量的消耗内存和运算能力。在处理大量数据时,谨慎使用。

# 利用递归求阶乘
def fact(n):
    if n >= 0:
        if n == 0:
            return 1
        if n == 1:
            return 1
        return n * fact(n - 1)


print(fact(3))  # 6

八,嵌套函数

1,嵌套函数

        嵌套函数(内部函数)是指定义在一个函数内部的函数,他属于外层函数值(values)的一部分。

def out():  # 外层函数
    out1 = 1  # 定义一个外层变量
    print(out1, 'and id is', id(out1))  # 打印外层变量的信息

    def inn():  # 定义内部函数
        print(out1, 'and id is', id(out1))  # 内部函数用了外部变量(只是使用,并没有修改),看看信息有无变化

    inn()  # 执行内部函数


out()  # 执行外部函数
# 1 and id is 140725638888512
# 1 and id is 140725638888512

        一般在什么情况下使用嵌套函数?

        1 ,封装 - 数据隐藏外部无法访问“嵌套函数”。

        2 ,贯彻 DRY(Don’t Repeat Yourself) 原则。

        3 ,嵌套函数,可以让我们在函数内部避免重复代码。

        4 ,闭包。

        当我们某些函数功能类似,只是根据某些参数类型不同而选择不同的函数时,可以把这些功能类似的函数写在嵌套函数里。

2,nonlocal关键字


def out():  # 外层函数
    out1 = 1  # 定义一个外层变量
    print(out1, 'and id is', id(out1))  # 打印外层变量的信息

    def inn():  # 定义内部函数
        out1 = 520
        print(out1, 'and id is', id(out1))  # 内部函数修改了外部变量,看看信息有无变化

    print(out1, 'and id is', id(out1))  # 打印外层变量的信息
    inn()  # 执行内部函数


out()  # 执行外部函数
# 1 and id is 140725638888512
# 1 and id is 140725638888512
# 520 and id is 1511753801328

        内部函数对外部函数的变量进行了修改,可以发现对象已经改变,但是外部变量并没发生变化。因为在内部函数对外部函数变量进行修改时,会创建一个新对象来把外部变量隐藏。

        那么如何才能使内部函数也能修改外部变量呢?

        可以使用nonlocal关键字来声明,必须在内部函数里声明,且变量名要与外部变量相同。

def out():  # 外层函数
    out1 = 520  # 定义一个外层变量
    print(out1, 'and id is', id(out1))  # 打印外层变量的信息

    def inn():  # 定义内部函数
        nonlocal out1  # 声明为非本地变量
        out1 = 520  # 内部函数修改了外部变量,
        print(out1, 'and id is', id(out1))  # 看看信息有无变化

    inn()  # 先执行内部函数
    print(out1, 'and id is', id(out1))  # 再打印外层变量的信息



out()  # 执行外部函数
# 520 and id is 2060121235056
# 520 and id is 2060121235248
# 520 and id is 2060121235248

九,LEGB原则

        legb原则指的是python解释器查找变量名时的顺序。

        L:local,先在定义的函数的函数体里找

        E:enclosed,嵌套函数的闭包起来的变量,其实就是外层函数的变量。

        G:global,全部变量

        B:built,内置预定的变量

        假如没有嵌套函数,则只有EGB原则,因为没有本地变量(local varible);

        假如有嵌套函数,就是LEGB原则。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐