一文掌握Python函数用法

本文通过理论+代码的方式介绍了Python函数的相关知识:包括:函数的参数、参数传递方式、参数解包、返回值、文档字符串、作用域、命名空间、递归、高阶函数、匿名函数、闭包、装饰器。在开发过程中或者看别人源码的过程中,这些知识是非常有用和经常遇到的,因此掌握这些知识很重要。本文也是Python系列文章的第3篇。

系列文章:

【Python 基础】一文补齐Python基础知识
【趣学Python:B站四大恶人】一文掌握列表、元组、字典、集合
【Python进阶】一文掌握Python函数用法
【Python进阶】Python面向对象之类与对象详解
【Python进阶】Python面向对象之装饰器与封装详解
【Python进阶】Python面向对象之继承和多态详解
【Python进阶】Python异常处理和模块详解
【Python进阶】Python文件(I/O)操作详解



1. 函数

1.1 函数简介(function)

  • 函数也是一个对象(object)
  • 对象是内存中专门用来存储数据的一块区域
  • fn是函数对象 ;fn()调用函数;例如:print 是函数对象 print() 调用函数;
  • 函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用;
  1. 创建函数:
def 函数名([形参1,形参2,...形参n]) :
    代码块
- 函数名必须要符号标识符的规范;
    (可以包含字母、数字、下划线、但是不能以数字开头)  
- 函数中保存的代码不会立即执行,需要调用函数代码才会执行;
  1. 调用函数:函数对象()

1.2 函数的参数

在定义函数时,可以在函数名后的()中定义数量不等的形参,多个形参之间使用,隔开;

1.2.1 形参和实参

  • 形参(形式参数),定义形参就相当于在函数内部声明了变量,但是并不赋值;
  • 实参(实际参数);如果函数定义时,指定了形参,那么在调用函数时也必须传递实参;实参将会赋值给对应的形参,简单来说,有几个形参就得传几个实参。
# 定义函数时,指定形参
def fn(a, b) :
    print(a,"+",b,"=",a + b)

# 调用函数时,传递实参
fn(10,20)
fn(123,456)

实参的传递方式:

def mul(a,b,c):
    print(a*b*c)
  • 位置参数;位置参数就是将对应位置的实参复制给对应位置的形参;第一个实参赋值给第一个形参,第二个实参赋值给第二个形参 ;fn(1 , 2 , 3)
  • 关键字参数;关键字参数,可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数:fn(b=1 , c=2 , a=3)
  • 位置参数和关键字参数可以混合使用;混合使用关键字和位置参数时,必须将位置参数写到前面:fn(1,c=30)
  • 函数在调用时,解析器不会检查实参的类型;实参可以传递任意类型的对象
  • 如果形参执行的是一个对象,当我们通过形参去修改对象时,会影响到所有指向该对象的变量;
def fn4(a):
    a[0] = 30
    print('a =',a,id(a))
c = 10   
c = [1,2,3] 
fn4(c)
# 不改变对象
fn4(c.copy()) # or fn4(c[:])

1.2.2 不定长参数

# 定义一个函数,可以求任意个数字的和
def sum(*nums):
    # 定义一个变量,来保存结果
    result = 0
    # 遍历元组,并将元组中的数进行累加
    for n in nums :
        result += n
    print(result)
  • 在定义函数时,可以在形参前边加上一个*,这样这个形参将会获取到所有的实参;它将会将所有的实参保存到一个元组中。
  • *a会接受所有的位置实参,并且会将这些实参统一保存到一个元组中(装包)
def fn(*a):
    print("a =",a,type(a))
fn()

'''
输出: 
a = () <class 'tuple'>
'''
  • 带星号的形参只能有一个;带星号的参数,可以和其他参数配合使用;第一个参数给a,第二个参数给b,剩下的都保存到c的元组中;
def fn2(a,b,*c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn2(1,2,3,4,5,6)

'''
输出:
a = 1
b = 2
c = (3, 4, 5, 6)
'''
  • 可变参数不是必须写在最后,但是注意,*的参数后的所有参数,必须以关键字参数的形式传递;第一个参数给a,剩下的位置参数给b的元组,c必须使用关键字参数
def fn3(a,*b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn3(1,2,3,4,5,c=6)

'''
输出:
a = 1
b = (2, 3, 4, 5)
c = 6
'''
  • 所有的位置参数都给a,b和c必须使用关键字参数;
def fn4(*a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn4(1,2,3,4,b=5,c=6)

'''
输出:
a = (1, 2, 3, 4)
b = 5
c = 6
'''
  • 如果在形参的开头直接写一个*,则要求所有的参数必须以关键字参数的形式传递
def fn5(*,a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn5(a=4,b=5,c=6)

'''
输出:
a = 4
b = 5
c = 6
'''
  • *形参只能接收位置参数,而不能接收关键字参数;
  • **形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中;字典的key就是参数的名字,字典的value就是参数的值;**形参只能有一个,并且必须写在所有参数的最后
def fn6(b,c,**a) :
    print('a =',a,type(a))
    print('b =',b)
    print('c =',c)

fn6(b=1,d=2,c=3,e=10,f=20)

'''
输出:
a = {'d': 2, 'e': 10, 'f': 20} <class 'dict'>
b = 1
c = 3
'''

1.2.3 参数解包

  • 传递实参时,也可以在序列类型的参数前添加星号,这样他会自动将序列中的元素依次作为参数传递;这里要求序列中元素的个数必须和形参的个数的一致
def fn7(a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)

# 创建一个元组
t = (10,20,30)
fn7(t[0],t[1],t[2])

# 创建一个列表
s = [10,20,30]
fn7(*s)

# 创建一个字典
d = {'a':10,'b':20,'c':30}
fn7(**d)

'''
输出:
a = 10
b = 20
c = 30
'''

1.2.3 返回值

  • 返回值,返回值就是函数执行以后返回的结果;
  • 可以通过 return 来指定函数的返回值;
  • 可以之间使用函数的返回值,也可以通过一个变量来接收函数的返回值;
  • return 后边可以跟任意的对象,返回值甚至可以是一个函数;
def fn():
    def fn2() :
        print('hello')
    return fn2 # 返回值也可以是一个函数
r = fn() # 这个函数的执行结果就是它的返回值
  • 如果仅仅写一个return 或者 不写return,则相当于return None ;
  • break 用来退出当前循环;continue 用来跳过当次循环;return 用来结束函数;
def fn4() :
    for i in range(5):
        if i == 3 :
            # break 用来退出当前循环
            # continue 用来跳过当次循环
            return # return 用来结束函数
        print(i)
    print('循环执行完毕!')
'''
break执行结果:0,1,2,循环执行完毕!
continue执行结果:0,1,2,4,循环执行完毕!
return执行结果:0,1,2
'''
def fn2() :
	a = 10
    return 
  • 在函数中,return后的代码都不会执行,return 一旦执行函数自动结束;
    参数解包:
def sum(*nums):
    # 定义一个变量,来保存结果
    result = 0
    # 遍历元组,并将元组中的数进行累加
    for n in nums :
        result += n
    return result

r = sum(1,2,3)
print(r)
print(r + 4)
def fn5():
    return 10

fn5fn5() 的区别:

  • fn5是函数对象,打印fn5实际是在打印函数对象;
  • fn5()是在调用函数,打印fn5()实际上是在打印fn5()函数的返回值;
    print(fn5) 输出<function fn5 at 0x05771BB8>;
    print(fn5()) 输出10;

1.3 文档字符串(doc str)

  • help()是Python中的内置函数;

  • 通过help()函数可以查询python中的函数的用法;

  • 语法:help(函数对象)

  • help(print) 获取print()函数的使用说明;

  • 文档字符串(doc str)

  • 在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明;

  • 当我们编写了文档字符串时,就可以通过 help() 函数来查看函数的说明;

  • 文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串;

  • -> int 表示函数返回值是int类型;

def fn(a:int,b:bool,c:str='hello') -> int:
    '''
    这是一个文档字符串的示例

    函数的作用:。。。。。
    函数的参数:
        a,作用,类型,默认值。。。。
        b,作用,类型,默认值。。。。
        c,作用,类型,默认值。。。。
    '''
    return 10

help(fn)

1.4 作用域与命名空间

1.4.1 作用域

  • 作用域(scope):指变量生效的区域;在Python中一共有两种作用域;

全局作用域

  • 全局作用域在程序执行时创建,在程序执行结束时销毁;
  • 所有函数以外的区域都是全局作用域;
  • 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问;

函数作用域

  • 函数作用域在函数调用时创建,在调用结束时销毁;
  • 函数每调用一次就会产生一个新的函数作用域;
  • 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问;

变量的查找

  • 当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用;如果没有则继续去上一级作用域中寻找,如果有则使用,如果依然没有则继续去上一级作用域中寻找,以此类推,直到找到全局作用域,依然没有找到,则会抛出异常NameError: name 'a' is not defined
def fn2():
	a = 10
    def fn3():
        print('fn3中:','a =',a)
    fn3()

fn2()

'''
输出:
10
'''    

在函数中为变量赋值时,默认都是为局部变量赋值;如果希望在函数内部修改全局变量,则需要使用 global 关键字,来声明变量;

def fn():
    global a # 声明在函数内部的使用a是全局变量,此时再去修改a时,就是在修改全局的a
    a = 10 # 修改全局变量
    print('函数内部:','a =',a)
fn()
print('函数外部:','a =',a)

1.4.2 命名空间

命名空间(namespace)

  • 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中;
  • 每一个作用域都会有一个它对应的命名空间;
  • 全局命名空间,用来保存全局变量。函数命名空间用来保存函数中的变量;
  • 命名空间实际上就是一个字典,是一个专门用来存储变量的字典;

locals()用来获取当前作用域的命名空间

  • 如果在全局作用域中调用 locals() 则获取全局命名空间;如果在函数作用域中调用locals()则获取函数命名空间;返回的是一个字典;scope = locals() 获取当前命名空间;
  • 在函数中可以获取全局命名空间,但在全局中无法获取函数的命名空间;
def fn4():
    a = 10
    # scope = locals() # 在函数内部调用locals()会获取到函数的命名空间
    # scope['b'] = 20 # 可以通过scope来操作函数的命名空间,但是也是不建议这么做

    # globals() 函数可以用来在任意位置获取全局命名空间
    global_scope = globals()
    # print(global_scope['a'])
    global_scope['a'] = 30
    # print(scope)

fn4()    

1.5 递归

# 尝试求10的阶乘(10!)
创建一个变量保存结果
n = 10
for i in range(1,10):
    n *= i

print(n)

创建一个函数,可以用来求任意数的阶乘:

def factorial(n):
    '''
        该函数用来求任意数的阶乘
        参数:n 要求阶乘的数字
    '''
    # 创建一个变量,来保存结果
    result = n
    for i in range(1,n):
        result *= i
    return result    

递归式的函数

  • 无穷递归,如果这个函数被调用,程序的内存会溢出,效果类似于死循环
def fn():
    fn()
fn()
  • 递归是解决问题的一种方式,和循环很像。它的整体思想是,将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题。

递归式函数的两个要件:
1.基线条件

  • 问题可以被分解为的最小问题,当满足基线条件时,递归就不再执行了;

2.递归条件

  • 将问题继续分解的条件;

使用递归重写计算任意阶乘的函数:

def factorial(n):
    '''
        该函数用来求任意数的阶乘
        参数:n 要求阶乘的数字
    '''
    # 基线条件;判断n是否为1,如果为1则此时不能再继续递归
    if n == 1 :
        # 1的阶乘就是1,直接返回1
        return 1

    # 递归条件    
    return n * factorial(n-1)
print(factorial(10))
  • 递归和循环类似,基本是可以互相代替的;
  • 循环编写起来比较容易,阅读起来稍难;递归编写起来难,但是方便阅读。
'''
 练习
   创建一个函数 power 来为任意数字做幂运算 n ** i
   10 ** 5 = 10 * 10 ** 4
   10 ** 4 = 10 * 10 ** 3
   ...
   10 ** 1 = 10
   '''
def power(n , i):
    '''
        power()用来为任意的数字做幂运算
        参数:
            n 要做幂运算的数字
            i 做幂运算的次数
    '''
    # 基线条件
    if i == 1:
        # 求1次幂
        return n
    # 递归条件
    return n * power(n , i-1)
    
print(power(8,6))
print(8**6)    

创建一个函数,用来检查一个任意的字符串是否是回文字符串,如果是返回True,否则返回False。

 ''' 

  回文字符串,字符串从前往后念和从后往前念是一样的
      abcba
  abcdefgfedcba
  先检查第一个字符和最后一个字符是否一致,如果不一致则不是回文字符串
      如果一致,则看剩余的部分是否是回文字符串
  检查 abcdefgfedcba 是不是回文
  检查 bcdefgfedcb 是不是回文
  检查 cdefgfedc 是不是回文
  检查 defgfed 是不是回文
  检查 efgfe 是不是回文
  检查 fgf 是不是回文
  检查 g 是不是回文
  '''
def hui_wen(s):
    '''
        该函数用来检查指定的字符串是否回文字符串,如果是返回True,否则返回False
        参数:
            s:就是要检查的字符串
    '''
    # 基线条件
    if len(s) < 2 :
        # 字符串的长度小于2,则字符串一定是回文
        return True
    elif s[0] != s[-1]:
        # 第一个字符和最后一个字符不相等,不是回文字符串
        return False    
    # 递归条件    
    return hui_wen(s[1:-1])

print(hui_wen('abcdefgfedcba'))     

1.6 高阶函数

  • 接收函数作为参数,或者将函数作为返回值的函数是高阶函数;
  • 当使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数;
# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]

# 定义一个函数,用来检查一个任意的数字是否是偶数
def fn2(i) :
    if i % 2 == 0 :
        return True
    return False    

# 这个函数用来检查指定的数字是否大于5
def fn3(i):
    if i > 5 :
        return True    
    return False

def fn(func , lst) :
    '''
        fn()函数可以将指定列表中的所有偶数获取出来,并保存到一个新列表中返回
        参数:
            lst:要进行筛选的列表
    '''
    # 创建一个新列表
    new_list = []

    # 对列表进行筛选
    for n in lst :
        # 判断n的奇偶
        if func(n) :
            new_list.append(n)

    # 返回新列表
    return new_list

def fn4(i):
    return i % 3 == 0
    
print(fn(fn4 , l))

1.6.1 filter()

  • filter() 可以从序列中过滤出符合条件的元素,保存到一个新的序列中
    参数:
    1.函数,根据该函数来过滤序列(可迭代的结构)
    2.需要过滤的序列(可迭代的结构)
    返回值:过滤后的新序列(可迭代的结构)
r = filter(fn4, 1)
print(r)
  • fn4 是作为参数传递进filter()函数中;fn4实际上只有一个作用,就是作为filter()的参数;filter() 调用完毕以后,fn4 就已经没用;

1.6.2 lambda 函数表达式 (语法糖)

  • lambda函数表达式专门用来创建一些简单的函数,他是函数创建的又一种方式;
  • 语法:lambda 参数列表 : 返回值
  • 匿名函数一般都是作为参数使用,其他地方一般不会使用;
r = filter(lambda i : i > 5 , l)
print(list(r))

1.6.3 map()

  • map() 函数可以对可跌倒对象中的所有元素做指定的操作,然后将其添加到一个新的对象中返回;
l = [1,2,3,4,5,6,7,8,9,10]
r = map(lambda i : i ** 2 , l)
print(list(r))

1.6.4 sort()

  • 该方法用来对列表中的元素进行排序;
  • sort()方法默认是直接比较列表中的元素的大小;
    sort()可以接收一个关键字参数key=key=需要一个函数作为参数,当设置了函数作为参数,每次都会以列表中的一个元素作为参数来调用函数,并且使用函数的返回值来比较元素的大小。
l = ['bb','aaaa','c','ddddddddd','fff']
l.sort(key=len)
print(l)
# 输出:['c', 'bb', 'fff', 'aaaa', 'ddddddddd']

l = [2,5,'1',3,'6','4']
l.sort(key=int)
print(l)
# 输出:['1', 2, 3, '4', 5, '6']

1.6.5 sorted()

  • 这个函数和sort()的用法基本一致,但是sorted()可以对任意的序列进行排序;
  • 并且使用sorted()排序不会影响原来的对象,而是返回一个新对象
l = [2,5,'1',3,'6','4']
print('排序前:',l)
print(sorted(l,key=int))
print('排序后:',l)
'''
输出:
排序前: [2, 5, '1', 3, '6', '4']
['1', 2, 3, '4', 5, '6']
排序后: [2, 5, '1', 3, '6', '4']
'''

1.6.6 闭包

  • 将函数作为返回值返回,也是一种高阶函数;这种高阶函数我们也称为叫做闭包
  • 通过闭包可以创建一些只有当前函数能访问的变量;也可以将一些私有的数据藏到的闭包中;
def fn():
    a = 10
    # 函数内部再定义一个函数
    def inner():
        print('我是fn2' , a)
    # 将内部函数 inner作为返回值返回   
    return inner
r = fn()
  • r 是一个函数,是调用fn()后返回的函数;
  • 这个函数只在fn()内部定义,并不是全局函数;
  • 所以这个函数总是能访问到fn()函数内的变量;

形成闭包的要件:
① 函数嵌套;
② 将内部函数作为返回值返回;
③ 内部函数必须要使用到外部函数的变量;

def make_averager():
    # 创建一个列表,用来保存数值
    nums = []

    # 创建一个函数,用来计算平均值
    def averager(n) :
        # 将n添加到列表中
        nums.append(n)
        # 求平均值
        return sum(nums)/len(nums)

    return averager

averager = make_averager()

print(averager(10)) # 输出:10.0
print(averager(20)) # 输出:15.0
print(averager(30)) # 输出:20.0
print(averager(40)) # 输出:25.0

1.7 装饰器

# 创建几个函数
def fn():
    print('我是fn函数....')
    
def add(a , b):
    '''
        求任意两个数的和
    '''
    r = a + b
    return r

def mul(a , b):
    '''
        求任意两个数的积
    '''
    r = a * b
    return r  

需求:希望函数可以在计算前,打印开始计算,计算结束后打印计算完毕。

我们可以直接通过修改函数中的代码来完成这个需求,但是会产生以下一些问题:
1.如果要修改的函数过多,修改起来会比较麻烦;
2.并且不方便后期的维护;
3.并且这样做会违反开闭原则(OCP):
  程序的设计,要求开发对程序的扩展,要关闭对程序的修改;

为了解决这个问题,我们创建一个函数,让这个函数可以自动的帮助我们生产函数;

def begin_end(old):
    '''
        用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束

        参数:
            old 要扩展的函数对象
    '''
    # 创建一个新函数
    def new_function(*args , **kwargs):
        print('开始执行~~~~')
        # 调用被扩展的函数
        result = old(*args , **kwargs)
        print('执行结束~~~~')
        # 返回函数的执行结果
        return result

    # 返回新函数        
    return new_function

  • *args接收所有位置参数;
  • **args接收所有的关键字参数;
  • 函数定义def new_function(*args , **kwargs)中的两个参数是把传入的参数装包成一个元组(*args)或一个字典(**kwargs);
  • result = old(*args , **kwargs)中的两个参数,*args将元组拆包成位置参数传入函数,**kwargs将字典拆包成关键字参数传入函数。

调用:

f = begin_end(fn)
f2 = begin_end(add)
f3 = begin_end(mul)

r = f()

print('第一次输出:\n',r)
r = f2(123,456)
print('第二次输出:\n',r)
r = f3(123,456)
print('第三次输出:\n',r)

'''
输出:
开始执行~~~~
我是fn函数....
执行结束~~~~
第一次输出:
 None
开始执行~~~~
执行结束~~~~
第二次输出:
 579
开始执行~~~~
执行结束~~~~
第三次输出:
 56088
'''

begin_end()这种函数我们就称它为装饰器

  • 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展;
  • 在开发中,通过装饰器来扩展函数的功能
  • 在定义函数时,可以通过@装饰器,来使用指定的装饰器,来装饰当前的函数

装饰器的标准用法:

@begin_end
def say_hello():
    print('大家好~~~')

say_hello()

'''
输出:
大家好~~~
'''
  • 可以同时为一个函数指定多个装饰器,这样函数将会按照从内向外的顺序被装饰
def fn3(old):
    '''
        用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束
        参数:
            old 要扩展的函数对象
    '''
    # 创建一个新函数
    def new_function(*args , **kwargs):
        print('fn3装饰~开始执行~~~~')
        # 调用被扩展的函数
        result = old(*args , **kwargs)
        print('fn3装饰~执行结束~~~~')
        # 返回函数的执行结果
        return result

    # 返回新函数        
    return new_function

@fn3
@begin_end
def say_hello():
    print('大家好~~~')
    
say_hello()

'''
输出:
fn3装饰~开始执行~~~~
开始执行~~~~
大家好~~~
执行结束~~~~
fn3装饰~执行结束~~~~
'''

交换装饰器执行顺序

@begin_end
@fn3
def say_hello():
    print('大家好~~~')
    
say_hello()
'''
输出:
开始执行~~~~
fn3装饰~开始执行~~~~
大家好~~~
fn3装饰~执行结束~~~~
执行结束~~~~
'''

参考课程:https://edu.aliyun.com/course/1782

发布了138 篇原创文章 · 获赞 551 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_39653948/article/details/105223715