一、闭包

元组字典解包

def func(*args, **kwargs):
    print(type(args))   # <class 'tuple'>
    print(type(kwargs)) # <class 'dict'>

    # *args: tuple解包成位置参数
    # **kwargs: dict解包成关键字参数
    print(*args, **kwargs)  # 1-2-3.

func(1, 2, 3, sep="-", end=".")

嵌套函数

函数定义可以嵌套函数,这在其它语言中也经常见到,但是可以嵌套多层函数还是很少见到。

def function():
    print("function")

    def func():
        print("func")

        def fun():
            print("fun")
        return fun
    return func

function()()()

闭包

闭包的构成条件:有嵌套+有引用+有返回:

  • 有嵌套:在函数嵌套(函数里面再定义函数)的前提下;
  • 有引用:内部函数使用了外部函数的变量(还包括外部函数的参数);
  • 有返回:外部函数返回了内部函数名。

闭包是用来延长函数内变量的声明周期。闭包可以保存函数内的变量,而不会随着调用完函数而被销毁。

在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,这种使用外部函数变量的内部函数称为闭包。

#外部函数
def 外部函数名(外部参数):
	#内部函数
	def 内部函数名(内部参数):
		...[使用外部函数的变量]
	return 内部函数名			#闭包
def func_outer(num1):
	def func_inner(num2):		#1 在函数嵌套(函数里面再定义函数)的前提下
		sum =num1+ num2				#2 内部函数使用了外部函数的变量(还包括外部函数的参数)
		print("现在的值:",sum)	
	return func_inner			  #3 外部函数返回了内部函数


f=func_outer(10)	#执行闭包
f(1)

nonlocal:声明能够让内部函数去修改外部函数的变量

def func_outer():
    a = 100

    def func_inner():
        # 注意要声明
        nonlocal a
        a = a + 1
        print(f"修改的结果:{a}")

    return func_inner

inner = func_outer()
# 修改的结果:101
inner()
# 修改的结果:102
inner()

二、装饰器

装饰器的作用是不改变原有函数的基础上,给原有函数增加额外功能。
装饰器本质上就是一个闭包函数

装饰器的构成条件:

  • 有嵌套:在函数嵌套(函数里面再定义函数)的前提下;
  • 有引用:内部函数使用了外部函数的变量(还包括外部函数的参数);
  • 有返回:外部函数返回了内部函数名。
  • 有额外功能:给需要装饰的原有函数增加额外功能。

装饰器是一个特殊的函数或者类,用于包装目标函数,可以在执行目标函数前或者执行目标函数后执行装饰器函数。装饰器的底层就是闭包。 和Java中的面向切面编程AOP作用类似,在使用上和Java中的注解类似以@开头。在目标函数定义的上方通过@符号+装饰器函数名来使用装饰器。

装饰器是一个特殊的函数:

  • 装饰器函数接收一个函数类型参数,这个函数类型参数就是我们要执行的目标函数对象;
  • 装饰器函数的返回值是一个包装函数,包装函数就是一个普通的函数,接收两个参数,一个是可变长度参数*args,另一个是命名关键字参数 **kwargs,包装器的函数的名字一般叫wrapper,如果不是wrapper最好通过@functools.wraps(func)来注解一下(最新版本的没有此限制)。包装函数内部调用目标函数,此时可以在调用目标函数前或者调用目标函数后加一些代码,这样就达到在目标函数前后执行一些代码。

3.1 函数实现

3.1.1 闭包实现

本质就是函数的参数类型是函数类型function

def outer(func):
    def inner():
        print('我要开始睡觉了')
        func()
        print('我睡醒了')
    return inner


def sleep():
    print('睡觉中zZZZ')

# 先获取内部函数返回值
innter = outer(sleep)
"""
我要开始睡觉了
睡觉中zZZZ
我睡醒了
"""
# 再调用内部函数
innter()

内部函数 = 外部函数(原有函数)
内部函数()

3.1.2 基础装饰器(目标函数无参无返回值)

def outer(func):
    def inner():
        print('我要开始睡觉了')
        func()
        print('我睡醒了')
    return inner


@outer
def sleep():
    print('睡觉中zZZZ')

"""
我要开始睡觉了
睡觉中zZZZ
我睡醒了
"""
# 一步到位:就像调普通函数一样,只需调用一次
sleep()

装饰器和闭包的唯一区别就是在调用方式上,闭包是调用的外部函数,而装饰器是直接调用目标函数的。

先执行目标函数上的装饰器,再执行目标函数。

3.1.3 打印函数耗时(目标函数有参无返回值)

import datetime
import time

def printtime(func):
    """方法耗时"""
    def wrapper(*args, **kwargs):
        print("开始计算耗时")
        begin = datetime.datetime.now().timestamp()
        func(*args, **kwargs)
        end = datetime.datetime.now().timestamp()
        print("结束计算耗时")
        print(f'结束计算耗时:方法耗时{end - begin}秒')
    return wrapper

@printtime
def sleep(second):
    time.sleep(second)
    print(f'sleep {second}s ...')

"""
开始计算耗时
sleep 2s ...
结束计算耗时
结束计算耗时:方法耗时2.0054750442504883秒
"""
sleep(2)

3.1.4 有参实现

装饰器传参: 需要在装饰器函数中嵌套两层函数,包装器函数最外侧接收包装器参数,函数名一般命名为decorator

  • 最外层函数:接收装饰器的参数
  • 中间层函数(装饰器函数):接收目标函数参数 func(装饰器函数只能最多有一个func参数)
  • 最内层函数:接收目标函数对应的参数为位置参数和关键字参数(*args, **kwargs)
import datetime
import time


def printtime(echo):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if echo:
                print("开始计算耗时")
            begin = datetime.datetime.now().timestamp()
            func(*args, **kwargs)
            end = datetime.datetime.now().timestamp()
            if echo:
                print("结束计算耗时")
            print(f'结束计算耗时:方法耗时{end - begin}秒')
        return wrapper
    return decorator


@printtime(echo=False)
def sleep(second):
    time.sleep(second)
    print(f'sleep {second}s ...')

"""
sleep 2s ...
结束计算耗时:方法耗时2.003556966781616秒
"""
sleep(2)

3.1.4 同时使用多个装饰器

多个装饰器的装饰过程是:离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程。
但从语法糖表面现象看是装饰器从上往下执行。

def before(func):
    def wrapper():
        print('我要开始睡觉了')
        func()
    return wrapper

def after(func):
    def wrapper():
        func()
        print('我睡醒了')
    return wrapper

闭包方式调用。

after_inner = after(sleep)
before_inner = before(after_inner)
"""
我要开始睡觉了
睡觉中zZZZ
我睡醒了
"""
before_inner()

语法糖方式调用。

@before
@after
def sleep():
    print('睡觉中zZZZ')

"""
我要开始睡觉了
睡觉中zZZZ
我睡醒了
"""
sleep()

3.2 类实现

装饰器可以用函数实现,也可以用类实现,

  • __init__构造器接收目标函数对象,
  • __call__函数来调用目标函数。

3.2.1 无参实现

import datetime
import time


class Printtime:
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        # '__':表示私有方法
        begin = datetime.datetime.now().timestamp()
        self._func()
        end = datetime.datetime.now().timestamp()
        print(f'方法{self._func.__name__}耗时{end - begin}秒')


@Printtime
def test():
    time.sleep(2)
    print('test wrapper: printtime')


test()

3.2.2 有参实现

  • __init__构造器接收装饰器参数(如msg)
  • __call__函数来接收目标函数,并调用装饰函数。
  • __callback装饰函数。
import datetime
import time


class Printtime:
    def __init__(self, msg):
        self.msg = msg

    def __call__(self, func):
        # _func的名字是固定的
        self._func = func
        return self.__callback

    def __callback(self, *args, **kwargs):
        # '__':表示私有方法
        begin = datetime.datetime.now().timestamp()
        self._func()
        end = datetime.datetime.now().timestamp()
        print(f'{self.msg}:方法{self._func.__name__}耗时{end - begin}秒')


@Printtime(msg='参数')
def test():
    time.sleep(2)
    print('test wrapper: printtime')


test()

3.3 Python常用的装饰器

  1. @unique :用于检查枚举中的值是否重复,重复运行的时候会报错。
  2. @property :用于getter方法,调用该方法就像获取属性一样。通过对象.方法名一样来调用方法。
  3. @方法名.setter: 用于将setter方法包装成可以通过像属性赋值一样使用,使用给属性赋值的语法来调用setter方法。
  4. @staticmethod :静态方法调用,通过类名.方法名来调用,也支持通过对象名.方法名来调用。使用@staticmethod的方法的第一个参数不需要self。
  5. @functools.wraps(func):函数对象有一个__name__属性,可以拿到函数的名字。当使用包装器当将一个函数赋值给一个变量时,通过该变量获取函数的名字__name__属性时是获取到包装器函数的名字(一般习惯写成wrapper), 此时是不对的,解决办法可以在包装器内修改函数的__name__属性wrapper.__name__ = func.__name__,或者使用系统自带的装饰器@functools.wraps(func)来修改函数名。
from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6


day1 = Weekday.Sat
print(day1.value)
class Student:
    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100')
        self._age = value
class Foobar:
    @staticmethod
    def foo():
        print("foo")

# 通过类名.方法名调用
Foobar.foo()

# 也支持对象.方法名调用
foobar = Foobar()
foobar.foo()
class Outer:
    @staticmethod
    class Inner:
        def test(self):
            print("test")

Outer.Inner().test()
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐