Python学习之函数(2)

118 阅读14分钟

Python学习之函数(2)

本笔记参考《尚硅谷Python零基础入门教程》整理总结,供后续学习查阅

1.介绍

  • 函数也是一个对象

  • 对象是内存中专门用来存储数据的一块区域

  • 函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用

  • 创建函数:

    def 函数名([形参1,形参2,...形参n]) :
        代码块
    
    • 函数名必须要符号标识符的规范 (可以包含字母、数字、下划线、但是不能以数字开头)
  • 函数中保存的代码不会立即执行,需要调用函数代码才会执行

  • 调用函数:函数对象()

  • 定义函数一般都是要实现某种功能的

# 定义一个函数
def fn() :
    print('这是我的第一个函数!')
    print('hello')
    print('今天天气真不错!')

# 定义一个函数,可以用来求任意两个数的和
def sum() :
    a = 123
    b = 456
    print(a + b)
sum()

2.函数的参数

  • 在定义函数时,可以在函数名后的()中定义数量不等的形参, 多个形参之间使用,隔开
  • 形参(形式参数),定义形参就相当于在函数内部声明了变量,但是并不赋值
  • 实参(实际参数)
    • 如果函数定义时,指定了形参,那么在调用函数时也必须传递实参, 实参将会赋值给对应的形参,简单来说,有几个形参就得传几个实参
# 定义函数时指定形参
def fn2(a , b) :
    print(a,"+",b,"=",a + b)
# 调用函数时,来传递实参
fn2(10,20)

# 定义形参时,可以为形参指定默认值
# 指定了默认值以后,如果用户传递了参数则默认值没有任何作用
#   如果用户没有传递,则默认值就会生效
def fn(a = 5 , b = 10 , c = 20):
    print('a =',a)
    print('b =',b)
    print('c =',c)
    
# 关键字参数,可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数
fn(b=1 , c=2 , a=3)
# 位置参数和关键字参数可以混合使用
# 混合使用关键字和位置参数时,必须将位置参数写到前面
fn(1,c=30)

# 函数在调用时,解析器不会检查实参的类型
# 实参可以传递任意类型的对象
def fn2(a):
    print('a =',a)
fn2(1)
fn2('fff') 

def fn4(a):
    # 在函数中对形参进行重新赋值,不会影响其他的变量
    # a = 20
    # a是一个列表,尝试修改列表中的元素
    # 如果形参执行的是一个对象,当我们通过形参去修改对象时
    #   会影响到所有指向该对象的变量
    a[0] = 30
    print('a =',a,id(a))
c = 10
fn4(c)

3.不定长的参数


# 定义一个函数,可以求任意个数字的和
# 在定义函数时,可以在形参前边加上一个*,这样这个形参将会获取到所有的实参
def sum(*nums):
    # 定义一个变量,来保存结果
    result = 0
    # 遍历元组,并将元组中的数进行累加
    for n in nums :
        result += n
    print(result)
sum(123,456,789,10,20,30,40)

# *a会接受所有的位置实参,并且会将这些实参统一保存到一个元组中(装包)
def fn(*a):
    print("a =",a,type(a))

# 带星号的形参只能有一个
# 带星号的参数,可以和其他参数配合使用
# 第一个参数给a,第二个参数给b,剩下的都保存到c的元组中
def fn2(a,b,*c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn2(1,2,3,4,5)
    
# 可变参数不是必须写在最后,但是注意,带*的参数后的所有参数,必须以关键字参数的形式传递
# 第一个参数给a,剩下的位置参数给b的元组,c必须使用关键字参数
def fn2(a,*b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn2(1,2,3,4,c=5) 
    
# 所有的位置参数都给a,b和c必须使用关键字参数
def fn2(*a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)    

# 如果在形参的开头直接写一个*,则要求我们的所有的参数必须以关键字参数的形式传递
def fn2(*,a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
fn2(a=3,b=4,c=5)    
    
# *形参只能接收位置参数,而不能接收关键字参数
def fn3(*a) :
    print('a =',a)
fn3(a=1,b=3)  #关键字参数   
    
    
# **形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中
#   字典的key就是参数的名字,字典的value就是参数的值
# **形参只能有一个,并且必须写在所有参数的最后
def fn3(b,c,**a) :
    print('a =',a,type(a))
    print('b =',b)
    print('c =',c)

fn3(b=1,d=2,c=3,e=10,f=20)    
#结果:
#a = {'d': 2, 'e': 10, 'f': 20} <class 'dict'>
#b = 1
#c = 3 
    
    
# 参数解包(拆包)
def fn4(a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)

# 创建一个元组
t = (10,20,30)
# 传递实参时,也可以在序列类型的参数前添加星号,这样他会自动将序列中的元素依次作为参数传递
# 这里要求序列中元素的个数必须和形参的个数的一致
fn4(*t)    

# 创建一个字典
d = {'a':100,'b':200,'c':300}
# 通过 **来对一个字典进行解包操作
fn4(**d)    
    

4.返回值

返回值,返回值就是函数执行以后返回的结果

可以通过 return 来指定函数的返回值

可以之间使用函数的返回值,也可以通过一个变量来接收函数的返回值


# return 后边跟什么值,函数就会返回什么值
def fn():
    return 100
print(fn())

# return 后边可以跟任意的对象,返回值甚至可以是一个函数
def fn():
    def fn2() :
        print('hello')
    return fn2 # 返回值也可以是一个函数

# 如果仅仅写一个return 或者 不写return,则相当于return None 
def fn2() :
    a = 10
    return 

# 在函数中,return后的代码都不会执行,return 一旦执行函数自动结束
def fn3():
    print('hello')
    return
    print('abc')

def fn4() :
    for i in range(5):
        if i == 3 :
            # break 用来退出当前循环
            # continue 用来跳过当次循环
            return # return 用来结束函数
        print(i)
    print('循环执行完毕!')    
    

5.文档字符串

# help()是Python中的内置函数
# 通过help()函数可以查询python中的函数的用法
# 语法:help(函数对象)
help(print) # 获取print()函数的使用说明


# 文档字符串(doc str)
# 在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明
#   当我们编写了文档字符串时,就可以通过help()函数来查看函数的说明
#   文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串
def fn(a:int,b:bool,c:str='hello') -> int:
    '''
    这是一个文档字符串的示例

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

help(fn)

6.作用域和命名空间

作用域:作用域指的是变量生效的区域

在Python中一共有两种作用域 全局作用域

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

函数作用域

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

变量的查找

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

# 局部变量
def fn():
    a = 10 # a定义在了函数内部,所以他的作用域就是函数内部,函数外部无法访问
    print('函数内部:','a =',a)
    print('函数内部:','b =',b)
 fn()   

# 使用global关键字
a = 20
def fn3():
    # a = 10 # 在函数中为变量赋值时,默认都是为局部变量赋值
    # 如果希望在函数内部修改全局变量,则需要使用global关键字,来声明变量
    global a # 声明在函数内部的使用a是全局变量,此时再去修改a时,就是在修改全局的a
    a = 10 # 修改全局变量
    print('函数内部:','a =',a)
fn3()
print('函数外部:','a =',a)

命名空间:指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中

  • 每一个作用域都会有一个它对应的命名空间

  • 全局命名空间,用来保存全局变量。函数命名空间用来保存函数中的变量

  • 命名空间实际上就是一个字典,是一个专门用来存储变量的字典

# locals()用来获取当前作用域的命名空间
# 如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间
# 返回的是一个字典
scope = locals() # 当前命名空间
print(type(scope)) #<class 'dict'>
a = 1
print(a)  等同于 print(scope['a'])

scope['c'] = 1000 # 向字典中添加key-value就相当于在全局中创建了一个变量(一般不建议这么做)
print(c)

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() 

7.递归

递归简单理解就是自己去引用自己!

递归式函数,在函数中自己调用自己!

递归是解决问题的一种方式,它和循环很像 它的整体思想是,将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题 递归式函数的两个要件 1.基线条件

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

2.递归条件

  • 将问题继续分解的条件 递归和循环类似,基本是可以互相代替的, 循环编写起来比较容易,阅读起来稍难 递归编写起来难,但是方便阅读
# 创建一个函数,可以用来求任意数的阶乘
def factorial(n):
    '''
        该函数用来求任意数的阶乘

        参数:
            n 要求阶乘的数字
    '''

    # 创建一个变量,来保存结果
    result = n
    
    for i in range(1,n):
        result *= i

    return result   

8.高阶函数

函数式编程

  • 在Python中,函数是一等对象

  • 一等对象一般都会具有如下特点: ① 对象是在运行时创建的 ② 能赋值给变量或作为数据结构中的元素 ③ 能作为参数传递 ④ 能作为返回值返回

  • 高阶函数

    • 高阶函数至少要符合以下两个特点中的一个 ① 接收一个或多个函数作为参数 ② 将函数作为返回值返回

高阶函数:接收函数作为参数,或者将函数作为返回值的函数是高阶函数

当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数

# 定义一个函数,用来检查一个任意的数字是否是偶数
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 :
        # 函数引用
        if func(n) :
            new_list.append(n)
    # 返回新列表
    return new_list

# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]
print(fn(fn2 , l))
print(fn(fn3 , l))



# filter()
# filter()可以从序列中过滤出符合条件的元素,保存到一个新的序列中
# 参数:
#  1.函数,根据该函数来过滤序列(可迭代的结构)
#  2.需要过滤的序列(可迭代的结构)
# 返回值:
#   过滤后的新序列(可迭代的结构)

# fn2是作为参数传递进filter()函数中
#   而fn2实际上只有一个作用,就是作为filter()的参数
#   filter()调用完毕以后,fn2就已经没用
print(list(filter(fn2,l)))


# 匿名函数 lambda 函数表达式 (语法糖)
#   lambda函数表达式专门用来创建一些简单的函数,他是函数创建的又一种方式
#   语法:lambda 参数列表 : 返回值
#   匿名函数一般都是作为参数使用,其他地方一般不会使用
def fn5(a , b):
    return a + b
上面方法定义类似 lambda a,b : a + b
匿名方法调用:(lambda a,b : a + b)(10,20)
print((lambda a,b : a + b)(10,20))
print(list(filter(lambda i : i > 5,l)))


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

9.闭包

将函数作为返回值返回,也是一种高阶函数

这种高阶函数我们也称为叫做闭包,通过闭包可以创建一些只有当前函数能访问的变量

可以将一些私有的数据藏到的闭包

def fn():
    a = 10
    # 函数内部再定义一个函数
    def inner():
        print('我是fn2' , a)
    # 将内部函数 inner作为返回值返回   
    return inner

# r是一个函数,是调用fn()后返回的函数
# 这个函数是在fn()内部定义,并不是全局函数
# 所以这个函数总是能访问到fn()函数内的变量
r = fn() 
r() # 调用内部函数

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

10.装饰器

# 三个函数
def add(a , b):
    '''
        求任意两个数的和
    '''
    r = a + b
    return r


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

def fn():
    print('我是fn函数....')
    
# 希望函数可以在计算前,打印开始计算,计算结束后打印计算完毕
#  我们可以直接通过修改函数中的代码来完成这个需求,但是会产生以下一些问题
#   ① 如果要修改的函数过多,修改起来会比较麻烦
#   ② 并且不方便后期的维护
#   ③ 并且这样做会违反开闭原则(OCP)
#           程序的设计,要求开发对程序的扩展,要关闭对程序的修改

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

        参数:
            old 要扩展的函数对象
    '''
    # 创建一个新函数
    def new_function(*args , **kwargs): #由于有些没有参数,有些有参数,所以*args可以接受所有,**kwargs可以接受所有关键字参数
        print('开始执行~~~~')
        # 调用被扩展的函数
        result = old(*args , **kwargs)
        print('执行结束~~~~')
        # 返回函数的执行结果
        return result

    # 返回新函数        
    return new_function

f = begin_end(fn) #没有参数
f2 = begin_end(add) #有参数
f3 = begin_end(mul)
r = f()
r = f2(123,456)

print(r)

# 向begin_end()这种函数我们就称它为装饰器
#   通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展
#   在开发中,我们都是通过装饰器来扩展函数的功能的
# 在定义函数时,可以通过@装饰器,来使用指定的装饰器,来装饰当前的函数
#   可以同时为一个函数指定多个装饰器,这样函数将会安装从内向外的顺序被装饰

@fn3
@begin_end #使用@那么可以用begin_end来装饰say_hello
def say_hello():
    print('大家好~~~')
# 调用
say_hello()