Python 装饰器

庆云1年前技术文章424

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

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



相关文章

flink sql 批处理

进入flink sql命令行sql-client.shSource 表        与所有 SQL 引擎一样,Flink 查询操作是在表上进行。与传统数据库不同,Flink 不在本地管理静态数据;相...

PG体系结构(二)

PG体系结构(二)

二、逻辑架构graph TD     A[database] -->B(schema)     B -->C[表]     B -->D[视图]     B -->E[...

ACK版本升级

ACK版本升级

需求:将ACK版本从1.14.8升级至1.16.9升级前注意事项:集群升级需要机器可以公网访问,以便下载升级所需的软件包。集群升级Kubernetes过程中,可能会有升级失败的情况,为了您的数据安全,...

大数据集群部署规划(三)节点选配方案

节点部署原则适用场景组网规则管理节点、控制节点和数据节点分开部署(此方案至少需要8个节点,manager为部署商业化大数据集群时所需例如:hdp,cdh等)core × 11 + worker × n...

apache Hbase2.x  使用hbck2修复工具

apache Hbase2.x 使用hbck2修复工具

1、背景默认情况下apache hbase 使用hbck2时,无法使用-j 来加载hbck2的jar包,无法进行修复2、解决办法是由于默认情况下只使用自带的hbase hbck修复命令,大部分功能在2...

Kubernetes原理分析--Kube-controller list&watch原理解析

Kubernetes原理分析--Kube-controller list&watch原理解析

1.list&watch流程:这里有三个 List-Watch,分别是 Controller Manager(运行在 Master),Scheduler(运行在Master),kubelet(...

发表评论    

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