python学习笔记:函数、装饰器

函数

注:python中无函数重载

定义函数

格式:

def 函数名(参数列表):
    函数体
#函数体需要有一个相对def语句的tab缩进

注:函数名亦可以作为实参传入函数

# 例:定义一个函数:生成10个[1,20)的随机数并打印
def fun():
    for i in range(10):
        ra = random.randint(1, 20)
        print(ra)

fun()

# 例:定义一个函数:求两数和并返回
def addfun(a, b):
    return a + b

print(addfun(1, 2))

# 求可迭代数据对象的最大值
def getmax(iterable):
    maxi = iterable[0]
    for i in iterable:
        if i > maxi:
            maxi = i
    return maxi
print(getmax([2, 3, 1, 0]))

可变参数

格式:

#形式1:
def 函数名(*参数名):# 星号后的参数名一般用 args
    函数体

#形式2:
def 函数名(**参数名):# 星号后的参数名一般用 kwargs
    函数体

可变参数必须位于参数列表末尾,且形式1和形式2最多出现一次,若两种形式同时出现,则*必须位于**之前
在底层进行实参与形参的匹配时,

  • 若遇到一个星号,则会将其后进行匹配的实参都装进一个以星号后的参数名命名的元组形参中
    • 若在相应位置传入list,tuple…等容器变量作为* 的实参,则此容器变量会被整个看作原元组的一个元素
    • 若想要是的传入的容器中的元素作为参数的元素,应当在传参时在其前方添加*进行拆包
    def fun(a, b, *c):
        print(a, b, c)
    
    fun(1,2)# 1 2 ()
    fun(1, 2, 3)# 1 2 (3,)
    fun(1,2,3,4)#1 2 (3, 4) 
    
    def fun(a, *c):
        print(a, c)
    
    tup = [3, 5]
    fun(0,tup) # 0 ([3, 5],)
    fun(0,*tup) ##0 (3, 5)
    
    # 编写一个函数,实现求多个数据的和
    def fun(*args):
        sum_f = 0
        if len(args) > 0:# 判断元组是否为空
            for i in args:
                if isinstance(i,int):#判断元素是否为整型变量
                    sum_f += i
        print(sum_f)
    
    fun(1, 2, 3)
    
  • 若遇到两个星号,则会将其后进行匹配的实参都装进一个以星号后的参数名命名的字典形参中
    • 此时进行传参时,对应位置应当以key = value的形式传参,
      (注意:若key为字符串形式,则其不可用引号包裹)
    • 在此种情况下也可以把字典作为实参,传入时应当在其前方添加**表示拆包为key = value的形式
    def fun(**info):
        print(info)
    
    fun()#打印  {}
    fun(name='feng', age=19)#打印{'name': 'feng', 'age': 19}
    
    dict_1 = {
           'type': '001', 'weight': 65} #定义一个字典变量用于作为实参传递
    fun(**dict_1)# 打印{'type': '001', 'weight': 65}
    
def fun(a, b, *c, **d):
    print(a, b, c, d)

fun(1, 2) #打印 1 2 () {}
fun(1, 2, 3, 4)#打印 1 2 (3, 4) {}
fun(1, 2, x=5, y=6)#打印 1 2 () {'x': 5, 'y': 6}
fun(1, 2, 3, x=5, y=6) # 打印  1 2 (3,) {'x': 5, 'y': 6}
fun(1, 2, x=5, 3) # 出错

关键字参数

在参数列表中以key = value的形式出现,

def 函数名(..., key = value, ...)
   函数体

此参数在传参时可以省略,使用value值做为默认值
当参数列表中含有多个关键字参数时,参数的匹配会按参数列表中的顺序进行匹配
若想要给位于中后部的关键字参数传参,则需要以key = value的格式完整的指明索要传参的参数和相应值.

def fun2(a, b=100,c = 200):
    print(a + b + c)

fun2(10) #打印310
fun2(10,10) #打印220
fun2(10, c = 20) # 打印130

def fun(a, b=100, **c):
    print(a, b, c)

fun(10)  # 10 100 {}
fun(10,10,b=20)  # 出错,在关键字参数与**的可变参数同时使用时,在为**参数赋值时的key值不可与其他参数名相同

函数的返回值

使用关键字 return

  • return 后面可以跟多个返回值,系统会将多个参数作为元素保存到一个元组中,然后返回此元组
  • 接收时也可以使用多个变量接收,参考拆包部分
    def fun(a, b):
        return a + b, a * b
    
    x, y = fun(10, 20)
    z = fun(10, 20)
    print(x, y, z)  # 30 200 (30, 200)
    
  • return语句也可以返回内部函数,使得外部的变量可以接收函数的内部函数,从而通过此变量来调用函数的内部函数(闭包)
    def fun():
        str = 'aaa'
    
        def inner_fun(): #定义内部函数
            print(str)
    
        return inner_fun #在外部函数中将内部函数名作为返回值,注意不带括号 
    
    my_fun = fun() # 变量 my_fun 接收函数fun返回的内部函数
    my_fun() # 通过变量名my_fun加上括号来实现调用内部函数的功能
    

全局变量与局部变量

声明在函数外面的变量称为全局变量,在代码块中定义的变量称为局部变量
若在函数中修改不可变的全局变量则会报错,而修改可变全局变量不会报错

str = 'aaa'#变量str为全局变量
def fun():
    str += 'sss'#此处在函数中试图修改全局变量str,会报错
    print(str)

解决:
使用关键字 global
在函数中,若想修改某不可变的全局变量的值,需要在函数内部的最前方添加带有global关键字的变量声明
(global声明要位于要修改全局不可变变量的函数内部,即若内部函数要修改其,则声明应置于内部函数中)

str = 'aaa'#变量str为全局变量
def fun():
    global str #使用global关键字进行声明
    str += 'sss'#此处对str的修改不会报错
    print(str)


list_1 = [1, 2, 3, 4]#可变全局变量
def fun():
    list_1.append(5)#在函数中不使用全局声明不会报错
    print(list_1)
fun()
print(list_1)

内部函数

在函数体中嵌套定义函数,
内部函数需要在函数内部调用才可以执行
带有内部函数的函数的底层处理步骤:

  1. 解释器扫描到外部函数的定义部分,将其加载到内存的一块空间中(仅是加载,而不执行),继续扫描函数外面的其他内容.
  2. 解释器扫描到调用外部函数的语句,将相应参数(如果有参数的话)传给函数的地址空间,开始执行函数
    • 执行函数中的操作逻辑
    • 扫描到内部函数的定义部分,将其加载到内存的一块空间中(并不执行内部函数).
    • 执行外部函数的其余部分
      • 若遇到内部函数的调用语句,则将相应数据传递到内部函数的地址空间中执行内部函数,执行完毕返回到调用内部函数的语句处
      • 若无则顺序执行余下逻辑

特点:

  • 可以访问外部函数的变量
  • 可以修改外部函数定义的可变变量,若要修改外部函数中的不可变变量,需要在内部函数中进行 nonlocal声明
  • 若要修改全局不可变变量,则应在内部函数中进行global声明
    def fun():
        lists = [9, 6, 5, 7, 3, 4]
        print(lists)
    
        def inner_fun():#内部函数
            lists.sort()
            print(lists)
    
        inner_fun()#调用内部函数
    
    fun()
    
    
    def fun():
        str = 'aaa'#str为不可变变量
    
        def inner_fun():
            str += 'bbb'#在内部函数中直接修改外部函数的不可变变量会报错
            print(str)
        inner_fun()
    
    fun()#运行时会报错
    
    def fun():
    
        str = 'aaa'#str为不可变变量
    
        def inner_fun():
            str += 'bbb'#在内部函数中直接修改外部函数的不可变变量会报错
            print(str)
        inner_fun()
    
    fun()#运行时会报错
    
    def fun():
        str = 'aaa'
    
        def inner_fun():
            nonlocal str #对要修改的外部函数变量进行nonlocal声明
            str += 'bbb'
            print(str)
    
        inner_fun() #调用内部函数
    
    fun()#此时可以正确运行
    
  • 系统内置函数locals() 将局部符号表作为字典返回(将当前作用域中的变量及其值以字典的形式返回返回)
    def fun():
        str = 'aaa'
        print(locals()) #{'str': 'aaa'}
    
        def inner_fun():
            st = 'bbb'
    
        inner_fun()
    
    fun() # 打印结果{'str': 'aaa'}
    
    • 在外部调用内部函数:见闭包

闭包

由函数及其内部函数组成的一个整体

将内部函数名作为外部函数的返回值的形式成为闭包
可以使用一个变量接收此返回值,并通过变量名()的方式实现在外部调用内部函数

def fun():
    str = 'aaa'

    def inner_fun(): #定义内部函数
    print(str)

    return inner_fun #在外部函数中将内部函数名作为返回值,注意不带括号 

my_fun = fun() # 变量 my_fun 接收函数fun返回的内部函数
my_fun() # 通过变量名my_fun加上括号来实现调用内部函数的功能

闭包的条件

  1. 外部函数中定义了内部函数
  2. 外部函数有返回值
  3. 返回值为内部函数名***注意:不带括号***
  4. 内部函数要引用了外部函数的变量

闭包的作用

  1. 可以实现统计的作用
  2. 读取其他元素的内部变量
  3. 延长作用域

闭包的应用

  • 保存参数状态
    如下例,变量first_fun接收了参数为1,2时的内部函数,变量second_fun接收了参数为3,4时的内部函数,当调用first_fun时,结果为当参数为1,2时的结果,即其保存了参数为1,2时的状态

    def fun(a, b):
        c = 10
    
        def inner_fun():
            print(a + b + c)
    
        return inner_fun # 返回内部函数名
    
    first_fun = fun(1, 2) # 将参数为1,2 时的内部函数赋值给变量
    second_fun = fun(3, 4) # 将参数为3,4 时的内部函数赋值给变量
    first_fun() # 打印 13
    second_fun() # 打印 17
    
  • 计数器

    def fun():
        con = [0]
    
        def inner_fun():
            con[0] += 1
            print('当前是第{}此访问'.format(con[0]))
    
        return inner_fun
    
    counter = fun()
    counter() # 当前是第1此访问
    counter() # 当前是第2此访问
    counter() # 当前是第3此访问
    
  • 闭包同级访问
    同级的内部函数可以互相调用

    def fun():
        a = 100
    
        def inner_fun1():# 内部函数1
            b = 90
            s = a + b
            print(s)
    
        def inner_fun2():# 内部函数2
            inner_fun1() # 调用内部函数1
            print('内部函数2')
            return 'aaaa'
    
        return inner_fun2
    
    f = fun()
    s = f() # 打印 内部函数2
    print(s)# 打印 aaaa
    

装饰器

在不修改原函数的情况下扩充原函数的功能

引入:地址引用

def func():
    print('函数func执行')

def test(f):#其参数为函数名
    print('函数test执行')
    print(f)
    f()#执行参数函数

test(func)#调用函数test并将函数func的函数名作为参数传入
'''
打印结果:
函数test执行

函数func执行
'''

装饰器的特点

  1. 函数作为参数传递给另一个函数
  2. 要有闭包的特点

装饰器的定义与使用

def my_decorate(func):#定义装饰器,其参数为函数名
   a = 100

   def wrapper():#装饰器的内部函数
       func()#调用参数函数
       print('1111')
       print('2222')

   return wrapper#返回内部函数

#使用装饰器,在要进行装饰的函数定义前添加注解  @装饰器名

@my_decorate #添加注解
def my_func():#定义被装饰函数my_func()
   print('0000')


my_func()  # 调用函数my_func()
'''
打印结果:
0000
1111
2222
'''

上述装饰器为自定义函数my_func()增加了打印11112222的功能

def my_decorate(func):
    a = 100
    print('wrapper外层打印测试....')#添加输出语句
    def wrapper():
        func()
        print('1111')
        print('2222')
    print('wrapper加载完成...')#在wrapper函数外添加输出语句
    return wrapper

@my_decorate  # 用装饰器修饰函数
def my_func():
    print('0000')
# 此处并未调用被装饰的函数
'''
打印结果:
wrapper外层打印测试....
wrapper加载完成...
'''

装饰器的执行:

  1. 解释器解释到装饰器的定义部分,在内存中开辟区域加载装饰器函数(此时加载器并不会解释其内部的逻辑,仅仅将其加载到内存)
  2. 解释器解释到@装饰器名处,底层自动进行以下处理
    • 将被装饰函数名作为参数传递给装饰器(相当于调用了装饰器函数)
    • 系统执行装饰器函数的内部逻辑
    • 解释器解释到装饰器函数的内部函数,将其加载到内存的一块区域中(装饰器的内部函数中必定存在调用参数函数(即被装饰函数)的语句,但底层并不调用内部函数)
    • 解释器执行其他处理逻辑(即内部函数之后的逻辑)
    • 解释器执行装饰器函数的return语句(装饰器使用了闭包的方式,因此其必定存在返回内部函数名的return语句)
    • 系统底层使用与被装饰函数同名的变量接收装饰器返回的内部函数名(此时在调用被装饰函数的地方的地址已经变成了装饰器内部函数的地址,而并非原函数,可通过打印被装饰函数的函数名来验证,其地址已经变成了装饰器的内部函数)
  3. 调用被调用函数(相当于调用了装饰器的内部函数)

带有参数的被装饰函数

若被装饰函数有参数,则装饰器的内部函数以及内部函数中进行调用参数函数的语句都应当带上参数,为了使装饰器可以适合多种参数列表的形式,可以采用两种可变参数作为装饰器内部函数的参数,并在其内部调用参数函数时将两个可变参数进行拆包传入.

import time

def my_decorate(func):
    a = 10

    def wrapper(*args, **kwargs):#装饰器内部函数带参数,为适合多种参数形式,因此采用可变参数
        print('加载中,请稍后...')
        time.sleep(2)  # 等待2秒钟
        print('加载完成')
        func(*args,**kwargs)#调用参数函数时传入参数,此处应带上*及**进行拆包

    return wrapper

@my_decorate
def f1(n):
    print('--f1--', n)

f1(4)

@my_decorate
def f2(a, b):
    print('--f2--', a, b)

f2(4, 5)

@my_decorate
def f3(lists):
    for i in lists:
        print(i)

names = ['lily', 'lucy', 'mike']
f3(names)

@my_decorate
def f4(a, age=12):
    print('--f1--', a, age)

f4(18, age=10)

带参数的装饰器

若装饰器要求带有参数,则此装饰器需要有三层

def outer(a): # 装饰器第一层,带有一个参数a,其负责接收装饰器的参数
    def decorate(func): # 装饰器第二层,其负责接收被装饰函数
        def wrapper(*args,**keargs): # 装饰器第三层.其负责接收被装饰函数的参数
            func(*args,**keargs)
            print('装饰'.format(a))

        return wrapper # 返回第三层

    return decorate # 返回第二层

@outer(10) # 添加装饰器并传入参数
def my_fun():
    print('hahaha')

my_fun()
'''
打印结果:
hahaha
装饰10
'''

多个装饰器修饰同一个被装饰函数

被装饰函数可以被多个装饰器同时装饰,但其装饰顺序采用就近原则,越靠近被装饰函数的就越先进行装饰

def decorate_1(func):#装饰器1
    print('动作1---开始')
    def wrapper():
        func()
        print('动作1--拿筷子')

    print('act_1---结束')
    return wrapper


def decorate_2(func):#装饰器2
    print('动作2---开始')
    def wrapper():
        func()
        print('动作2--拿馒头')

    print('act_2---结束')
    return wrapper


@decorate_1
@decorate_2
def eat():#被装饰函数,使用两个装饰器进行修饰
    print('准备吃饭...')

eat()#调用被装饰函数
'''
动作2---开始
动作2---结束
动作1---开始
动作1---结束
准备吃饭...
act_2--拿馒头
act_1--拿筷子
'''

上例中装饰器距离被装饰函数最近,先由其进行装饰.

装饰器的应用–付款(模拟)

import time

islogin = False


def login():  # 登录模块
    username = input('请输入用户名:\n')
    pw = input('请输入密码:\n')
    if username == 'admin' and '123456':
        return True
    else:
        return False


# 定义一个装饰器,进行付款验证
def login_required(func):
    def wrapper(*args, **kwargs):
        # 验证用户是否已经登录
        global islogin
        if islogin:
            func(*args, **kwargs)
        else:
            # 跳转到登陆模块
            print('尚未登陆,请登录')
            islogin = login()
            print('result', islogin)

    return wrapper

@login_required
def pay(money):
    print('正在付款,付款金额为:{}'.format(money))
    print('付款中')
    time.sleep(2)
    print('付款完成')

pay(100)

匿名函数

对于逻辑较少的函数(函数体只有一两行)可通过匿名函数的形式进行简化.

  • 定义格式:

    lambda 参数1,参数2...:运算  # 运算结果会自动return 
    
  • 匿名函数的调用:
    使用一变量接收匿名函数,则此变量与匿名函数指向同一地址,可通过变量名()的形式调用此匿名函数,匿名函数的运算结果会自动return,因此也需要一变量接收匿名函数的返回值
    如:

    fun = lambda a, b: a + b  # 定义匿名函数并用一变量接收此函数
    
    sum=fun(1,2)  # 调用匿名函数
    
  • 匿名函数可以作为函数的参数

    def fun(x, y, func):  # 参数func为一函数参数
        print(x, y)
        print(func)
        a = func(x, y)
        print(a)
    
    fun(1, 2, lambda a, b: a + b)  # 将匿名函数作为参数传入
    
  • 匿名函数与内置函数结合
    如,内置函数max()

    def max(*args, key=None): # known special case of max
        """
        max(iterable, *[, default=obj, key=func]) -> value
        max(arg1, arg2, *args, *[, key=func]) -> value
        
        With a single iterable argument, return its biggest item. The
        default keyword-only argument specifies an object to return if
        the provided iterable is empty.
        With two or more arguments, return the largest argument.
        """
        pass
    
    list2 = [{
           'a': 10, 'b': 20}, {
           'a': 13, 'b': 8}, {
           'a': 2, 'b': 5} # 其为元素是字典的列表,
    max_li = max(list2, key=lambda dic: dic['a'])#调用max()时指定器关键字参数key,使器在比较列表中的各个字典时按照字典中的'a'进行比较
    print(max_li)
    

    上例中列表的元素为字典类型,max()函数无法直接比较字典的大小(字典无法使用>及<进行比较),要想对其进行比较,需要使用max()函数中的关键字参数,指定其为字典中的可比较类型,是的函数按照此类型对字典进行比较.
    max()函数的关键字参数key=func,即其接收一个函数作为参数,默认值为None,在使用此参数时,可以使用匿名函数作为参数传入

你可能感兴趣的:(python)