浅谈Python中的装饰器

本文深入解析Python装饰器的原理及应用,包括闭包、柯里化等基础概念,以及如何使用装饰器进行函数功能增强,避免代码耦合,实现日志记录等功能。同时探讨了装饰器的副作用及解决方案。

Python的装饰器,是Python函数(或者类)功能增强的一种方式。在了解装饰器之前,有必要先了解两个概念:闭包和柯里化。

闭包

Python的装饰器,实际上的闭包的应用。了解闭包是什么及其特性,请浏览这篇文章《理解Python闭包概念》

柯里化

所谓的柯里化,先来看看它在维基百科上的解释:

柯里化,英语:Currying,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

例子如下:

# add 函数,接收两个参数并返回和
def add(x,y):
    return x + y


# 将add函数柯里化
def add(x):
    def _add(y):
        return x + y
    return _add

# 调用:
t = add(4)
t(5) # 输出 9

通过嵌套函数,把函数柯里化了。柯里化函数,其实就是闭包的应用。或者反过来说也成立,利用柯里化机制的函数,就是闭包函数。二者相辅相成。

了解了柯里化和闭包的概念,我们就可以来看看装饰器的概念了。

装饰器

Python的装饰器是为函数进行功能增强的一种方式。试想下有这么一个需求:

实际业务中,某些关键性函数必须增加打印日志的功能。

为了降低代码的耦合性,打印功能不应该糅合到其他业务功能的代码中,而是作为一个增强性的功能而存在。此时,就不应该侵入到指定的函数增加输出日志的代码,而应该把它抽象出来,作为一个功能增强的装饰而存在。

我们知道,为了实现代码重用,可以把代码抽象为函数的形式。那装饰器是不是也是函数?是的。

本质上,装饰器就是一个函数(或者类),它接收函数作为参数,并返回另一个函数对象。因此,装饰器是一种高阶函数,它实现了对传入函数的功能的增强(装饰)。

无参装饰器

下面来看看例子,是如何使用装饰器:

需求:一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息。

# 原函数
def add(x,y):
	return x + y
	
# 增加信息输出功能 v1
def add(x,y):
	print("call add, x + y") # 日志输出到控制台
	return x + y

上述的v1版加法函数满足了需求,但存在以下缺点:

  • 打印语句的耦合度太高
  • 加法函数属于业务功能,而输出信息的功能则属于非业务功能代码,不应该放到业务函数中。否则改变了业务功能函数的属性。

我们只需要,在调用add函数前,把打印语句先打印出来就可以了。比如:

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

def logger(fn,x,y):
    print('args: {}.{}'.format(x,y))
    ret = fn(x,y)
    print('call finished!')
    return ret

print(logger(add,4,5))

更加通用的实现:

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

def logger(fn,*args,**kwargs): # 这里存在多个参数,可以做柯里化
    print('args: {},{}'.format(*args,*kwargs))
    ret = fn(*args,**kwargs)
    print('call finished!')
    return ret

print(logger(add,4,5))

对logger柯里化:

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

def logger(fn):
    def _logger(*args,**kwargs):
        print('args: {},{}'.format(*args, *kwargs))
        ret = fn(*args,**kwargs)
        return ret
    print('call finished!')
    return _logger


print(logger(add)(4,5))

注意:logger(add)返回的是_logger,而_logger函数返回的是fn,也就是add的调用结果。因此,实际上_logger函数指向了新的add函数。也就是说logger(add) -> _logger -> add

因此,可以如此表示add = logger(add),使用语法糖@,变成了以下形式:

def logger(fn):
    def _logger(*args,**kwargs):
        print('args: {},{}'.format(*args, *kwargs))
        ret = fn(*args,**kwargs)
        return ret
    print('call finished!')
    return _logger

@logger  # 语法糖,用于装饰器函数。它相当于执行了add = logger(add)
def add(x,y):
    return x + y

print(add(4,5))

业务函数好比一幅画,而装饰器则比作画外部的画框,以实现不同的装饰。比如,前置功能增强或者后置功能增强。

装饰器的本质是使用非侵入式代码进行功能增强。当然,此功能即使不使用,也不应该影响原来的业务功能。

副作用

经过logger装饰的add函数,实际上已经不是原来的add函数了,它已经变成了装饰器函数。因此带来一些副作用。

看下面的例子:

def logger(fn):
    def wrapper(*args,**kwargs):
        'I am wrapper'
        print('args: {},{}'.format(*args, *kwargs))
        ret = fn(*args,**kwargs)
        return ret
    print('call finished!')
    return wrapper

@logger
def add(x,y):
    'This is a function for add'
    return x + y

print("name={},doc={}".format(add.__name__,add.__doc__))	

输出:

call finished!
name=wrapper,doc=I am wrapper 

add函数的文档字符串改变了。原对象的属性都被替换了。实际上,我们关心的是被包装函数的属性(原add函数),而无关装饰器。

因此,需要考虑将需要的属性,从被包装函数,覆盖到包装函数中。

def copy_properties(src,dest):
    dest.__name__ = src.__name__
    dest.__doc__ = src.__doc__
    # ... 还有许多属性

def logger(fn):
    def wrapper(*args,**kwargs):
        'I am wrapper'
        print('args: {},{}'.format(*args, *kwargs))
        ret = fn(*args,**kwargs)
        return ret
    copy_properties(fn,wrapper)
    print('call finished!')
    return wrapper

@logger
def add(x,y):
    'This is a function for add'
    return x + y

print("name={},doc={}".format(add.__name__,add.__doc__))

输出:

call finished!
name=add,doc=This is a function for add

我们应该知道,上述的copy_properties函数,实际上就是wrapper函数功能的增强,那是否可以把它改造成装饰器?

带参装饰器

def copy_properties(src):
    def _inner(dest):
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest
    return _inner

def logger(fn):
    @copy_properties(fn)  # wrapper = copy_properties(fn)(wrapper)
    def wrapper(*args,**kwargs):
        'I am wrapper'
        print('args: {},{}'.format(*args, *kwargs))
        ret = fn(*args,**kwargs)
        return ret
    print('call finished!')
    return wrapper

@logger
def add(x,y):
    'This is a function for add'
    return x + y

print("name={},doc={}".format(add.__name__,add.__doc__))

因为装饰器携带参数,因此成为带参装饰器。上述的例子,是一个应用在装饰器里的装饰器。

functools模块

functools.update_wrapper

上述带参装饰器的例子中,写一个copy_properties函数来指定原函数需要传递的属性值,这种方式不够优雅。functools.update_wrapper正好解决了这个问题。

先看看它的构造函数:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    for attr in assigned: # 以元组的形式
        try:
            value = getattr(wrapped, attr) # 相当于value = wrapped[attr]
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value) # 把原属性值,追加到wrapper. wrapper[attr] = value
    for attr in updated: # 以字典的形式,attr为key
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    wrapper.__wrapped__ = wrapped
    return wrapper
  • wrapper是包装函数,wrapped是被包装函数
  • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性
  • 元组WRAPPER_UPDATES是要被更新的属性,__dict__属性字典
  • 增加一个__wrapped__属性,保留着wrapped被包装函数

因此,可以如此使用它:

import functools

def logger(fn):

    def wrapper(*args,**kwargs):
        'I am wrapper'
        print('args: {},{}'.format(*args, *kwargs))
        ret = fn(*args,**kwargs)
        return ret
    functools.update_wrapper(wrapper,fn) # 更新属性
    print('call finished!')
    return wrapper

@logger
def add(x,y):
    'This is a function for add'
    return x + y

print("name={},doc={}".format(add.__name__,add.__doc__))

是否可以把functools.update_wrapper改造成装饰器?

functools的另一个工具可以解决这个问题。

functools.wraps

源码:

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

这里简单说明下偏函数的作用

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

上述例子中,wraps 相当于update_wrapper(wrapped,assigned,updated)。当然它需要传递wrapped函数作为参数,因此它是一个带参装饰器。

偏函数把wrapped=wrapped,assigned=assigned, updated=updated这些参数固定住了,并传递给update_wrapper.

因此,可以如此使用它:

def logger(fn):

    @functools.wraps(fn)
    def wrapper(*args,**kwargs):
        'I am wrapper'
        print('args: {},{}'.format(*args, *kwargs))
        ret = fn(*args,**kwargs)
        return ret
    print('call finished!')
    return wrapper

此时,add.__wrapped__实际上就是原add,也就是fn了。这保留了wrapped函数。

内容概要:本文介绍了一个基于冠豪猪优化算法(CPO)的无人机三维路径规划项目,利用Python实现了在复杂三维环境中为无人机规划安全、高效、低能耗飞行路径的完整解决方案。项目涵盖空间环境建模、无人机动力学约束、路径编码、多目标代价函数设计以及CPO算法的核心实现。通过体素网格建模、动态障碍物处理、路径平滑技术和多约束融合机制,系统能够在高维、密集障碍环境下快速搜索出满足飞行可行性、安全性与能效最优的路径,并支持在线重规划以适应动态环境变化。文中还提供了关键模块的代码示例,包括环境建模、路径评估和CPO优化流程。; 适合人群:具备一定Python编程基础和优化算法基础知识,从事无人机、智能机器人、路径规划或智能优化算法研究的相关科研人员与工程技术人员,尤其适合研究生及有一定工作经验的研发工程师。; 使用场景及目标:①应用于复杂三维环境下的无人机自主导航与避障;②研究智能优化算法(如CPO)在路径规划中的实际部署与性能优化;③实现多目标(路径最短、能耗最低、安全性最高)耦合条件下的工程化路径求解;④构建可扩展的智能无人系统决策框架。; 阅读建议:建议结合文中模型架构与代码示例进行实践运行,重点关注目标函数设计、CPO算法改进策略与约束处理机制,宜在仿真环境中测试不同场景以深入理解算法行为与系统鲁棒性。
在科技快速演进的时代背景下,移动终端性能持续提升,用户对移动应用的功能需求日益增长。增强现实、虚拟现实、机器人导航、自动驾驶辅助、手势识别、物体检测与距离测量等前沿技术正成为研究与应用的热点。作为支撑这些技术的核心,双目视觉系统通过模仿人类双眼的成像机制,同步获取两路图像数据,并借助图像处理与立体匹配算法提取场景深度信息,进而生成点云并实现三维重建。这一技术体系对提高移动终端的智能化程度及优化人机交互体验具有关键作用。 双目视觉系统需对同步采集的两路视频流进行严格的时间同步与空间校正,确保图像在时空维度上精确对齐,这是后续深度计算与立体匹配的基础。立体匹配旨在建立两幅图像中对应特征点的关联,通常依赖复杂且高效的计算算法以满足实时处理的要求。点云生成则是将匹配后的特征点转换为三维空间坐标集合,以表征物体的立体结构;其质量直接取决于图像处理效率与匹配算法的精度。三维重建基于点云数据,运用计算机图形学方法构建物体或场景的三维模型,该技术在增强现实与虚拟现实等领域尤为重要,能够为用户创造高度沉浸的交互环境。 双目视觉技术已广泛应用于多个领域:在增强现实与虚拟现实中,它可提升场景的真实感与沉浸感;在机器人导航与自动驾驶辅助系统中,能实时感知环境并完成距离测量,为路径规划与决策提供依据;在手势识别与物体检测方面,可精准捕捉用户动作与物体位置,推动人机交互设计与智能识别系统的发展。此外,结合深度计算与点云技术,双目系统在精确距离测量方面展现出显著潜力,能为多样化的应用场景提供可靠数据支持。 综上所述,双目视觉技术在图像处理、深度计算、立体匹配、点云生成及三维重建等环节均扮演着不可或缺的角色。其应用跨越多个科技前沿领域,不仅推动了移动设备智能化的发展,也为丰富交互体验提供了坚实的技术基础。随着相关算法的持续优化与硬件性能的不断提升,未来双目视觉技术有望在各类智能系统中实现更广泛、更深层次的应用。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值