第六章 函数

d函数

一、函数基础

1、什么是函数?

在一个完整的项目中,某些功能会被反复使用。那么会将某段代码封装成函数,当我们要使用功能的时候直接调用函数即可

函数是可以实现一些特定功能的小方法或是小程序。

优点

  1. 增加了代码的重复使用率 简化代码结构,增加了代码的复用度(重复使用的程度)
  2. 减少了代码量 提高了 代码 的可读性
  3. 提高了代码的维护

2、函数的定义

  • 主体结构

    使用def关键字 + 函数名():

    函数体

    实例:

    def 函数名([参数]):
        函数体
    

    函数名:

    使用统一标识符来定义函数名

    注意:

    • 函数名区分大小写
    • 函数可以重名 但是会被覆盖

3、函数调用

  • 调用函数

    格式:函数名(参数列表)

  • 注意:调用函数必须在定义之后

  • 本质:实参给形参赋值的过程

4、最简单的函数(无参无返回值)

speak()  # 报错,函数未定义
def speak():
    print('Lucky老师真的帅啊')
speak()
speak()

5、函数的返回值

  • 注意

  • 最后的return 表达式,可以不写,相当于return None

  • return 当函数体遇到return 那么return下面的代码将不会在执行 有return的函数可以将值赋给变量

    def demo():
        return "hello" #返回单个值
      return ["a","b"] #返回多个值 == return 1,2,4#元祖的形式
      print("我将不会在输出")
    var = demo()
    print(var)
    
  • 如果没有返回值的函数 不能将值赋给变量 当赋值给变量 打印该变量为None

    def demo():
        print("没有返回值")
    print(demo()) #None
    
  • 返回多个值

    多个值用逗号分隔

    #return 返回多个值
    def value():
        return [1,2,3,4,'a','b']
        return 1,2,3  #(1, 2, 3)
        return {"a":1}
          return 1, 2  # 多个值用逗号分隔
    print(value())
    

二、函数提升

1、 带普通参数的函数

  • 形参:在函数定义的时候 在括号里 写入的变量称为 形参

  • 实参:在函数调用的时候 传递的参数 成为实参

  • 注意:参数的数量理论上是无限的,但是实际上最好不要超过6、7个,类型是没有限制

    实例:

    def add(x,y):  #形参  且没有默认值
        print(x,y)
    add(10,20)  #实参  实参的个数要和形参的个数对应上  前提是 形参没有默认值
    

2、参数默认值

  • 主体结构

    def 函数名(参数=值,参数=值):

    pass

    实例:

    def test(x=10,y): #函数的形参的默认值  如果在形参中有的参数有默认值 有的没有 那么要将有默认值的参数 放在后面
    print(x,y)
    test() #SyntaxError   non-default argument follows default argument
    
  • 注意:

    1. 如果定义的函数有形参 且 没有默认值 那么在调用的时候 必须传实参 且传的实参的个数 不能大于 形参的个数
    2. 函数在定义的时候 如果形参都有默认值 那么传的实参 会将默认值 覆盖掉 此时的实参可传可不传
    3. 函数的形参的默认值 如果在形参中有的参数有默认值 有的没有 那么要将有默认值的参数 放在后面(要遵循默认值的规则)

3、关键字参数

可以使用关键字 来改变我的形参与实参的默认从左到右的对应关系
  • 注意:

    当不按照顺序给形参的值的时候 关键字 要么就都给 要么就不给

    实例

    def test(x,y):
        print('x为',x,'y为',y)
    test(y=10,x=100)
    test(10,100)
    
  • 关键字参数和默认值的搭配

    def a(x=0,y=0):
        print(x,y)
    
    a(2,y=2)
    
    def a(a,b,c):
        print(a,b,c)
    
    print(a(0,1,c=2))
    
  • 注意:

    1. 有形参的且没有默认值的时候 必须要有实参

    2. 有形参且有默认值的时候 实参可有可无( 默认参数必须放在最后面,否则会报错)

    3. 参数的位置可以改变 但是在使用的时候 形参名=实参的值

    4. 如果定义的函数 有形参 且没有默认值 则关键字参数 都需要指定给值#如果定义的函数 有形参 且有默认值 则可以只给第一个设置关键字 后面可以不传实参 如果第一个给了关键字 则后面都需要统一给定关键字

    5. 如果形参后面都有默认值 则可以指定后面的某个参数传递关键字参数

      def test(x,y=2,z=3):
          print(x,y)
      
      test(1,z=4)
      

4、不定长参数

  • 概述: 也就是传入的参数的个数不固定 能处理比当初定义时更多的参数

  • *args

    不确定参数 *形参名 以元组的形式去接收

    在形参变量前加了个星号,变量会存放所有未命名的变量的参数,如果在函数调用时没有指定参数,它就是个空元组

    示例:

    def func(*args):
        print(args) #(1,5345,34) 元组类型
    func(1,5345,34)
    
    def func(a,b,*args):
        print(a) #1
        print(b) #5
        print(args)#(2,34)
    func(1,5,2,34)
    
  • **kwargs

    不确定实参的**接收 必须是 关键字 加值 接收以后 是 字典类型

    def func(**kwargs):  #接收不确定的形参的值  以元组的形式
        print(kwargs)
        for i in kwargs:  #遍历元组
            print(i,kwargs[i])
    
    func2(x=1, y=2, z=3) #函数调用
    
  • *和 **一起作为形参

    *args**kwargs可以同时在函数的定义中,但是*args必须在**kwargs前面. SyntaxError: invalid syntax

    def func(*args,**kwargs):
        print(args,kwargs)
    func(1,2,3,4,username="lucky",password="123456")
    
  • ** 函数调用的时候的** 和参数**的接收

    def func(**dic):
        print(dic) # {'name':'zs'}
    x = {'name': 'zs'}
    func(**x)
    
  • 当调用处实参为*args的情况

    def func(a,b,c,d):
        pass
    myList = [1,2,3,4]
    func(*myList)
    
    def a(*args):
        print(args) #
    x = '123'
    print(a(*x))
    

5、函数也是一种数据

函数也是一种数据,那么就可以用变量来保存(函数名也是一个特殊的变量)

应用:将函数作为参数传递(回调函数)

def speak():
    print('Lucky老师真的帅啊')

def test(say):
    say()
test(speak)

三、函数的例子

1、实现类似pop 函数的功能

def myPop(myList):
    x = myList[-1]
    del myList[-1]
    return x
x = [1,2,3]
print(myPop(x))

2、 实现类似popitem()函数的功能 删除最后一对键值对 并返回

Dict = {'a':'a'}
def a(Dict):
    keys = list(Dict.keys())
    # print(keys[-1])
    myTup = (keys[-1],Dict[keys[-1]])
    del Dict[keys[-1]]
    return myTup

print(a(Dict))
print(Dict)

3、定义一个函数 实现 字典得键值交换

Dict = {'a':'b','c':'d'}
def Demo(Dict):
    newDict = {}
    for i in Dict:
        # print(i)
        newDict[Dict[i]] = i
    return newDict
print(Demo(Dict))

4、计算器

def add(x, y):
    """相加   """

    return x + y

def subtract(x, y):
    """相减"""

    return x - y

def multiply(x, y):
    """相乘"""

    return x * y

def divide(x, y):
    """相除"""

    return x / y

# 用户输入
print("选择运算:")
print("1、相加")
print("2、相减")
print("3、相乘")
print("4、相除")

choice = input("输入你的选择(1/2/3/4):")

num1 = int(input("输入第一个数字: "))
num2 = int(input("输入第二个数字: "))
# num1,num2 = eval(input('输入'))

if choice == '1':
    print(num1, "+", num2, "=", add(num1, num2))

elif choice == '2':
    print(num1, "-", num2, "=", subtract(num1, num2))

elif choice == '3':
    print(num1, "*", num2, "=", multiply(num1, num2))

elif choice == '4':
    print(num1, "/", num2, "=", divide(num1, num2))
else:
    print("非法输入")

四、变量的作用域

1、作用域

  • 概念

    Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。

  • 作用域的划分

    1. 局部作用域(L)
    2. 闭包函数外到函数中(E)
    3. 全局作用域(G)
    4. 内建作用域(B)
  • 变量查找的规则

    L->E->G->B

    以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。不会向低级作用域中查找

  • 注意

    Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这这些语句内定义的变量,外部也可以访问

    x = int(2.9)  # 内建作用域
     
    g_count = 0  # 全局作用域
    def outer():
        o_count = 1  # 闭包函数外的函数中
        def inner():
            i_count = 2  # 局部作用域
    

2、作用域体现

在函数外部声明的变量为全局变量 在函数外部声明的变量为局部变量

num = 10 #全局变量
def demo():
    num = 2 #局部变量

在函数外部声明的变量 在函数内部可以获取到 在函数内部声明的变量在函数外部不能够获取到

num = 3
def demo():
    print(num)

注意:

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

错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。

3、global 修改全局变量的值

关键字 global 声明变量函数内外使用同一个

str1 = x
def test():
    global str1  #将str1变量 声明为 全局变量  内外使用的都为同一个变量
                 #否则在函数内部是可以获取函数外的变量 但是修改以后 就会生成一个新的局部变量 和函数外的变量 不是同一个变量
    str1 = 2
test()
print(str1)

4、nonlocal 修改嵌套作用域中变量的值

如果函数发生嵌套 那么内部的函数通过 nonlocal 可以获取外层函数的变量

def  Test():
    num=10 #函数内部都是局部变量
    def Testin():
        nonlocal num #内层testin,直接操作外层Test,
        num=1000 #没有noloacal解释为新建一个变量,否则解释为重新赋值
        print("test in",num,id(num))

    Testin()
    print("test",num,id(num))

Test()

5、值传递与引用传递

不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a) 内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。

可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响

  • 值传递:传递不可变数据类型

    在函数内部如果修改函数外部的变量(如整形 字符串...) 那么在函数内部相当于是声明了一个新的变量 和函数外部的变量没有关联 (如列表 是可以修改值 但是重新赋值一个新的列表 那么就为一个新的变量)

    num = 10
    def ChangeInt():
        num = 20
        print(num) # 20 此刻num为局部变量 一个新的变量 和函数外部的变量没有关联
    ChangeInt()
    print(num)  # 10
    

    实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它

  • 引用传递:传递可变数据类型

    可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了

    #!/usr/bin/python3
     
    # 可写函数说明
    def changeme( mylist ):
       "修改传入的列表"
       mylist.append([1,2,3,4]);
       print ("函数内取值: ", mylist)
       return
     
    # 调用changeme函数
    mylist = [10,20,30];
    changeme( mylist );
    print ("函数外取值: ", mylist)
    

    传入函数的和在末尾添加新内容的对象用的是同一个引用。故输出结果如下:

    函数内取值:  [10, 20, 30, [1, 2, 3, 4]]
    函数外取值:  [10, 20, 30, [1, 2, 3, 4]]
    

6、闭包

概念:又称闭包函数或者闭合函数,其实和前面讲的嵌套函数类似,不同之处在于,闭包中外部函数返回的不是一个具体的值,而是一个函数

优点:避免污染全局环境,这样就可以在函数体外使用函数体中定义的变量

缺点:数据会长期驻留在内存中,造成内存极大的浪费

注意:尽量避免使用闭包

  • 示例

    def func1():
        b = 2
        def func2():
            return b
        return func2
    
    f2 = func1()
    print(f2())
    
  • 参数延迟绑定(闭包的坑)

    def func():
        arr = []
        for i in range(3):
            def f():
                print("-----------", i)
            arr.append(f)
        return arr
    
    li = func()
    li[0]()
    li[1]()
    li[2]()
    

五、匿名函数

关键字 lambda

不在使用def语句这样标准的形式定义函数,而是使用lambda来创建匿名函数

格式:lambda [arg1[, arg2[, args,……]]]: exception

作用:作为参数传递,实现回调,简化代码

特点:

  1. lambda只是一个表达式,函数体比def简单的多

  2. lamdba主体是一个的表达,而不是代码块,仅能在lamdba表达式中封装有限的逻辑

  3. lamdba函数拥有自己的命名空间,且不能访问自有参数列表之外的或全局命名空间里的参数

  4. 虽然lamdba函数体看起来只有一行,却不同于C和C++的内联函数,后者的目的是调用小函数时不占用栈内存而增加效率

  5. lambda 只是一个表达式,函数体比 def 简单很多

  6. lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去

  7. lambda 函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。

实例

第一种
#定义匿名函数  返回a+b的值
func = lambda a,b:a+b
#调用匿名函数
func(1,2)

第二种 定义并调用
(lambda a,b:a+b)(100,200)

第三种 有参数有默认值
g= lambda x=0,y=0 : x**2+y**2
g(2,3)

第四种 没有参数的
a = 10
func = lambda:a
print(func())

第五种 嵌套
func = lambda:(lambda:print('lucky'))()

六、递归函数

概述:

递归函数就是自己调用自己的一个过程 当条件不满足则停止递归调用

1、递归阶乘

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

如果我们计算fact(5),可以根据函数定义看到计算过程如下:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

fact(1000)

2、递归求和

#输入一个数(大于等于1),求1+2+3+……+n的和
#普通写法
def sum1(n):
 sum = 0
 for x in range(1, n + 1):
     sum += x
     return sum
#递归写法
def sum2(n):
if n == 1:
    return 1
else:
    return n + sum2(n - 1)
res = sum2(5)

3、 递归进行值的输出

def demo(n):
    print(n)
    if n>1:
        demo(n-1)
    print(n)

4、递归的分解

def a(x):
    print(x)
    b(x-1)
    print(',',x)
def b(x):
    print(x)
    c(x-1)
    print(x)
def c(x):
    print(x)
    print(x)
a(3)

通过递归实现斐波那契数列

def recur_fibo(n):
   """递归函数
   输出斐波那契数列"""
   if n <= 1:
       return n
   else:
       return(recur_fibo(n-1) + recur_fibo(n-2))

# 获取用户输入
nterms = int(input("您要输出几项? "))

# 检查输入的数字是否正确
if nterms <= 0:
    print("输入正数")
else:
    print("斐波那契数列:")
    for i in range(nterms):
        print(recur_fibo(i))

七、高阶函数

1、map&reduce

  • 概述

    Python内建了map()和reduce()函数

  • map()函数

    原型:map(fn, lsd)

    参数:

      fn:是一个函数
    
      lsd:集合
    

    功能:将传输的函数fn依次作用到lsd集合中的每个元素,并把结果作为一个Iterator返回

    myList = [1,2,3,4,5]
    def my_func(arg):
       return arg**2
    res = map(my_func,myList)
    print(list(res))
    print(tuple(res))
    print(set(res))
    
    # 使用lambda表达式来实现
    res = map(lambda x:x**2,myList)
    print(list(res))
    
    # 使用内置数据类型转换函数实现
    res = map(int,['1','2','3','4','5'])
    res = map(bool,[0,'1','2','3','','4',(),{},'5'])
    print(list(res))
    
  • reduce()函数

    原型:reduce(fn, lsd)

    参数:
    fn:一个函数
    lsd:集合

    功能:传入的fn函数作用在lsd集合中,这个fn函数必须接收两个参数,reduce把结果继续和序列中的下一个元素做累积运算

    from functools import reduce
    # 进行值的累加
    def add(x,y):
        print(x,y)
        return x+y
    """
    1 2
    3 3
    6 4
    10 5
    15
    """
    my_list = [1,2,3,4,5]
    # res = reduce(add,my_list)
    # 使用lambda表达式实现
    res = reduce(lambda x,y:x+y,my_list)
    print(res)
    
  • reduce&map组合使用案例

    from functools import reduce
    
    #将字符串转成对应字面量数字
    def str2int(str):
        def fc(x, y):
            # print(x,'*',10,"+",y) #打印查看运行结果
            return x * 10 + y
        def fs(chr):
            return int(chr)
        return reduce(fc, map(fs, list(str)))
    
    a = str2int("12345")
    print(a)
    print(type(a))
    """
    12345
    1+2    1*10+2
    3+3     12*10+3
    6+4     123*10+4
    10+5    1234*10+5 12345
    """
    

    使用lambda表达式实现

    # 将字符串的 '12345'  变成整形的12345
    print(reduce(lambda x,y:x*10+y,list(map(lambda x:int(x),list('12345')))))
    

2、filter

原型:filter(fn, lsd)

参数:
fn:函数
lsd:集合
功能:用于过滤列表,把传入的fn函数依次作用在lsd集合中的每个元素上,然后根据返回True还是False决定是否保留该元素

li = [1,2,3,4,5,6,7,8,9,10]

# def func1(arr):
#     for x in arr:
#         if x % 2 == 0:
#             arr.remove(x)
# func1(li)


def func2(num):
    if num % 2 == 0:
        return False
    return True
res = filter(func2, li)
print(res)
print(list(res))
print(li)

删除列表中是空字符串的元素

li2 = ["a", "", "", "c", "    ", "  bsg  "]
def f(item):
    return item and item.strip()
res = filter(f, li2)
print(list(res))

3、sorted

  • 排序算法

    冒泡排序、快速排序、选择排序,计数器排序等

  • 效率

    在数据量小时冒泡和快速没有什么区别,但是当处理大量数据时快速排序的效率明显高于冒泡排序

  • 冒泡算法原理

    1 2 3 4 5 -> 5 4 3 2 1
    1 2 3 4 5 -> 2 1 3 4 5 -> 2 3 1 4 5 -> 2 3 4 1 5 -> 2 3 4 5 1
                 0 1          1 2          2 3          3 4
    2 3 4 5 1 -> 3 2 4 5 1 -> 3 4 2 5 1 -> 3 4 5 2 1
    3 4 5 2 1 -> 4 3 5 2 1 -> 4 5 3 2 1
    4 5 3 2 1 -> 5 4 3 2 1
    

    代码实现

    li = [1,2,3,4,5]
    for i in range(len(li) - 1):
        for j in range(len(li) - i - 1):
            if li[j] < li[j+1]:
                temp = li[j]
                li[j] = li[j+1]
                li[j+1] = temp
    print(li)
    
  • sorted()函数

    原型:sorted(lsd, key=func)

    参数:
    lsd:集合
    key:规定排序的规则

    功能:将led中的每个元素作用在key函数上,用函数的结果的大小来排序
    优点:可以自定义排序规则

    def func(x):
        return ord(x[2])
    li2 = ["awaefweg","sgeweg3","sgwrtwe","wr2","ergstrhrt"]
    li3 = sorted(li2, key=len, reverse=True)
    li3 = sorted(li2, key=func, reverse=True)
    print(li2)
    print(li3)
    #按绝对值大小排序
    list3 = [4,-7,2,6,-3]
    #key接受函数来实现自定义排序规则
    list4 = sorted(list3, key=abs)
    

八、装饰器

1、概念

装饰器: 装饰器本质上是一个Python函数,其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值(return)也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

在代码运行期间动态的增加功能 称之为装饰器

2、 函数回顾

  • 函数普通调用

    def say():
        return "hi lucky" 
     
    print(say())
     
    # 我们甚至可以将一个函数赋值给一个变量,比如
    speak = say
    # 我们这里没有在使用小括号,因为我们并不是在调用hi函数
    # 而是在将它放在say变量里头。我们尝试运行下这个
     
    print(speak())
    
  • 在函数中定义函数

    def demo(name="lucky"):
        print("now you are inside the hi() function")
     
        def inner1():
            return "now you are in the inner1() function"
     
        def inner2():
            return "now you are in the inner2() function"
     
        print(inner1())
        print(inner2())
        print("now you are back in the demo() function")
    demo()
    

    上面展示了无论何时你调用hi(), greet()和welcome()将会同时被调用。

    然后greet()和welcome()函数在hi()函数之外是不能访问的,比如:

  • 函数内调用外部函数

    def demo(x):
        print('我是demo函数')
        x()
    
    def func():
        print('我是func函数')
    
    #在其它语言里 称为 回调函数  也就是传进去的参数是一个函数名
    demo(func)
    

3、无参装饰器

  • 在lucky is a good man 上下分别实现输出一句话

    def wrapper(fun):
        def inner():
            print('-----------')
            a = fun()
            print('-----------')
            return a
        return b
    
    def say():
        print('lucky is a good man')
        return 1
    
    inner = wrapper(say)
    print(inner())
    

    使用@符号装饰

    python2.4开始支持使用@符号将装饰器应用到函数上,只需要在函数定义时加上"@装饰器名称"即可完成装饰操作

    def wrapper(fun):
        def inner():
            print('-----------')
            a = fun()
            print('-----------')
            return fun
        return inner
    @wrapper
    def say():
        print('lucky is a good man')
        return 1
    say()
    
  • 日志打印

    def foo():
        print('i am foo')
    
     
    def foo():
        print('i am foo')
        logging.info("foo is running")
    

    如果其它函数也有类似的需求,怎么做?再写一个logging在bar函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代

    def use_logging(func):
        logging.warning("%s is running" % func.__name__)
        func()
     
    def bar():
        print('i am bar')
     
    use_logging(bar)
    

    逻 辑上不难理解, 但是这样的话,我们每次都要将一个函数作为参数传递给use_logging函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行 运行bar(),但是现在不得不改成use_logging(bar)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

    import logging
    
    
    def use_logging(func):
        def inner():
            func()
            logging.warning("%s is running" % func.__name__)
        return inner
    
    @use_logging
    def bar():
        print('i am bar')
    
    @use_logging
    def bar2():
        print('i am bar2')
    
    # use_logging(bar)
    # use_logging(bar2)
    
    bar()
    bar2()
    
  • 统计函数执行次数案例

    def wrapper(f):
        count = 1
        def inner(*args, **kwargs):
            nonlocal count
            print("第%d次执行"%(count))
            res = f(*args, **kwargs)
            count += 1
            return res
        return inner
    @wrapper
    def say():
        print("lucky is a good man")
    
    say()
    say()
    say()
    
  • 统计函数运行时间案例

    import time
    
    def wrapper(f):
        def inner(*args, **kwargs):
            t1 = time.time() #获取当前时间的时间戳
            res = f()
            t2 = time.time()
            print("耗时%.2f秒"%(t2-t1))
            return res
        return inner
    
    @wrapper
    def say():
        print("lucky is a good man")
        # 休息2秒钟,可以是浮点数
        time.sleep(2)
        print("lucky is a nice man")
    
    say()
    

4、带参数的装饰器

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')

执行结果如下:

>>> now()
execute now():
2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)

我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'

>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

或者针对带参数的decorator:

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

九、偏函数

1、int()

int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

>>> int('12345')
12345

int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

2、思考

大量进行二进制转换

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

def int2(x, base=2):
    return int(x, base)

这样,我们转换二进制就非常方便了:

>>> int2('1000000')
64
>>> int2('1010101')
85

3、偏函数实现

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

>>> import functools
>>> int2 = functools.partial(int, base=2)   [ˈpɑ:rʃl
>>> int2('1000000')
64
>>> int2('1010101')
85

所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:

>>> int2('1000000', base=10)
1000000

最后,创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数,当传入:

int2 = functools.partial(int, base=2)

实际上固定了int()函数的关键字参数base,也就是:

int2('10010')

相当于:

kw = { 'base': 2 }
int('10010', **kw)

当传入:

max2 = functools.partial(max, 10)

实际上会把10作为*args的一部分自动加到左边,也就是:

max2(5, 6, 7)

相当于:

args = (10, 5, 6, 7)
max(*args)

结果为10

小结

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容