Python 装饰器

庆云1年前技术文章334

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(参数列表) 方式调用

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



相关文章

ES运维(二)字段类型与内存管理

ES运维(二)字段类型与内存管理

一、ES常见字段类型1、 概述字段是数据存储的最小微粒,根据数据的性质不同将数据分成不同的字段类型,熟悉不同字段类型的特性,对索引的Mapping设计、查询调优都极其重要。2、 关键参数In...

企业级大数据安全架构(十)

企业级大数据安全架构(十)

一、DBeaver连接Kerberos认证下的hive1.配置本地hosts因为Kerberos认证过程及集群服务中,很多是以主机名的形式进行访问的,所以工作机要设置hosts. 域名映射,我们通过部...

可持续集成工具

可持续集成工具

持续集成中常用的 Jenkins 替代方案。1BuildMaster项目地址:https://inedo.com/buildmasterInedo 的 BuildMaster 是 Jenkins 替代...

MySQL运维实战之备份和恢复(8.8)恢复单表

xtrabackup支持单表恢复。如果一个表使用了独立表空间(innodb_file_per_table=1),就可以单独恢复这个表。1、Prepareprepare时带上参数--export,xtr...

压测实操--produce压测方案

压测实操--produce压测方案

环境信息:操作系统centos7.9,kafka版本为hdp集群中的2.0版本。 Producer相关参数使用Kafka自带的kafka-producer-perf-test.sh脚本进行压测,该脚本...

HBase HBCK运维指南

HBase HBCK运维指南

HBase HBCK是HBase运维人员经常会用到的一个HBase运维工具,主要是用于检查 HBase region等元数据一致性以及修复的工具。目前HBCK工具有两个版本,本次主要介绍用于HBase...

发表评论    

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