添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

函数的定义和调用

在 Python 中定义函数使用 def 关键字,一般格式如下:

1
2
3
4
5
6
7
def 函数名(参数列表):
函数体

def hello():
print('hello')

hello()#调用

函数名的命名规则,与变量名类似:

  • 函数名区分大小写。

  • 函数名只能是 字母、数字 或 下划线 的任意组合,不能使用任何的标点符号。

  • 函数名不能以数字开头。

  • 函数名不能与 Python 的关键字同名。

    函数的参数

    任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。

    形参和实参

    形参指的是形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应)。

    实参是指实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参。

    形参和实参的区别是,形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import time

    times = time.strftime('%Y-%m-%d')


    def f(time_str):
    print('Now time is : %s' % time_str)


    f(times)

    传入函数的参数

    位置参数

    位置参数也叫必备参数,是必须要传给函数的参数,并且在传参时必须以正确的顺序传入函数,调用时的数量必须和函数声明时的一样。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def f(name, age):
    print('I am %s,I am %d years old.' % (name, age))


    f('Tom', 18)
    f('Jerry', 16)

    '''
    I am Tom,I am 18 years old.
    I am Jerry,I am 16 years old.
    '''

    关键字参数

    关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def f(name, age):
    print('I am %s, I am %d years old.' % (name, age))


    # f(16,'Tom') #报错
    f(age=16, name='Tom')

    '''
    I am Tom, I am 16 years old.
    '''

    定义函数时的参数

    默认参数

    默认参数也叫缺省参数,同时也是关键字参数,调用函数时缺省参数的值如果没有传入,则被认为是默认值。默认参数常用于多数情况下对象的值是固定的情况,例如一些女子学校中,学生的性别是固定的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def echo_info(name, age, sex='male'):
    print('-'.center(15, '-'))
    print('Name:%s' % name)
    print('Age:%d' % age)
    print('Sex:%s' % sex)


    echo_info('Tom', 18)
    echo_info('Jerry', 40, 'female')

    默认参数的陷阱: 在定义函数时,如果默认参数的值是可变数据类型,那么每一次调用函数的时候如果不传入值,就会公用这个数据类型的资源,即每次都操作的是同一个对象。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    def test(names=[]):
    names.append('Tom')
    print(names)

    test()
    test()
    test()
    test()

    输出结果:

    1
    2
    3
    4
    ['Tom']
    ['Tom', 'Tom']
    ['Tom', 'Tom', 'Tom']
    ['Tom', 'Tom', 'Tom', 'Tom']
    1
    2
    3
    4
    5
    6
    7
    8
    def atest(k, data={}):
    data[k] = 'v'
    print(data)


    atest(1)
    atest(2)
    atest(3)

    输出结果:

    1
    2
    3
    {1: 'v'}
    {1: 'v', 2: 'v'}
    {1: 'v', 2: 'v', 3: 'v'}

    不定长参数

    有时候可能需要一个函数能处理比当初声明时更多的参数,这些参数就叫不定长参数,也叫动态参数。动态参数和位置参数、关键字参数不同,声明时不会命名。例如下面的加法器,能且仅能接收三个参数,并求出和:

    1
    2
    3
    4
    5
    def add(x, y, z):
    print(x + y + z)


    add(1, 2, 3)

    如果在变量名前面加了星号 * ,则变量会以元组的形式存放所有 未命名 的变量参数,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def add(*args):
    print('args =', args)
    the_sum = 0
    for i in args:
    the_sum += i
    print('the_sum =', the_sum)


    add(1, 2, 3, 4, 6, 9, 10)

    '''
    args = (1, 2, 3, 4, 6, 9, 10)
    the_sum = 35
    '''

    下面的打印基本信息的函数,最多只能接收三个参数,如果再想添加其他参数就需要重新定义函数的参数:

    1
    2
    3
    4
    5
    6
    7
    def echo_info(name, age, sex='male'):
    print('Name : %s' % name)
    print('Age : %d' % age)
    print('Sex : %s' % sex)


    echo_info('Tom', 18)

    而如果在变量名前面加了 ** , 则变量会以字典的形式存放 有命名 的变量参数,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def echo_info(*args, **kwargs):
    print('args = ', args)
    print('kwargs = ', kwargs)
    for i in kwargs:
    print('%s : %s' % (i, kwargs[i]))


    echo_info('Tom', 18, 'male', Job='IT', Height='188')

    '''
    args = ('Tom', 18, 'male')
    kwargs = {'Job': 'IT', 'Height': '188'}
    Job : IT
    Height : 188
    '''

    参数的位置关系

    定义函数时应该 *args 放左边, **kwargs 放右边,下面是错误的写法:

    1
    def print_info(name, **kwargs, *args): # 报错

    按照规范,定义函数时默认参数应该放在位置参数之后。

    1
    2
    3
    4
    5
    def test(name, age, *args, sex='fmale', **kwargs):
    def test(*args, sex='fmale'):
    # 不规范的写法:
    # def test(name, age, sex='fmale', *args, **kwargs):
    # def test(sex='fmale', *args):

    调用函数进行传参时,关键字参数应该放在位置(必备)参数之后。

    1
    2
    3
    echo_info('Tom', 18, hobby='girl', nationality='Chinese', ability='Python')
    # echo_info(hobby='girl', 'alex', 18, nationality='Chinese', ability='Python') # 报错
    # echo_info('Jerry', hobby='girl', 18, nationality='Chinese', ability='Python') # 报错

    其他传参方式

    函数在接收动态参数时,会把接收的参数聚合成一个元组,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def func(*args):
    print('args =', args)


    func(1, 2, 3)
    func(['a', 'b', 'c'])

    '''
    args = (1, 2, 3)
    args = (['a', 'b', 'c'],)
    '''

    而在函数中使用 *args 的方式调用参数时,会把聚合的元组打散,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    def func(*args):
    print('args =', args)
    print('*args =', *args)


    func(1, 2, 3)

    '''
    args = (1, 2, 3)
    *args = 1 2 3
    '''

    func(['a', 'b', 'c'])

    '''
    args = (['a', 'b', 'c'],)
    *args = ['a', 'b', 'c']
    '''

    这种方式其实又还原成了最原始传入参数的方式,这种情况最常用的情形是在装饰器内:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def wrapper(funcname):
    def inner(*args, **kwargs): # 函数接收参数,聚合成一个元组
    retval = funcname(*args, **kwargs) # 调用参数,打散元组,还原初始传入形式
    print('Something')
    return retval

    return inner


    @wrapper
    def func(s):
    print(s)


    func([1, 2, 3])

    如果传给函数的参数是个列表或元组,还可以使用 * 将其打散之后传入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def func(*args):
    print('args =', args)


    func([1, 2, 3])

    func(*[1, 2, 3]) # 将列表打散: *[1, 2, 3] < == > 1, 2, 3
    func(1, 2, 3)

    '''
    args = ([1, 2, 3],)
    args = (1, 2, 3)
    args = (1, 2, 3)
    '''

    如果函数接收的是 **kwargs ,那么单纯地传入字典会被识别为位置参数,必须打散才能被接收。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def func(**kargs):
    print('kwargs =', kargs)


    # func({'Name': 'Jerry'}) # 错误传参

    func(**{'name': 'Tom'})

    '''
    kwargs = {'name': 'Tom'}
    '''

    函数的返回值

    要想获取函数的执行结果,就可以用 return 语句把结果返回。

  • 函数在执行过程中只要遇到 return 语句,就会停止执行并返回结果,也可以理解为 return 语句代表着函数的结束。

  • 如果未在函数中指定 return ,那这个函数的返回值为 None

  • return 可以接收多个对象,此时 Python 会将多个对象封装成一个元组,并将这个元组对象返回。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-


    def get_os_release(fpath='/etc/redhat-release'):
    with open(fpath, 'rt', encoding='utf-8') as fr:
    return fr.readline().strip()


    x = get_os_release()
    y = get_os_release('/etc/issue')

    print(x)
    print(y)

    '''
    CentOS Linux release 7.6.1810 (Core)
    \S
    '''

    函数的注释

    定义了函数后,应该对整个函数作注释而不是逐行注释。以下是建议的注释方式:

    1
    2
    3
    4
    5
    6
    7
    def func(name,sex):
    '''
    函数的作用
    参数1:数据类型,用途
    参数2:数据类型,用途
    '''
    pass

    命名空间

    从 python 解释器开始执行代码之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间的对应关系记录下来。

    但是 当遇到函数定义的时候解释器只是象征性的将函数名读入内存 ,表示知道这个函数的存在了,至于函数内部的变量和逻辑解释器根本不关心。

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

    命名空间是指存放名字与值的绑定关系的空间,分为三种:

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

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

  • 内置命名空间 中存放了python解释器为我们提供的名字:input,print,str,list,tuple…。

    这三种命名空间之间的加载顺序为:

    内置命名空间(程序运行前加载) => 全局命名空间(程序运行中:从上到下加载) => 局部命名空间(程序运行中:调用时才加载)

    取值顺序为:

  • 在局部调用:局部命名空间 => 全局命名空间 => 内置命名空间

  • 在全局调用:全局命名空间 => 内置命名空间

    作用域

    作用域就是作用范围,按照生效范围可以分为两类:

  • 全局作用域:包含 内置命名空间、全局命名空间 ,在整个文件的任意位置都能被引用、全局有效 。

  • 局部作用域: 局部命名空间 ,只能在局部范围 生效。

    使用 globals() locals() 方法则会以字典类型返回当前位置的全局变量和局部变量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 全局调用
    print(globals())
    print(locals())


    def func():
    a = 12
    b = 20
    # 局部调用
    print(locals())
    print(globals())


    func()

    作用域介绍

    python中的作用域分4种情况:

  • L:local,局部作用域,即函数中定义的变量;

  • E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;

  • G:global,全局变量,就是模块级别定义的变量;

  • B:built-in,系统固定模块里面的变量,比如 int bytearray 等。 搜索变量的优先级顺序依次是:局部作用域 => 外层作用域 => 当前模块中的全局 => Python 内置作用域,也就是 LEGB 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    x = int(2.9)  # int built-in

    g_count = 0 # global


    def outer():
    o_count = 1 # enclosing

    def inner():
    i_count = 2 # local
    print(o_count)

    # print(i_count) 找不到
    inner()


    outer()

    # print(o_count) #找不到

    并且, local enclosing 是相对的, enclosing 变量相对上层来说也是 local

    作用域产生

    在 Python 中只有模块( module ),类( class )以及函数( def lambda )才会引入新的作用域,其它的代码块(如 if try for 等)是不会引入新的作用域的,如下代码中 if 并没有引入一个新的作用域, x 仍处在当前作用域中,后面代码可以使用:

    1
    2
    3
    if 2>1:
    x = 1
    print(x) # 1

    但是函数 def class lambda 会引入新的作用域:

    1
    2
    3
    def test():
    x = 2
    print(x) # NameError: name 'x' is not defined

    变量的修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    x = 6


    def f2():
    print(x)
    x = 5


    f2()

    '''
    UnboundLocalError: local variable 'x' referenced before assignment
    '''

    上述代码出现错误的原因在于 print(x) 时,解释器会先在局部作用区域找到变量 x 。这是因为在进行函数调用( f2() )之前,其前面的代码包括函数都会先加载到内存,因此执行到 print(x) 会先在局部区域找到 x = 5 ,但 x 的使用出现在 x 的声明前了,所以报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # x=6
    def f2():
    print(x)
    # x=5


    f2()

    '''
    NameError: name 'x' is not defined
    '''

    上述代码出现错误的原因在于 print(x) 时,解释器会在局部作用域找,找不到 变量 x 的声明,然后去全局作用域找也找不到,最后在 Python 的内置区域找也找不到,因此会报变量未声明的错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    x = 6


    def f2():
    x += 1


    f2()

    '''
    UnboundLocalError: local variable 'x' referenced before assignment
    '''

    x += 1 x = x + 1 是先加再赋值,解释器会在局部作用域找,会找到 x += 1 (函数已经加载到内存),但 x 的加法运算出现在赋值前了,所以报错。

    关键字 global

    当内部作用域想修改外部作用域的变量时,就要用到 global nonlocal 关键字了,当修改的变量是在全局作用域(global 作用域)上的,就要使用 global 先声明一下。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    count = 10


    def outer():
    global count
    print(count)
    count = 100
    print(count)


    outer()
    # 10
    # 100

    关键字 nonlocal

    关键字 global 声明的变量必须在全局作用域上,不能应用在嵌套作用域上,当要修改嵌套作用域( enclosing 作用域,外层非全局作用域)中的变量怎么办呢,这时就需要 nonlocal 关键字了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def outer():
    count = 10

    def inner():
    nonlocal count
    count = 20
    print(count)

    inner()
    print(count)


    outer()
    # 20
    # 20
  • 对于一个变量,内部作用域先声明就会覆盖外部变量,不在内部声明而直接使用,就会使用外部作用域的变量。
  • 内部作用域要修改外部作用域变量的值时,全局变量要使用 global 关键字,嵌套作用域变量要使用 nonlocal 关键字。nonlocal 是 Python3 新增的关键字,有了这个关键字就能完美的实现闭包了。
  •