백엔드 개발 파이썬 튜토리얼 Python中的装饰器用法详解

Python中的装饰器用法详解

Jun 06, 2016 am 11:21 AM
python 용법 데코레이터

本文实例讲述了Python中的装饰器用法。分享给大家供大家参考。具体分析如下:

这里还是先由stackoverflow上面的一个问题引起吧,如果使用如下的代码:

代码如下:

@makebold
@makeitalic
def say():
   return "Hello"


打印出如下的输出:

Hello
你会怎么做?最后给出的答案是:

代码如下:

def makebold(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped
 
def makeitalic(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped
 
@makebold
@makeitalic
def hello():
    return "hello world"
 
print hello() ## 返回 hello world


现在我们来看看如何从一些最基础的方式来理解Python的装饰器。

代码如下:

def foo():
    print 'in foo()'
foo()


这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:

代码如下:

import time
def foo():
    start = time.clock()
    print 'in foo()'
    end = time.clock()
    print 'used:', end - start
 
foo()


很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。

怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?

1.2. 以不变应万变,是变也

还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!

代码如下:

import time
 
def foo():
    print 'in foo()'
 
def timeit(func):
    start = time.clock()
    func()
    end =time.clock()
    print 'used:', end - start
 
timeit(foo)


看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。

1.3. 最大限度地少改动!

既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!

代码如下:

#-*- coding: UTF-8 -*-
import time
 
def foo():
    print 'in foo()'
 
# 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
def timeit(func):
    
    # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    
    # 将包装后的函数返回
    return wrapper
 
foo = timeit(foo)
foo()


这样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。

这个例子仅用于演示,并没有考虑foo带有参数和有返回值的情况,完善它的重任就交给你了 :)

上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。

代码如下:

import time
 
def timeit(func):
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper
 
@timeit
def foo():
    print 'in foo()'
 
foo()


重点关注第11行的@timeit,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。

要理解python的装饰器,我们首先必须明白在Python中函数也是被视为对象。这一点很重要。先看一个例子:

代码如下:

def shout(word="yes") :
    return word.capitalize()+" !"
 
print shout()
# 输出 : 'Yes !'
 
# 作为一个对象,你可以把函数赋给任何其他对象变量
 
scream = shout
 
# 注意我们没有使用圆括号,因为我们不是在调用函数
# 我们把函数shout赋给scream,也就是说你可以通过scream调用shout
 
print scream()
# 输出 : 'Yes !'
 
# 还有,你可以删除旧的名字shout,但是你仍然可以通过scream来访问该函数
 
del shout
try :
    print shout()
except NameError, e :
    print e
    #输出 : "name 'shout' is not defined"
 
print scream()
# 输出 : 'Yes !'


我们暂且把这个话题放旁边,我们先看看python另外一个很有意思的属性:可以在函数中定义函数:

代码如下:

def talk() :
 
    # 你可以在talk中定义另外一个函数
    def whisper(word="yes") :
        return word.lower()+"...";
 
    # ... 并且立马使用它
 
    print whisper()
 
# 你每次调用'talk',定义在talk里面的whisper同样也会被调用
talk()
# 输出 :
# yes...
 
# 但是"whisper" 不会单独存在:
 
try :
    print whisper()
except NameError, e :
    print e
    #输出 : "name 'whisper' is not defined"*


函数引用

从以上两个例子我们可以得出,函数既然作为一个对象,因此:

1. 其可以被赋给其他变量

2. 其可以被定义在另外一个函数内

这也就是说,函数可以返回一个函数,看下面的例子:

代码如下:

def getTalk(type="shout") :
 
    # 我们定义另外一个函数
    def shout(word="yes") :
        return word.capitalize()+" !"
 
    def whisper(word="yes") :
        return word.lower()+"...";
 
    # 然后我们返回其中一个
    if type == "shout" :
        # 我们没有使用(),因为我们不是在调用该函数
        # 我们是在返回该函数
        return shout
    else :
        return whisper
 
# 然后怎么使用呢 ?
 
# 把该函数赋予某个变量
talk = getTalk()    
 
# 这里你可以看到talk其实是一个函数对象:
print talk
#输出 :
 
# 该对象由函数返回的其中一个对象:
print talk()
 
# 或者你可以直接如下调用 :
print getTalk("whisper")()
#输出 : yes...


还有,既然可以返回一个函数,我们可以把它作为参数传递给函数:

代码如下:

def doSomethingBefore(func) :
    print "I do something before then I call the function you gave me"
    print func()
 
doSomethingBefore(scream)
#输出 :
#I do something before then I call the function you gave me
#Yes !


这里你已经足够能理解装饰器了,其他它可被视为封装器。也就是说,它能够让你在装饰前后执行代码而无须改变函数本身内容。

手工装饰

那么如何进行手动装饰呢?

代码如下:

# 装饰器是一个函数,而其参数为另外一个函数
def my_shiny_new_decorator(a_function_to_decorate) :
 
    # 在内部定义了另外一个函数:一个封装器。
    # 这个函数将原始函数进行封装,所以你可以在它之前或者之后执行一些代码
    def the_wrapper_around_the_original_function() :
 
        # 放一些你希望在真正函数执行前的一些代码
        print "Before the function runs"
 
        # 执行原始函数
        a_function_to_decorate()
 
        # 放一些你希望在原始函数执行后的一些代码
        print "After the function runs"
 
    #在此刻,"a_function_to_decrorate"还没有被执行,我们返回了创建的封装函数
    #封装器包含了函数以及其前后执行的代码,其已经准备完毕
    return the_wrapper_around_the_original_function
 
# 现在想象下,你创建了一个你永远也不远再次接触的函数
def a_stand_alone_function() :
    print "I am a stand alone function, don't you dare modify me"
 
a_stand_alone_function()
#输出: I am a stand alone function, don't you dare modify me
 
# 好了,你可以封装它实现行为的扩展。可以简单的把它丢给装饰器
# 装饰器将动态地把它和你要的代码封装起来,并且返回一个新的可用的函数。
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#输出 :
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs


现在你也许要求当每次调用a_stand_alone_function时,实际调用却是a_stand_alone_function_decorated。实现也很简单,可以用my_shiny_new_decorator来给a_stand_alone_function重新赋值。

代码如下:

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#输出 :
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
 
# And guess what, that's EXACTLY what decorators do !


装饰器揭秘

前面的例子,我们可以使用装饰器的语法:

代码如下:

@my_shiny_new_decorator
def another_stand_alone_function() :
    print "Leave me alone"
 
another_stand_alone_function()
#输出 :
#Before the function runs
#Leave me alone
#After the function runs


当然你也可以累积装饰:

代码如下:

def bread(func) :
    def wrapper() :
        print "''''''\>"
        func()
        print ""
    return wrapper
 
def ingredients(func) :
    def wrapper() :
        print "#tomatoes#"
        func()
        print "~salad~"
    return wrapper
 
def sandwich(food="--ham--") :
    print food
 
sandwich()
#输出 : --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs :
#''''''\>
# #tomatoes#
# --ham--
# ~salad~
#


使用python装饰器语法:

代码如下:

@bread
@ingredients
def sandwich(food="--ham--") :
    print food
 
sandwich()
#输出 :
#''''''\>
# #tomatoes#
# --ham--
# ~salad~
#


装饰器的顺序很重要,需要注意

代码如下:

@ingredients
@bread
def strange_sandwich(food="--ham--") :
    print food
 
strange_sandwich()
#输出 :
##tomatoes#
#''''''\>
# --ham--
#
# ~salad~


最后回答前面提到的问题:

代码如下:

# 装饰器makebold用于转换为粗体
def makebold(fn):
    # 结果返回该函数
    def wrapper():
        # 插入一些执行前后的代码
        return "" + fn() + ""
    return wrapper
 
# 装饰器makeitalic用于转换为斜体
def makeitalic(fn):
    # 结果返回该函数
    def wrapper():
        # 插入一些执行前后的代码
        return "" + fn() + ""
    return wrapper
 
@makebold
@makeitalic
def say():
    return "hello"
 
print say()
#输出: hello
 
# 等同于
def say():
    return "hello"
say = makebold(makeitalic(say))
 
print say()
#输出: hello


内置的装饰器

内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。而属性也不是不可或缺的,Java没有属性也一样活得很滋润。从我个人的Python经验来看,我没有使用过property,使用staticmethod和classmethod的频率也非常低。

代码如下:

class Rabbit(object):
    
    def __init__(self, name):
        self._name = name
    
    @staticmethod
    def newRabbit(name):
        return Rabbit(name)
    
    @classmethod
    def newRabbit2(cls):
        return Rabbit('')
    
    @property
    def name(self):
        return self._name


这里定义的属性是一个只读属性,如果需要可写,则需要再定义一个setter:

代码如下:

@name.setter
def name(self, name):
    self._name = name


functools模块

functools模块提供了两个装饰器。这个模块是Python 2.5后新增的,一般来说大家用的应该都高于这个版本。但我平时的工作环境是2.4 T-T

2.3.1. wraps(wrapped[, assigned][, updated]):
这是一个很有用的装饰器。看过前一篇反射的朋友应该知道,函数是有几个特殊属性比如函数名,在被装饰后,上例中的函数名foo会变成包装函数的名字wrapper,如果你希望使用反射,可能会导致意外的结果。这个装饰器可以解决这个问题,它能将装饰过的函数的特殊属性保留。

代码如下:

import time
import functools
 
def timeit(func):
    @functools.wraps(func)
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper
 
@timeit
def foo():
    print 'in foo()'
 
foo()
print foo.__name__


首先注意第5行,如果注释这一行,foo.__name__将是'wrapper'。另外相信你也注意到了,这个装饰器竟然带有一个参数。实际上,他还有另外两个可选的参数,assigned中的属性名将使用赋值的方式替换,而updated中的属性名将使用update的方式合并,你可以通过查看functools的源代码获得它们的默认值。对于这个装饰器,相当于wrapper = functools.wraps(func)(wrapper)。

2.3.2. total_ordering(cls):
这个装饰器在特定的场合有一定用处,但是它是在Python 2.7后新增的。它的作用是为实现了至少__lt__、__le__、__gt__、__ge__其中一个的类加上其他的比较方法,这是一个类装饰器。如果觉得不好理解,不妨仔细看看这个装饰器的源代码:

代码如下:

def total_ordering(cls):
      """Class decorator that fills in missing ordering methods"""
      convert = {
          '__lt__': [('__gt__', lambda self, other: other                      ('__le__', lambda self, other: not other                      ('__ge__', lambda self, other: not self           '__le__': [('__ge__', lambda self, other: other                      ('__lt__', lambda self, other: not other                      ('__gt__', lambda self, other: not self           '__gt__': [('__lt__', lambda self, other: other > self),
                     ('__ge__', lambda self, other: not other > self),
                     ('__le__', lambda self, other: not self > other)],
          '__ge__': [('__le__', lambda self, other: other >= self),
                     ('__gt__', lambda self, other: not other >= self),
                     ('__lt__', lambda self, other: not self >= other)]
      }
      roots = set(dir(cls)) & set(convert)
      if not roots:
          raise ValueError('must define at least one ordering operation: =')
      root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
      for opname, opfunc in convert[root]:
          if opname not in roots:
              opfunc.__name__ = opname
              opfunc.__doc__ = getattr(int, opname).__doc__
              setattr(cls, opname, opfunc)
      return cls

希望本文所述对大家的Python程序设计有所帮助。

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

PHP와 Python 중에서 선택 : 가이드 PHP와 Python 중에서 선택 : 가이드 Apr 18, 2025 am 12:24 AM

PHP는 웹 개발 및 빠른 프로토 타이핑에 적합하며 Python은 데이터 과학 및 기계 학습에 적합합니다. 1.PHP는 간단한 구문과 함께 동적 웹 개발에 사용되며 빠른 개발에 적합합니다. 2. Python은 간결한 구문을 가지고 있으며 여러 분야에 적합하며 강력한 라이브러리 생태계가 있습니다.

Windows 8에서 코드를 실행할 수 있습니다 Windows 8에서 코드를 실행할 수 있습니다 Apr 15, 2025 pm 07:24 PM

VS 코드는 Windows 8에서 실행될 수 있지만 경험은 크지 않을 수 있습니다. 먼저 시스템이 최신 패치로 업데이트되었는지 확인한 다음 시스템 아키텍처와 일치하는 VS 코드 설치 패키지를 다운로드하여 프롬프트대로 설치하십시오. 설치 후 일부 확장은 Windows 8과 호환되지 않을 수 있으며 대체 확장을 찾거나 가상 시스템에서 새로운 Windows 시스템을 사용해야합니다. 필요한 연장을 설치하여 제대로 작동하는지 확인하십시오. Windows 8에서는 VS 코드가 가능하지만 더 나은 개발 경험과 보안을 위해 새로운 Windows 시스템으로 업그레이드하는 것이 좋습니다.

PHP와 Python : 다른 패러다임이 설명되었습니다 PHP와 Python : 다른 패러다임이 설명되었습니다 Apr 18, 2025 am 12:26 AM

PHP는 주로 절차 적 프로그래밍이지만 객체 지향 프로그래밍 (OOP)도 지원합니다. Python은 OOP, 기능 및 절차 프로그래밍을 포함한 다양한 패러다임을 지원합니다. PHP는 웹 개발에 적합하며 Python은 데이터 분석 및 기계 학습과 같은 다양한 응용 프로그램에 적합합니다.

VScode 확장자가 악의적입니까? VScode 확장자가 악의적입니까? Apr 15, 2025 pm 07:57 PM

VS 코드 확장은 악의적 인 코드 숨기기, 취약성 악용 및 합법적 인 확장으로 자위하는 등 악성 위험을 초래합니다. 악의적 인 확장을 식별하는 방법에는 게시자 확인, 주석 읽기, 코드 확인 및주의해서 설치가 포함됩니다. 보안 조치에는 보안 인식, 좋은 습관, 정기적 인 업데이트 및 바이러스 백신 소프트웨어도 포함됩니다.

터미널 VSCODE에서 프로그램을 실행하는 방법 터미널 VSCODE에서 프로그램을 실행하는 방법 Apr 15, 2025 pm 06:42 PM

vs 코드에서는 다음 단계를 통해 터미널에서 프로그램을 실행할 수 있습니다. 코드를 준비하고 통합 터미널을 열어 코드 디렉토리가 터미널 작업 디렉토리와 일치하는지 확인하십시오. 프로그래밍 언어 (예 : Python의 Python Your_file_name.py)에 따라 실행 명령을 선택하여 성공적으로 실행되는지 여부를 확인하고 오류를 해결하십시오. 디버거를 사용하여 디버깅 효율을 향상시킵니다.

Python에서 비주얼 스튜디오 코드를 사용할 수 있습니다 Python에서 비주얼 스튜디오 코드를 사용할 수 있습니다 Apr 15, 2025 pm 08:18 PM

VS 코드는 파이썬을 작성하는 데 사용될 수 있으며 파이썬 애플리케이션을 개발하기에 이상적인 도구가되는 많은 기능을 제공합니다. 사용자는 다음을 수행 할 수 있습니다. Python 확장 기능을 설치하여 코드 완료, 구문 강조 및 디버깅과 같은 기능을 얻습니다. 디버거를 사용하여 코드를 단계별로 추적하고 오류를 찾아 수정하십시오. 버전 제어를 위해 git을 통합합니다. 코드 서식 도구를 사용하여 코드 일관성을 유지하십시오. 라인 도구를 사용하여 잠재적 인 문제를 미리 발견하십시오.

vScode를 Mac에 사용할 수 있습니다 vScode를 Mac에 사용할 수 있습니다 Apr 15, 2025 pm 07:36 PM

VS 코드는 Mac에서 사용할 수 있습니다. 강력한 확장, GIT 통합, 터미널 및 디버거가 있으며 풍부한 설정 옵션도 제공합니다. 그러나 특히 대규모 프로젝트 또는 고도로 전문적인 개발의 경우 VS 코드는 성능 또는 기능 제한을 가질 수 있습니다.

vscode가 ipynb를 실행할 수 있습니다 vscode가 ipynb를 실행할 수 있습니다 Apr 15, 2025 pm 07:30 PM

Code vs Code에서 Jupyter 노트북을 실행하는 핵심은 Python 환경이 올바르게 구성되어 있는지 확인하고 코드 실행 순서가 셀 순서와 일치하고 성능에 영향을 줄 수있는 큰 파일 또는 외부 라이브러리를 알고 있어야합니다. VS 코드에서 제공하는 코드 완료 및 디버깅 기능은 코딩 효율성을 크게 향상시키고 오류를 줄일 수 있습니다.

See all articles