Python函数以及参数类型&变量作用域

什么是函数?函数(function)是用于完成特定任务的程序代码的自包含单元。在面向对象编程的类中,函数通常被称作方法。

  • 为什么要使用函数呢?

第一、函数的使用可以重用代码,省去重复性代码的编写,提高代码的重复利用率。如果程序中需要多次使用某种特定的功能,那么只需要编写一个合适的函数就可以了。程序可以在任何需要的地方调用该函数,并且同一个函数可以在不同的程序中调用,就像我们经常使用的print()和input()函数一样。

第二、函数能封装内部实现,保护内部数据,实现对用户的透明。很多时候,我们把函数看做“黑盒子”,即对应一定的输入会产生特定的结果或返回某个对象。往往函数的使用者并不是函数的编写者,函数的使用者对黑盒子的内部行为并不需要考虑,可以把精力投入到自身业务逻辑的设计而不是函数的实现细节。只有函数的设计者或者说编写者,才需要考虑函数内部实现的细节,如何暴露对外的接口,返回什么样的数据,也就是API的设计

  • python中函数的定义
def 函数名(参数):
    # 内部代码
    return 表达式 
在定义函数的过程中,需要注意以下几点:
1、函数代码块以def关键词开头,一个空格之后接函数标识符名称和圆括号(),再接个冒号。
2、任何传入的参数必须放在圆括号中间。
3、函数的第一行语句后可以选择性地使用文档字符串—用于存放函数说明。
4、函数内容以冒号起始,并且缩进。
5、使用return结束函数。默认返回None。
6、return语句依然在函数体内部,不能回退缩进。直到函数的所有代码写完,才回退缩进,表示函数体结束。
  • 函数调用
函数编写出来就是给人调用的。要调用一个函数,必须使用函数名后跟圆括号的方式才能调用函数。调用的同时要根据函数的定义体,提供相应个数和类型的参数,每个参数之间用逗号分隔。Python由于动态语言的特点,在做语法和词法分析检查的时候,并不会对参数类型进行检查,但在执行过程中,如果参数类型不符合函数内部运行机制的话,会弹出相应的错误
  • return语句
return语句用于表示函数执行到此结束,并且返回return后面的对象。有时候,函数不需要返回任何值,此时可以不需要return语句,它在后台默认给你返回个None,并且不给任何提示。但是更多的时候我们还是需要return一些东西。一旦函数执行过程遇到return语句,那么之后函数体内的所有代码都会被忽略,直接跳出函数体
  • return语句返回
1、不写return语句,它在后台默认给你返回个None,并且不给任何提示
2、什么都不返回,仅仅return,相当于不写return语句,
3、数字/字符串/任意数据类型: return 'hello'
4、一个表达式:return 1+2
5、一个判断语句:return 100 > 99
6、一个变量:return a
7、一个函数调用:return func()
8、甚至是返回自己!:return self

多个返回值,以逗号分隔:return a, 1+2, "hello"
  • 如何接受函数的返回值
我们在调用函数的时候,可以将函数的返回值保存在变量中。
def func():
    pass
    return "something"

result = func()

而对于同时返回多个值的函数,需要相应个数的变量来接收,变量之间以逗号分隔:
def func():
    return 1, [2, 3], "haha"

a, b, c = func()
  • 参数类型
x, y, z = 1, 2, 3
def add(a, b, c):

    return a+b+c
add(x, y, x)        # 使用变量,传递参数

a,b,c叫做形式参数,简称形参。而x,y,z和4,5,6叫做实际参数,简称实参,也就是实际要传递的值。而我们通常讨论的参数,指的都是形参。
python参数类型有:位置参数,还可以使用默认参数、动态参数和关键字参数,这些都是形参的种类。
  • 位置参数

也叫必传参数,顺序参数,是最重要的,也是必须在调用函数时明确提供的参数!位置参数必须按先后顺序,一一对应,个数不多不少的传递!

注意: Python在做函数参数传递的时候不会对数据类型进行检查,理论上你传什么类型都可以

def add(a, b, c):
    return a+b+c

result = add(1, 2,  3)
也叫必传参数,顺序参数,是最重要的,也是必须在调用函数时明确提供的参数!位置参数必须按先后顺序,一一对应,个数不多不少的传递
注意: Python在做函数参数传递的时候不会对数据类型进行检查,理论上你传什么类型都可以,但在执行过程中,如果参数类型不符合函数内部运行机制的话,会弹出相应的错误
但是 result = add('ab',2,3) 如果你传递了一个字符串和两个数字,结果是弹出异常,因为字符串无法和数字相加。这就是Python的弱数据类型和动态语言的特点
  • 默认值参数

在函数定义时,如果给某个参数提供一个默认值,这个参数就变成了默认参数,不再是位置参数了。在调用函数的时候,我们可以给默认参数传递一个自定义的值,也可以使用默认值

def power(x, n = 2):
    return x**n

ret1 = power(10)   # 使用默认的参数值n=2,实际计算 10**2的值
ret2 = power(10, 4)  # 将4传给n,实际计算10**4的值

使用默认值参数的注意事项:
1、默认参数必须在位置参数后面!
# 这是一个错误的例子
def power(n = 2,x):
    return x**n
2、当有多个默认参数的时候,通常将更常用的放在前面,变化较少的放后面。
def student(name, sex, age, classroom="101", tel="88880000", address="..."):
    pass
3、在调用函数的时候,尽量给实际参数提供默认参数名。
def student(name, sex, age, classroom="101", tel="88880000", address="..."):
    pass

student('jack','male',17)       # 其它全部使用默认值
student('tom','male',18,'102','666666','beijing')    # 全部指定默认参数的值
student('mary','female',18,'102',tel='666666')  # 挑着来
student('mary','female',18,tel='666666','beijing')   #  这是错误的参数传递方式
student("mary","female",18,tel="666666",address="beijing")
因为一切没有提供参数名的实际参数,都会当做位置参数按顺序从参数列表的左边开头往右匹配!
4、使用参数名传递参数
通常我们在调用函数时,位置参数都是按顺序先后传入,而且必须在默认参数前面。但如果在位置参数传递时,给实参指定位置参数的参数名,那么位置参数也可以不按顺序调用,例如:
def student(name, age, classroom, tel, address="..."):
    pass

student(classroom=101, name="Jack", tel=66666666, age=20)
注意指定的参数名必须和位置参数的名字一样。


  • 动态参数
顾名思义,动态参数就是传入的参数的个数是动态的,可以是1个、2个到任意个,还可以是0个。在不需要的时候,你完全可以忽略动态函数,不用给它传递任何值
Python的动态参数有两种,分别是*args和**kwargs,这里面的关键是一个和两个星号的区别,而不是args和kwargs在名字上的区别,实际上你可以使用*any或**whatever的方式。但就如self一样,默认大家都使用*args和**kwargs。

注意:动态参数,必须放在所有的位置参数和默认参数后面
def func(name, age, sex='male', *args, **kwargs):
    pass

*args

一个星号表示接收任意个数的参数。调用时,会将实际参数打包成一个元组传入形式参数。如果参数是个列表,会将整个列表当做一个参数传入。

如果我们想把列表中的每一个元素分别当作参数传给函数时,传参时在列表前面加一个*,就可以达到目的,任何序列类型数据对象,比如字符串、元组都可以通过这种方式将内部元素逐一作为参数,传递给函数。而字典,则会将所有的key逐一传递进去

Python函数以及参数类型&变量作用域_第1张图片

**args

两个星表示接受键值对的动态参数,数量任意。调用的时候会将实际参数打包成字典,如果我们希望们字典内的键值对能够像上面一样被逐一传入。使用两个星号**,两个星号能将字典内部的键值对逐一传入**kwargs

Python函数以及参数类型&变量作用域_第2张图片

  • 万能参数,

*args**kwargs组合起来使用,理论上能接受任何形式和任意数量的参数,在很多代码中我们都能见到这种定义方式。需要注意的是,*args必须出现在**kwargs之前。

加* 和 不加*的对比

def func(*args, **kwargs):

    for arg in args:
        print(type(arg))
        print(arg)

    for kwg in kwargs:
        print(type(kwg))
        print(kwg, kwargs[kwg])
lis = [1, 2, 3]
dic = {
    'k1': 'v1',
    'k2': 'v2'
}
func(lis, dic)
>>>(不加*)

[1, 2, 3]

{'k1': 'v1', 'k2': 'v2'}

func(*lis,**dic)
>>>(加*)

1

2

3

k1 v1

k2 v2

Python函数以及参数类型&变量作用域_第3张图片

  • 变量作用域

作用域指的是变量的有效范围。变量并不是在哪个位置都可以访问的,访问权限取决于这个变量是在哪里赋值的,也就是在哪个作用域内的。

函数就是内部代码可以访问外部变量,而外部代码通常无法访问内部变量

变量的作用域决定了程序的哪一部分可以访问哪个特定的变量名称。Python的作用域一共有4层,分别是:

  • L (Local) 局部作用域
  • E (Enclosing) 闭包函数外的函数中
  • G (Global) 全局作用域
  • B (Built-in) 内建作用域
x = int(2.9)  # 内建作用域,查找int函数

global_var = 0  # 全局作用域
def outer():
    out_var = 1  # 闭包函数外的函数中
    def inner():
        inner_var = 2  # 局部作用域

如果出现本身作用域没有定义的变量,那该如何寻找呢?

Python以L –> E –> G –>B的规则查找变量,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,最后去内建中找。如果这样还找不到,那就提示变量不存在的错误。
  • 局部变量和全局变量

定义在函数内部的变量拥有一个局部作用域,被叫做局部变量,定义在函数外的拥有全局作用域的变量,被称为全局变量

所谓的局部变量是相对的。局部变量也有可能是更小范围内的变量的外部变量。

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。

 

a = 1               # 全局变量

def func():
    b = 2           # 局部变量
    print(a)        # 可访问全局变量a,无法访问它内部的c

    def inner():
        c = 3       # 更局部的变量
        print(a)    # 可以访问全局变量a
        print(b)    # b对于inner函数来说,就是外部变量
        print(c)
  • global 和nolocal 关键字
  • global:指定当前变量使用外部的全局变量
total = 0                        # total是一个全局变量

def plus( arg1, arg2 ):
    total = arg1 + arg2          # total在这里是局部变量.
    print("函数内局部变量total=  ", total)
    print("函数内的total的内存地址是: ", id(total))
    return total

plus(10, 20)
print("函数外部全局变量total= ", total)
print("函数外的total的内存地址是: ", id(total))

>>>
函数内局部变量total=   30
函数内的total的内存地址是:  4540117584
函数外部全局变量total=  0
函数外的total的内存地址是:  4540116624

很明显,函数plus内部通过total = arg1 + arg2语句,新建了一个局部变量total,它和外面的全局变量total是两码事。而如果我们,想要在函数内部修改外面的全局变量total呢?使用global关键字!
global:指定当前变量使用外部的全局变量

total = 0                        # total是一个全局变量

def plus( arg1, arg2 ):
    global total    # 使用global关键字申明此处的total引用外部的total
    total = arg1 + arg2          
    print("函数内局部变量total=  ", total)
    print("函数内的total的内存地址是: ", id(total))
    return total

plus(10, 20)
print("函数外部全局变量total= ", total)
print("函数外的total的内存地址是: ", id(total))

>>>
函数内局部变量total=   30
函数内的total的内存地址是:  503494624
函数外部全局变量total=  30
函数外的total的内存地址是:  503494624
  • nolocal  关键字!它可以修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量。
a = 1
print("函数outer调用之前全局变量a的内存地址A: ", id(a),a)
def outer():
    a = 2
    print("函数outer调用之时闭包外部的变量a的内存地址B: ", id(a),a)
    def inner():
        global a   # 注意这行
        a = 3
        print("函数inner调用之后闭包内部变量a的内存地址C: ", id(a),a)
    inner()
    print("函数inner调用之后,闭包外部的变量a的内存地址D: ", id(a),a)
outer()
print("函数outer执行完毕,全局变量a的内存地址E: ", id(a),a)

>>>
函数outer调用之前全局变量a的内存地址A:  4494626480 1
函数outer调用之时闭包外部的变量a的内存地址B:  4494626512 2
函数inner调用之后闭包内部变量a的内存地址C:  4494626544 3
函数inner调用之后,闭包外部的变量a的内存地址D:  4494626512 2
函数outer执行完毕,全局变量a的内存地址E:  4494626544 3
---------------------------------------------
a = 1
print("函数outer调用之前全局变量a的内存地址A: ", id(a),a)
def outer():
    a = 2
    print("函数outer调用之时闭包外部的变量a的内存地址B: ", id(a),a)
    def inner():
        nonlocal a   # 注意这行
        a = 3
        print("函数inner调用之后闭包内部变量a的内存地址C: ", id(a),a)
    inner()
    print("函数inner调用之后,闭包外部的变量a的内存地址D: ", id(a),a)
outer()
print("函数outer执行完毕,全局变量a的内存地址E: ", id(a),a)

>>>
函数outer调用之前全局变量a的内存地址A:  4525268656 1
函数outer调用之时闭包外部的变量a的内存地址B:  4525268688 2
函数inner调用之后闭包内部变量a的内存地址C:  4525268720 3
函数inner调用之后,闭包外部的变量a的内存地址D:  4525268720 3
函数outer执行完毕,全局变量a的内存地址E:  4525268656 1

Python函数以及参数类型&变量作用域_第4张图片

Python中,在函数中为一个变量赋值时,当在函数中给一个变量名赋值是,Python总是创建或改变本地作用域的变量名,除非它已经在那个函数中被声明为全局变量.

若想要在函数中修改全局变量x,而不是在函数中新建一个变量,此时便要用到关键字global

nonlocal关键字可以在一个嵌套的函数中修改嵌套作用域中的变量,会修改 1 处的count值

  • 函数作用域实例

Python函数的作用域取决于其函数代码块在整体代码中的位置,而不是调用时机的位置。调用f1的时候,会去f1函数的定义体查找,对于f1函数,它的外部是name ='jack',而不是name = 'eric'

Python函数以及参数类型&变量作用域_第5张图片

Python函数以及参数类型&变量作用域_第6张图片

  • 请说出下面代码的运行结果
a = 10
def test():
    a += 1
    print(a)
test()

我会告诉你,这段代码有语法错误吗?a += 1相当于a = a + 1,按照赋值运算符的规则是先计算右边的a+1。但是,Python的规则是,如果在函数内部要修改一个变量,那么这个变量需要是内部变量,除非你用global声明了它是外部变量。很明显,我们没有在函数内部定义变量a,所以会弹出局部变量在未定义之前就引用的错误。

a = 10
def test():
    global a
    a += 1
    print(a)
test()

>>>
11

 

你可能感兴趣的:(python3)