>  기사  >  백엔드 개발  >  Python 데코레이터에 대한 자세한 소개

Python 데코레이터에 대한 자세한 소개

高洛峰
高洛峰원래의
2017-03-23 16:35:161566검색

데코레이터의 기능은 다양한 언어로 제공되며, 실제로는 새로운 기능을 작성하기보다는 개방형 및 폐쇄형 원칙을 강조하는 디자인 패턴을 구현합니다. 암호. 데코레이터는 함수뿐만 아니라 클래스와 같은 다른 객체도 꾸밀 수 있습니다. 하지만 일반적으로 우리는 데코레이팅된 함수를 예로 들어 사용법을 소개합니다. Python에서 데코레이터가 작동하는 방식을 이해하려면 단계별로 진행해야 합니다. 이 글은 가장 기본적인 내용부터 최대한 이해하기 쉽게 작성하려고 노력했습니다.
(참고: 다음은 Python3.5.1 환경을 사용합니다.)
1. Python의 함수 관련 기본
먼저 Python이 위에서 아래로 순차적으로 실행된다는 점을 강조해야 합니다. , 함수의 정의 코드 블록이 발견되면 즉시 실행되지 않습니다. 함수가 호출될 때만 내부 코드 블록이 실행됩니다.

def foo():
print("foo函数被运行了!") 
如果就这么样,foo里的语句是不会被执行的。
程序只是简单的将定义代码块读入内存中。


순차 실행의 예를 다시 보면:

def foo():
 print("我是上面的函数定义!")
def foo():
 print("我是下面的函数定义!")
foo()
运行结果:
我是下面的函数定义


순차 실행으로 인해 다음 foo가 위의 foo를 덮어쓰는 것을 볼 수 있습니다. 따라서 Python에는 코드 배치에 대한 요구 사항이 있으며 임의로 배치할 수 없습니다. 함수 본문은 호출된 문 앞에 배치되어야 합니다.
둘째, 먼저 몇 가지 사항을 알아내야 합니다. 함수 이름, 함수 본문, 반환 값, 함수의 메모리 주소, 함수 이름과 괄호, 함수 이름과 괄호는 매개변수로 처리되고, 함수 이름과 괄호는 처리됩니다. 매개변수로 함수 이름을 반환하고, 함수 이름과 괄호를 반환합니다. 다음 함수의 경우:

def foo():
 print("让我们干点啥!")
 return "ok"
 foo()  


함수 이름: foo
함수 본문: 1-3행
반환 값: 문자열 "ok" 반환 개체가 명시적으로 제공되지 않으면 없음
은 기본적으로 반환됩니다. 함수 본문을 메모리로 읽어올 때 함수 이름 foo로 참조됩니다.
함수 이름과 괄호: 예를 들어 함수의 호출 메서드인 foo()는 이 괄호가 보이는 한 프로그램은 함수 이름을 기반으로 메모리에서 함수 본문을 찾아 실행합니다.
다음 예를 살펴보겠습니다.

def outer(func):
 def inner():
 print("我是内层函数!")
 return inner
def foo():
 print("我是原始函数!") 
outer(foo)
outer(foo())

파이썬에서는 모든 것이 객체이고 함수도 예외는 아닙니다. 따라서 함수 이름을 사용하거나 괄호가 포함된 함수 이름을 사용하여 다른 함수의 반환 값으로 호출할 수도 있습니다. 위 코드에서 외부와 foo는 두 가지 함수입니다. 외부(foo)는 foo 함수의 함수 이름을 외부 함수에 매개변수로 전달하는 것을 의미하고 외부 함수를 실행하는 것은 foo 실행 후 반환 값을 처리하는 것을 의미합니다. function as 매개변수는 외부 함수에 전달되고 외부 함수는 실행됩니다. foo 함수는 반환 값을 지정하지 않으므로 실제로 None을 외부 함수에 전달합니다. 괄호가 있는지 없는지 차이점에 주목하세요.

마찬가지로 외부 함수 내부에서는 내부 함수 내부에 정의된 함수인 내부가 반환됩니다. 내부 뒤에 괄호가 없으므로 실제로 이름이 내부인 내부의 함수 본문이 반환됩니다. 단순한 참고사항일 뿐입니다. 그렇다면 외부 함수가 inner()를 반환한다면 어떻게 될까요? 이제 매우 명확해졌을 것입니다. 먼저 내부 함수의 내용을 실행한 다음 None을 외부에 반환하고 외부는 이를 호출한 개체에 None을 반환합니다.
함수 이름, 함수와 괄호는 매개변수로 전달될 수도 있고, 괄호의 유무는 완전히 다른 두 가지 의미를 갖는다는 점을 기억하세요!

2. 데코레이터의 사용 시나리오
데코레이터는 일반적으로 원래 함수 코드와 기능을 변경하지 않고 추가 기능을 추가하는 데 사용됩니다. 예를 들어, 원래 함수가 실행되기 전에 무언가를 실행하고, 실행 후에 무언가를 실행합니다. 데코레이터의 사용 시나리오와 이들이 구현하는 디자인 패턴을 예를 들어 살펴보겠습니다. (더 나은 시나리오를 설계할 수 없어서 안타깝지만 Wu Dashen의 사례만 인용하여 설명하겠습니다.)
한 대기업이 있는데, 그 회사의 기본 플랫폼 부서가 내부 애플리케이션 및 API 개발을 담당하고 있습니다. , 수백 개의 비즈니스가 있으며 각 부서는 자체 비즈니스를 처리하기 위해 기본 플랫폼 부서에서 제공하는 서로 다른 기능을 호출합니다.

# 基础平台部门开发了上百个函数
def f1():
 print("业务部门1数据接口......")
def f2():
 print("业务部门2数据接口......")
def f3():
 print("业务部门3数据接口......")
def f100():
 print("业务部门100数据接口......") 
#各部门分别调用
f1()
f2()
f3()
f100()


    由于公司在创业初期,基础平台部开发这些函数时,由于各种原因,比如时间,比如考虑不周等等,没有为函数调用进行安全认证。现在,平台部主管决定弥补这个缺陷,于是:
  第一回,主管叫来了一个运维工程师,工程师跑上跑下逐个部门进行通知,让他们在代码里加上认证功能,然而,当天他被开除了。
 第二回:主管又叫来了一个运维工程师,工程师用shell写了个复杂的脚本,勉强实现了功能。但他很快就回去接着做运维了,不会开发的运维不是好运维....
 第三回:主管叫来了一个python自动化开发工程师,哥们是这么干的:只对基础平台的代码进行重构,让N个业务部门无需做任何修改。这哥们很快也被开了,连运维也没得做。  

def f1():
 #加入认证程序代码
 print("业务部门1数据接口......")
def f2():
 # 加入认证程序代码
 print("业务部门2数据接口......")
def f3():
 # 加入认证程序代码
 print("业务部门3数据接口......")
def f100():
 #加入认证程序代码
 print("业务部门100数据接口......")
#各部门分别调用
f1()
f2()
f3()
f100()


 第四回:主管又换了个 工程师,他是这么干的:定义个认证函数,原来其他的函数调用它,代码如下框。但是,主管依然不满意,不过这一次他解释了为什么。主管说:写代码要遵循开放封闭原则,虽然在这个原则主要是针对面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码内部不允许被修改,但外部可以被扩展,即:封闭:已实现的功能代码块;开放:对扩展开放。如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3......f100的内部进行代码修改。遗憾的是,工程师没有漂亮的女朋友,所以很快也被开除了。

def login():
 print("认证成功!")
def f1():
 login()
 print("业务部门1数据接口......")
def f2():
 login()
 print("业务部门2数据接口......")
def f3():
 login()
 print("业务部门3数据接口......")
def f100():
 login()
 print("业务部门100数据接口......")
#各部门分别调用
f1()
f2()
f3()
f100()


   第五回:已经没有时间让主管找别人来干这活了,他决定亲自上阵,并且打算在函数执行后再增加个日志功能。主管是这么想的:不会装饰器的主管不是好码农!要不为啥我能当主管,你只能被管呢?嘿嘿。他的代码如下:

#/usr/bin/env python
#coding:utf-8
def outer(func):
 def inner():
 print("认证成功!")
 result = func()
 print("日志添加成功")
 return result
 return inner
@outer
def f1():
 print("业务部门1数据接口......")
@outer
def f2():
 print("业务部门2数据接口......")
@outer
def f3():
 print("业务部门3数据接口......")
@outer
def f100():
 print("业务部门100数据接口......")
#各部门分别调用
f1()
f2()
f3()
f100()


对于上述代码,也是仅需对基础平台的代码进行拓展,就可以实现在其他部门调用函数 f1 f2 f3 f100 之前都进行认证操作,在操作结束后保存日志,并且其他业务部门无需他们自己的代码做任何修改,调用方式也不用变。“主管”写完代码后,觉得独乐了不如众乐乐,打算显摆一下,于是写了篇博客将过程进行了详细的说明。
三、装饰器的内部原理、
 下面我们以f1函数为例进行说明:

def outer(func):
 def inner():
 print("认证成功!")
 result = func()
 print("日志添加成功")
 return result
 return inner
@outer
def f1():
 print("业务部门1数据接口......")


运用我们在第一部分介绍的知识来分析一下上面这段代码:
 程序开始运行,从上往下编译,读到def outer(func):的时候,发现这是个“一等公民”->函数,于是把函数体加载到内存里,然后过。
 读到@outer的时候,程序被@这个语法糖吸引住了,知道这是个装饰器,按规矩要立即执行的,于是程序开始运行@后面那个名字outer所定义的函数。(相信没有人会愚蠢的将@outer写到别的位置,它只能放在被装饰的函数的上方最近处,不要空行。)
 程序返回到outer函数,开始执行装饰器的语法规则,这部分规则是定死的,是python的“法律”,不要问为什么。规则是:被装饰的函数的名字会被当作参数传递给装饰函数。装饰函数执行它自己内部的代码后,会将它的返回值赋值给被装饰的函数。 
如下图所示:
Python 데코레이터에 대한 자세한 소개
这里面需要注意的是:
 @outer和@outer()有区别,没有括号时,outer函数依然会被执行,这和传统的用括号才能调用函数不同,需要特别注意!那么有括号呢?那是装饰器的高级用法了,以后会介绍。
 是f1这个函数名(而不是f1()这样被调用后)当做参数传递给装饰函数outer,也就是:func = f1,@outer等于outer(f1),实际上传递了f1的函数体,而不是执行f1后的返回值。
 outer函数return的是inner这个函数名,而不是inner()这样被调用后的返回值。
如果你对第一部分函数的基础知识有清晰的了解,那么上面的内容你应该很容易理解。
 4. 程序开始执行outer函数内部的内容,一开始它又碰到了一个函数,很绕是吧?当然,你可以在 inner函数前后安排点别的代码,但它们不是重点,而且有点小麻烦,下面会解释。inner函数定义块被程序观察到后不会立刻执行,而是读入内存中(这是潜规则)。
 5. 再往下,碰到return inner,返回值是个函数名,并且这个函数名会被赋值给f1这个被装饰的函数,也就是f1 = inner。根据前面的知识,我们知道,此时f1函数被新的函数inner覆盖了(实际上是f1这个函数名更改成指向inner这个函数名指向的函数体内存地址,f1不再指向它原来的函数体的内存地址),再往后调用f1的时候将执行inner函数内的代码,而不是先前的函数体。那么先前的函数体去哪了?还记得我们将f1当做参数传递给func这个形参么?func这个变量保存了老的函数在内存中的地址,通过它就可以执行 老的函数体,你能在inner函数里看到result = func()这句代码,它就是这么干的!
 6.接下来,还没有结束。当业务部门,依然通过f1()的方式调用f1函数时,执行的就不再是老的f1函数的代码,而是inner函数的代码。在本例中,它首先会打印个“认证成功”的提示,很显然你可以换成任意的代码,这只是个示例;然后,它会执行func函数并将返回值赋值个变量result,这个func函数就是老的f1函数;接着,它又打印了“日志保存”的提示,这也只是个示例,可以换成任何你想要的;最后返回result这个变量。我们在业务部门的代码上可以用 r = f1()的方式接受result的值。
 7.以上流程走完后,你应该看出来了,在没有对业务部门的代码和接口调用方式做任何修改的同时,也没有对基础平台部原有的代码做内部修改,仅仅是添加了一个装饰函数,就实现了我们的需求,在函数调用前先认证,调用后写入日志。这就是装饰器的最大作用。
 问题:那么为什么我们要搞一个outer函数一个inner函数这么复杂呢?一层函数不行吗?
 答:请注意,@outer这句代码在程序执行到这里的时候就会自动执行outer函数内部的代码,如果不封装一下,在业务部门还未进行调用的时候,就执行了些什么,这和初衷有点不符。当然,如果你对这个有需求也不是不行。请看下面的例子,它只有一层函数。

def outer(func):
 print("认证成功!")
 result = func()
 print("日志添加成功")
 return result
@outer
def f1():
 print("业务部门1数据接口......")
# 业务部门并没有开始执行f1函数
执行结果:
认证成功!
业务部门1数据接口......
日志添加成功


看到没?我只是定义好了函数,业务部门还没有调用f1函数呢,程序就把工作全做了。这就是封装一层函数的原因。
四、装饰器的参数传递
细心的朋友可能已经发现了,上面的例子中,f1函数没有参数,在实际情况中肯定会需要参数的,那参数怎么传递的呢?
 一个参数的情况:

def outer(func):
 def inner(username):
 print("认证成功!")
 result = func(username)
 print("日志添加成功")
 return result
 return inner
@outer
def f1(name):
print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack")

在inner函数的定义部分也加上一个参数,调用func函数的时候传递这个参数,很好理解吧?可问题又来了,那么另外一个部门调用的f2有2个参数呢?f3有3个参数呢?你怎么传递?
很简单,我们有*args和**kwargs嘛!号称“万能参数”!简单修改一下上面的代码:

def outer(func):
 def inner(*args,**kwargs):
 print("认证成功!")
 result = func(*args,**kwargs)
 print("日志添加成功")
 return result
 return inner
@outer
def f1(name,age):
 print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack",18)

五、更进一步的思考
 一个函数可以被多个函数装饰吗?可以的!看下面的例子!  

def outer1(func):
 def inner(*args,**kwargs):
 print("认证成功!")
 result = func(*args,**kwargs)
 print("日志添加成功")
 return result
 return inner
def outer2(func):
 def inner(*args,**kwargs):
 print("一条欢迎信息。。。")
 result = func(*args,**kwargs)
 print("一条欢送信息。。。")
 return result
 return inner
 @outer1
@outer2
def f1(name,age):
 print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack",18) 
执行结果:
认证成功!
一条欢迎信息。。。
jack 正在连接业务部门1数据接口......
一条欢送信息。。。
日志添加成功

更进一步的,装饰器自己可以有参数吗?可以的!看下面的例子:

# 认证函数
def auth(request,kargs):
 print("认证成功!")
# 日志函数
def log(request,kargs):
 print("日志添加成功")
# 装饰器函数。接收两个参数,这两个参数应该是某个函数的名字。
def Filter(auth_func,log_func):
 # 第一层封装,f1函数实际上被传递给了main_fuc这个参数
 def outer(main_func):
 # 第二层封装,auth和log函数的参数值被传递到了这里
 def wrapper(request,kargs):
 # 下面代码的判断逻辑不重要,重要的是参数的引用和返回值
 before_result = auth(request,kargs)
 if(before_result != None):
 return before_result;
 main_result = main_func(request,kargs)
 if(main_result != None):
 return main_result;
 after_result = log(request,kargs)
 if(after_result != None):
 return after_result;
 return wrapper
 return outer
# 注意了,这里的装饰器函数有参数哦,它的意思是先执行filter函数
# 然后将filter函数的返回值作为装饰器函数的名字返回到这里,所以,
# 其实这里,Filter(auth,log) = outer , @Filter(auth,log) = @outer
@Filter(auth,log)
def f1(name,age):
 print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack",18)
运行结果:
认证成功!
jack 正在连接业务部门1数据接口......
日志添加成功

又绕晕了?其实你可以这么理解,先执行Filter函数,获得它的返回值outer,再执行@outer装饰器语法。

위 내용은 Python 데코레이터에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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