zoukankan      html  css  js  c++  java
  • python学习:函数---基础知识

    一、函数的基本语法

    1、函数的定义:

    def 函数名(参数1, 参数2, 参数3, ...):
        函数体  # 函数的代码
        return 返回值  # 也可以没有返回值

    说明:

    • 函数名只能包含字母、数字或下划线,不能以数字开头。
    • 函数名可以随便取,但是要尽量短,并且要具有描述性,尽量做到“望文生义”。
    • 在其他地方调用函数时,只需要通过 函数名() 的方式就可以调用。不管有没有参数,都必须有括号。
    • 函数只定义了如果一直不调用,就一定不会执行。
    • 函数必须先定义后使用。

    二、参数

    1、形参和实参

    形参:形式参数,是在函数定义的时候,写在函数名后面的括号里面的参数。

    实参:实际参数,是在函数调用的时候,传给函数的值;也就是在函数调用的时候,写在函数名后面括号里面的值。实参可以是常量、变量、表达式、函数等。

    形参和实参的区别:

    • 形参是虚拟的,不占用内存空间,只有在函数被调用时才分配内存单元。实参是一个具体的变量,占用内存空间。
    • 只能把实参传给形参,不能将形参传给实参。
    • 形参和实参必须一一对应。

    2、形参的角度:

    位置参数:必须传值,有几个参数就必须传几个值

    默认参数:可以不传的参数,如果不传就使用默认的参数值,如果传了就使用传过来的参数的值。

    动态参数:动态接收位置参数用*args,可以接收任意多个按位置传入的参数,接收后组织成一个元组;

                      动态接收关键字参数用**kwargs,可以接收任意多个按关键字传入的参数,接收后组织成一个字典。

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

    3、实参的角度:

    按照位置传参:从左至右,一一对应的传给形参。

    按照关键字传参:不用考虑参数的位置顺序,形参按照关键字来接收。

    可以将位置和关键字混着传参:位置传参和关键字参数混着用。

    注意顺序:必须将关键字参数放在位置参数之后,且不能给一个参数传多个值。

    def my_sum(a, b):
        print('a = %s, b = %s'%(a, b))
        return a + b
    
    # 按照位置传参
    print("------按位置传参------")
    ret = my_sum(3, 2)
    print(ret)
    
    # 按照关键字传参
    print("------按关键字传参------")
    ret = my_sum(b = 2, a = 3)
    print(ret)
    
    # 位置和关键字混合传参
    print("------混合传参------")
    ret = my_sum(3, b = 2)
    print(ret)
    
    #运行结果:
    ------按位置传参------
    a = 3, b = 2
    5
    ------按关键字传参------
    a = 3, b = 2
    5
    ------混合传参------
    a = 3, b = 2
    5
    
    # 几种种错误的传参方式
    ret = my_sum(3)  # 错误,函数需要2个参数,实参只有一个
    ret = my_sum(3, a = 2) # 错误,参数a被重复赋值了,第一个位置参数将3传给a,第二个关键字参数又将2传给了a
    ret = my_sum(a = 3, 2) # 错误,位置参数必须放在关键字参数的前面

    4、默认参数的陷阱

    如果默认参数是一个可变数据类型,每次在调用函数的时候,如果不给默认参数传值,则共用这个可变数据类型的资源

    # 参数默认列表,不传值的时候,共享了这个列表;传值后就不再共享
    def func(l = []):
        l.append(100)
        print(l)
    
    func()        # [100]
    func()        # [100, 100]
    func([])      # [100]
    func(l = [])  # [100]
    func()        # [100, 100, 100]
    
    # 参数默认字典,不传值的时候,共享了这个字典;传值后就不再共享
    def func(k, l = {}):
    #     l[k] = 'v'
    #     print(l)
    #
    # func(1)         # {1: 'v'}
    # func(2)         # {1: 'v', 2: 'v'}
    # func(3,l = {})  # {3: 'v'}
    # func(4,l = {})  # {4: 'v'}
    # func(5)         # {1: 'v', 2: 'v', 5: 'v'}
    
    # 但是下面这个情况不是默认参数的陷阱了。
    # 因为默认参数是一个字典,字典的key不允许重新,导致后面一次调用直接修改了之前的value
    def func(l = {}):
        l['k'] = 'v'
        print(l)
    
    func() # {'k': 'v'}
    func() # {'k': 'v'}
    func() # {'k': 'v'}
    func() # {'k': 'v'}

    5、打散和聚合

    def func1(*args):
        print(args)
    
    # 方式一:直接按照位置将参数一个一个传入,*args返回了一个元组,这个就叫聚合
    func1(1,2,3,4,5)  # (1, 2, 3, 4, 5)
    
    l = [1,2,3,4,5]
    # 方式二、将列表l直接按照位置参数传入,*args返回了一个元组,这也是聚合,但是这个元组的元素却是一个列表,不是方式一的结果了
    func1(l)
    
    # 方式三,在l前面加删个一个*传入,*args返回的结果和方式一完全一样。通过print(*l)的结果我们看到,此时不再是将列表做为参数传入了,而是将列表的元素打开一个一个传入的,这就是打散。
    print(*l)
    func1(*l)
    
    # 运行结果:
    (1, 2, 3, 4, 5)
    ([1, 2, 3, 4, 5],)
    1 2 3 4 5
    (1, 2, 3, 4, 5)
    
    # =======继续**kwargs========
    dic1 = {'name': '张三', 'age': 18}
    dic2 = {'hobby': '王五', 'sex': ''}
    def func(**kwargs):
        print(kwargs)     # {'name': '张三', 'age': 18, 'hobby': '王五', 'sex': '男'}
    
    func(arg1 = dic1, arg2 = dic2)  # {'arg1': {'name': '张三', 'age': 18}, 'arg2': {'hobby': '王五', 'sex': '男'}}
    func(**dic1, **dic2)  # {'name': '张三', 'age': 18, 'hobby': '王五', 'sex': '男'}
    
    #=====处理剩下的元素======
    # 把1和2分别赋值给a和b
    a,b = (1,2)
    print(a, b) # 1 2
    
    # 把1赋值给a,上下的元素聚合成一个列表赋值给b 
    a,*b = (1, 2, 3, 4,)
    print(a, b) # 1 [2, 3, 4]
    
    # 最后两个元素分别赋值给a和b,前面的元素聚合成一个列表赋值给rest
    *rest,a,b = range(5)
    print(rest, a, b) # [0, 1, 2] 3 4
    
    # 将列表打散
    print([1, 2, *[3, 4, 5]]) # [1, 2, 3, 4, 5]

    三、返回值

    使用return关键字将函数执行的结果返回给调用它的地方,在调用它的地方用一个变量接收。当然函数也可以不用return返回值,不写return的时候,调用方接收到的是None。

    说明:

    • 在函数体内,遇到return函数就结束了,return下面的代码不会执行。
    • 如果return后面什么都不写,或者函数体内没有return,则返回的结果是None。
    • 如果return后面写了一个值,则就返回给调用者这个值。可以返回任何值。
    • 如果return后面写了多个值,调用者可以对应的用多个值去接收,return返回了几个值就必须用几个变量去接收;调用者也可以使用一个值来接收这个返回值,此时这个值是一个元组。
     1 def func1():
     2     return 1
     3 
     4 def func2():
     5     return 1, 2, 3
     6 
     7 r = func1()
     8 print('func1 :', r)  # func1 : 1
     9 r = func2()
    10 print('func2 :', r, 'type :', type(r))  # func2 : (1, 2, 3) type : <class 'tuple'>
    11 r1, r2, r3 = func2()
    12 print('func2 :', r1, r2, r3)  # func2 : 1 2 3

    四、命名空间和作用域

    1、命名空间

    • 内置命名空间:python解释器已启动就可以使用的名字存在内置命名空间中。
    • 全局命名空间:程序从上导下被执行的过程中一次加载进内存的变量名和函数名。
    • 局部命名空间:函数内部定义的名字,只有在函数被调用的时候才会产生这个空间。函数执行完毕了,这个命名空间也就随之消失了。
    • 在局部空间,可以使用全局和内置命名空间中的名字。
    • 在全局空间,可以使用内置命令空间的名字,但是不能使用局部空间中的名字。
    • 在内置空间,不能使用局部和全局命名空间中的名字。
    • 如果我们定义了一个函数名和某个内置函数的函数名相同时,就会直接调用我们自己定义的函数,而不会再调用内置函数了

    • 命名空间的加载顺序:内置命名空间(程序运行伊始加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)。
    • 命名空间的取值顺序:取值顺序与加载顺序是相反的,取值顺序满足的就近原则,从小范围到大范围一层一层的逐步引用。

    2、作用域

    全局作用域:全局命名空间和内置命名空间,在整个文件中都可以用。

    局部作用域:在函数内部使用。

    a = 100
    def func():
        a += 10  # 有异常抛出
        print(a)
    
    func()
    # 上面的程序会抛出异常:UnboundLocalError: local variable 'a' referenced before assignment
    # 异常原因:对于不可变数据类型,在函数里面是不能直接修改全局变量的,但是可以访问全局作用域的变量
    # 如果要在函数内部修改全局变量,可以在函数里面加上一个global,这样在函数里面的所有修改都对全局变量有效。不推荐在程序中使用global来修改全局变量
    a = 100
    def func():
        a = 200
        print(a) # 200
    
    func()
    print(a) # 100
    
    # 使用global后的效果
    a = 100
    def func():
        global a
        a += 10
        print(a) # 110
    
    func()
    print(a) # 110

    3、globals()和locals()

    a = 100
    b = 200
    def func():
        c = 300
        print("In func lcoals() :",locals())
        print("In func globas() :",globals())
    
    func()
    print("Out func lcoals() :",locals())
    print("Out func globas() :",globals())
    # 结论:
      # globals()永远会打印全局作用域的名字
      # locals()放在函数里面打印的是局部作用域的名字,放在函数外面打印的是全局作用域的名字

    五、函数的嵌套

    # 首先看一个函数嵌套的例子:
    def max(a,b):
        return a if a > b else b
    
    def three_max(x,y,z):
        c = max(x,y)
        return max(c,z)
    
    res = three_max(5,8,3)
    print(res) # 8
    
    # 下面这个函数执行的结果是什么?
    def outer():
        def inner():
            print("inner...")
    
    outer()
    # 结论:
      # 没有输出任何结果,这是因为调用了outer函数后,只定义了inner函数,并没有调用innder函数
    
    # 下面的函数就有输出结果
    def outer():
        def inner():
            print("inner...")
        inner()
    
    outer() # inner...
    # 输出结果:inner...
    # 上面这个函数的执行过程如下:

    补充一个叫nonlocal的东西:

    • 声明为nonlocal的局部变量被修改后,只会影响到离这个函数最近的上层函数的相同的局部变量,不会对全局变量有影响
    • nonlocal只能用于局部变量
    • 但是声明为global的变量被修改后,不管global在什么地方都会直接影响到全局变量

    实例一、在最内层函数中定义变量a并修改。从运行结果可以看出,此时只有最内层的变量a被修改了

    a = 100
    def outer():
        a = 100
        def inner1():
            a = 100
            def inner2():
                a = 100  # 这句被注释后会抛出异常:UnboundLocalError: local variable 'a' referenced before assignment
                a += 10
                print("inner2.a = ",a)
            inner2()
            print("inner1.a = ", a)
        inner1()
        print("outer.a = ", a)
    outer()
    print("全局变量 a = ",a)
    
    # 运行结果:
    inner2.a =  110
    inner1.a =  100
    outer.a =  100
    全局变量 a =  100

    实例二、将最内层的变量a定义为global,从运行结果可以看出,此时在最内层对a进行修改,影响的却是全局的a

    a = 100
    def outer():
        a = 100
        def inner1():
            a = 100
            def inner2():
                global a
                a += 10
                print("inner2.a = ",a)
            inner2()
            print("inner1.a = ", a)
        inner1()
        print("outer.a = ", a)
    outer()
    print("全局变量 a = ",a)
    
    # 运行结果:
    inner2.a =  110
    inner1.a =  100
    outer.a =  100
    全局变量 a =  110

    实例三、将最内存的变量a定义为nonlocal。从运行结果可以看出,a被修改后受到影响的是inner1中定义的变量a

    a = 100
    def outer():
        a = 100
        def inner1():
            a = 100
            def inner2():
                nonlocal a
                a += 10
                print("inner2.a = ",a)
            inner2()
            print("inner1.a = ", a)
        inner1()
        print("outer.a = ", a)
    outer()
    print("全局变量 a = ",a)
    
    # 运行结果:
    inner2.a =  110
    inner1.a =  110
    outer.a =  100
    全局变量 a =  100

    实例四、去掉inner1中定义的变量a,在inner2中修改a后,影响到了outer的变量a,因为此时outer中的a离inner2最近,且inner2是局部

    a = 100
    def outer():
        a = 100
        def inner1():
            def inner2():
                nonlocal a
                a += 10
                print("inner2.a = ",a)
            inner2()
            print("inner1.a = ", a)
        inner1()
        print("outer.a = ", a)
    outer()
    print("全局变量 a = ",a)
    
    # 运行结果:
    inner2.a =  110
    inner1.a =  110
    outer.a =  110
    全局变量 a =  100
  • 相关阅读:
    不停机还能替换代码?6年的 Java程序员表示不可思议
    redis 分布式锁的 5个坑,真是又大又深
    一口气说出 4种 LBS “附近的人” 实现方式,面试官笑了
    真没想到,Springboot能这样做全局日期格式化,有点香!
    springboot + aop + Lua分布式限流的最佳实践
    不可思议的hexo,五分钟教你免费搭一个高逼格技术博客
    Redis开发运维的陷阱及避坑指南
    Jar包一键重启的Shell脚本及新服务器部署的一些经验
    与Redis的初次相识,Redis安装、启动与配置
    SpringBoot项目中应用Jedis和一些常见配置
  • 原文地址:https://www.cnblogs.com/Ryan-Fei/p/12103746.html
Copyright © 2011-2022 走看看