Python 装饰器

庆云10个月前技术文章206

1、闭包

自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量。


闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是 JavaScript。


实例:

# python 2 实现闭包
def counter():
    c = [0]
    def inc():
        c[0] += 1 # 是赋值即定义嘛?不是!是修改值
        return c[0]
    return inc    # 返回标识符,即函数对象

m = counter()
m()               # 调用函数 inc(),但是 c 消亡了嘛?没有,内层函数没有消亡,c 不消亡(闭包)
m()
m()
print(m())


# 不推荐使用 global!
def counter():
    global c
    c = 0
    def inc():
        global c
        c += 1        # 不是闭包!
        return c
    return inc

m = counter()
m()
m()
m()
print(m())


# 推荐使用 nonlocal,python3 实现闭包
def counter():
    c = 0
    def inc():
        nonlocal c    # 非当前函数的本地变量,当前函数之外的任意层函数的变量,绝非 global
        c += 1        # 是闭包吗?是!
        return c
    return inc

m = counter()
m()
m()
m()
print(m())


nonlocal 语句: 将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局作用域中。



2、柯里化

指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程,新的函数返回一个以原有第二个参数为参数的函数。


实例: z = f(x, y) 转换成 z = f(x)(y) 的形式.

def add(x, y):
    return x + y
print(add(4, 5))

# 柯里化
def add1(x):         # 步骤1:先定义一个函数(一参)
    def func(y):     # 步骤3:定义返回函数(一参)
        return x + y
    return func      # 步骤2:返回函数

fn = add1(4)         # 返回函数
print(fn(5))         # 9
print(add1(4)(5))    # 9


建议: 逆推!原来函数调用为 add(4, 5),柯里化目标是 add(4)(5),两个括号两次函数调用,第一次函数调用返回仍然是函数。


add 函数再拓展:

# 已知函数 add(x, y, z) 求柯里化后,使其可以如下 add(4)(5, 6) 的形式:
def add(x):
    def func(y, z):
        return x + y + z
    return func
print(add(4)(5, 6))    # 15


# 已知函数 add(x, y, z) 求柯里化后,使其可以如下 add(4)(5)(6) 的形式:
def add(x):
    def func(y):
        def func1(z):
            return x + y + z
        return func1
    return func
print(add(4)(5)(6))    # 15


# 已知函数 add(x, y, z) 求柯里化后,使其可以如下 add(4, 5)(6) 的形式:
def add(x, y):
    def func(z):
        return x + y + z
    return func
print(add(4, 5)(6))    # 15



3、日志记录实现

3.1 日志功能分析


现有一个加法函数:

def add(x, y):
  return x + y


想增强它的功能,能够输出加法函数的日志信息:

def add(x, y):
  print("call add,x + y") #日志输出到控制台
  return x + y


上面的加法函数是完成了需求,但是有以下的缺点:

  • 打印日志信息是一个功能,这条语句和 add 函数耦合太高;

  • 加法函数属于业务功能,而输出日志信息的功能,属于非业务功能代码,不该放在业务函数 add 中。

3.2 业务功能分离


下面代码做到了业务功能分离,但是 fn 函数调用传参是个问题:

def add(x, y):
    return x + y

def logger(fn):
    print('before')
    print('add function:{} {}'.format(4, 5))
    ret = fn(4, 5)
    print('after')
    return ret

print(logger(add))


为了解决传参的问题,进一步改变:

def add(x, y):
    return x + y

def logger(fn, *args, **kwargs):
    print('before')
    print('add function:{} | {}'.format(args, kwargs))
    ret = fn(*args, **kwargs)    # 参数解构
    print('after')
    return ret

print(logger(add, 4, 5))
print(logger(add, x=4, y=6))
print(logger(add, 4, y=15))


柯里化:

def add(x, y):
    return x + y

def logger(fn):
    def wrapper(*args, **kwargs):
        print('before')
        print('add function:{} | {}'.format(args, kwargs))
        ret = fn(*args, **kwargs)    # 参数解构
        print('after')
        return ret
    return wrapper

print(logger(add)(4, 5))
# 换一种写法:
'''
add = logger(add)
print(add(x=5, y=10))
'''
# 1.先算等式右边,logger(add)
# 2.add函数对象被fn记住
# 3.logger函数返回 wrapper,即add = wrapper
# 4.调用add(4, 5)即wrapper(4, 5)
# 5.ret = fn(*args, **kwargs),此处的fn记住的是最原始的add函数对象

3.3 装饰器语法糖

def logger(fn):
    def wrapper(*args, **kwargs):
        print('before')
        print('add function:{} | {}'.format(args, kwargs))
        ret = fn(*args, **kwargs)    # 参数解构
        print('after')
        return ret
    return wrapper

@logger    # 等价于add = logger(add)
def add(x, y):
    return x + y

print(add(20, 30))
'''
可以采用逆推思维:
1.add(20, 30)
2.包装add(20, 30)为:wrapper(20, 30)
3.实现日志增强功能:logger(add)(20, 30)
即:add(20, 30) => wrapper(20, 30) => logger(add)(20, 30)

def logger(fn):
  def wrapper(*args, **kwargs):
    val = fn(*args, **kwargs)
    return val
  return wrapper
@logger
def add(x, y):
  return x + y
'''


@logger是什么?这就是装饰器语法


装饰器

  • 它是一个函数,函数作为它的形参,无参装饰器实际上就是一个单形参函数

  • 返回值也是一个函数


3.4 装饰器和高阶函数


装饰器可以是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)


'''
设定logger()增强日志功能:
1、增强谁,就将谁作为参数传入:即logger(add)
add = logger(add)
add(4, 5) = logger(add)(4, 5) —>柯里化wrapper(4, 5)

2、def logger(fn):
    def wrapper(*args, **kwargs):
        print("before")
          ret = fn(*args, **kwargs)
          print("alter")
      return wrapper
@logger    # add(4, 5) = logger(add)(4, 5)
add(4, 5)
'''

import datetime
import time
def logger(fn):
    def wrapper(*args, **kwargs):
        print('begin to work')
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)    # 参数解构
        stop = (datetime.datetime.now() - start).total_seconds()
        print('{} took {:.2f}s.'.format(fn.__name__, stop))
        return ret
    return wrapper

@logger    # 等价于add = logger(add)
def add(x, y):
    time.sleep(2)
    return x + y

print(add(20, 30))


总结: 怎么理解装饰器?非侵入式装饰



4、文档字符串


python 文档字符串 Documentation Strings,在函数语句块的第一行,且习惯是多行的文本,多使用三引号。


惯例是首字母大写,第一行写概述,空一行,第三行写详细描述。


可以使用特殊属性 __doc__ 访问这个文档字符串。


实例:

def add(x,y):
  """This is a function of addition"""
  return x + y
  
print("function name: {}\nfunction doc: {}".format(add.__name__, add.__doc__))
print(help(add))

# 输出结果:
function name: add
function doc: This is a function of addition
Help on function add in module __main__:

add(x, y)
    This is a function of addition

4.1 日志增强业务

import datetime
import time
def logger(fn):
    def wrapper(*args, **kwargs):
        """this is an function of wrapper"""
        print("begin to work...")
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        print("function {},took {:.2f}s.".format(fn.__name__, delta))
        return ret
    return wrapper

@logger
def add(x, y):
    """this is an function of add"""
    time.sleep(2)
    return x + y

print(add.__name__, add.__doc__)

# 执行结果:
wrapper this is an function of wrapper


@logger 装饰器语法,即:add = logger(add) = wrapper,所以此处的 add.__name__, add.__doc__ 为 wrapper 增强函数的函数名及文档字符串。

4.2 赋值更改文档字符串

import datetime
import time
def logger(fn):
    def wrapper(*args, **kwargs):
        """this is an function of wrapper"""
        print("begin to work...")
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        print("function {},took {:.2f}s.".format(fn.__name__, delta))
        return ret
    wrapper.__name__ = fn.__name__    # 赋值更改
    wrapper.__doc__ = fn.__doc__
    return wrapper

@logger
def add(x, y):
    """this is an function of add"""
    time.sleep(2)
    return x + y

print(add.__name__, add.__doc__)

# 输出结果:
add this is an function of add

4.3 函数调用实现

import datetime
import time
def copy_properties(src, dest):   # 函数实现
    dest.__name__ = src.__name__
    dest.__doc__ = src.__doc__

def logger(fn):
    def wrapper(*args, **kwargs):
        """this is an function of wrapper"""
        print("begin to work...")
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        print("function {},took {:.2f}s.".format(fn.__name__, delta))
        return ret
    copy_properties(fn, wrapper)
    return wrapper

@logger
def add(x, y):
    """this is an function of add"""
    time.sleep(2)
    return x + y

print(add.__name__, add.__doc__)

4.4 柯里化再改进

import datetime
import time
def copy_properties(src):    # 柯里化
    def _copy(dest):
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
    return _copy

def logger(fn):
    def wrapper(*args, **kwargs):
        """this is an function of wrapper"""
        print("begin to work...")
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        print("function {},took {:.2f}s.".format(fn.__name__, delta))
        return ret
    copy_properties(fn)(wrapper)    # 柯里化调用
    return wrapper

@logger
def add(x, y):
    """this is an function of add"""
    time.sleep(2)
    return x + y

print(add.__name__, add.__doc__)

4.5 装饰器语法再改进

import datetime
import time
def copy_properties(src):    # fn
    def _copy(dest):         # wrapper
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest    # 有下面返回None分析得,此处缺少返回值
    return _copy

def logger(fn):
    # 带参装饰器
    @copy_properties(fn)    # wrapper = copy_properties(fn)(wrapper) ——> 返回 None
    def wrapper(*args, **kwargs):
        """this is an function of wrapper"""
        print("begin to work...")
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.datetime.now() - start).total_seconds()
        print("function {},took {:.2f}s.".format(fn.__name__, delta))
        return ret
    #copy_properties(fn)(wrapper)
    return wrapper

@logger
def add(x, y):
    """this is an function of add"""
    time.sleep(2)
    return x + y

print(add.__name__, add.__doc__)


通过 copy_properties 函数将被包装函数的属性覆盖掉包装函数,凡是被装饰的函数都需要复制这些属性,这个函数很通用,可以将复制属性的函数构建成装饰器函数,带参装饰器。


带参装饰器:

  • 它是一个函数,函数作为它的形参,返回值是一个不带参的装饰器函数

  • 使用 @functionname(参数列表) 方式调用

  • 可以看做在装饰器外层又加了一层函数,这个函数可以多参数



相关文章

Nexus 制品管理平台

Nexus 制品管理平台

Nexus 官网:https://www.sonatype.com/nexus-repository-ossNexus 是一个很强大的私服软件,不仅仅是作为 Java 的 Maven 打包使用,同样的...

PG初识

PG数据库是一种典型的C/S模型应用,不同的客户端通过TCP/IP进行连接、每个连接启动一个fork进程(多进程数据库)。一、pg逻辑架构1.1 pg与MySQL异同对比逻辑架构postgresMyS...

MySQL 8.0 新特性:Persisted System Variables

MySQL 8.0 新特性:Persisted System Variables

一、前言MySQL 5.7 之前我们修改参数变量后,需要将其手动写入到服务端配置文件中,否则重启后又恢复原有的配置,在 8.0 中可以在 MySQL 客户端直接将参数持久化,节省在服务器操作的步骤,下...

Ldap部署

安装ldapyum -y install openldap compat-openldap openldap-clients openldap-servers openldap-servers-sql...

Redis 持久化机制 RDB

Redis 持久化机制 RDB

前言Redis 有两种持久化机制,分别是 RDB 与 AOF 本篇文章将介绍 RDB 的执行过程与应用。1. RDB 简介RDB 持久化是把当前进程数据生成快照保存到硬盘的过程,触发 RDB 持久化过...

JMS 介绍

JMS 介绍

一、JMS的基础JMS是什么:JMS是Java提供的一套技术规范JMS干什么用:用来异构系统 集成通信,缓解系统瓶颈,提高系统的伸缩性增强系统用户体验,使得系统模块化和组件化变得可行并更加灵活通过什么...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。