初学python–认识装饰器

python中装饰器的理解与使用,包括闭包、函数等基本概念。包括简单的装饰器,用于函数、类的装饰器,装饰器的应用场景,几个使用的装饰器实例。

目录:

0x00写在最前面

0x01装饰器的作用,应用场景

0x02函数的定义,使用,嵌套,传参

0x03闭包的概念与理解

0x04装饰器引入:通过函数嵌套和显示调用说明,装饰器调用过程

0x05装饰器简单实例,语法糖@,说明装饰器怎么用

0x06多个装饰器调用顺序

0x07带参装饰器

0x08装饰器作用于类中函数

0x09类装饰器

0x0a几个实用的装饰器

0x0b参考文章、资料

 

 

正文:

0x00写在最前面

  1. 刚开始学装饰器,哪里写的不对,敬请指出。只解释使用情况,具体原理未进行细究。
  2. 这篇文章涉及了类和对象的内容,下篇文章会专门说说python中的类和对象。希望大家一起进步。
  3. 看这篇文章时,希望你能坐在电脑前,打开你的IDE,跑一下文章中的代码,并有能力对其进行修改,效果会好很多。
  4. 文章同步发布在我的个人博客:ZKeeer’s Blog

0x01装饰器的作用,应用场景

我理解的装饰器:就像人生来裸露,为了遮羞,几件单薄衣服就够了;为了御寒,可能要多穿几件,还得加厚;为了美观,会添点儿修饰,做做造型。我们使用函数时,想在核心功能上根据需求不同添加不同的新功能,这当然不能更改核心功能的代码,装饰器的作用就凸显出来了。装饰器可以用于插入日志、数据预处理、权限校验、错误追踪、性能测试等场景,提高了代码重用度

0x02 函数的定义,使用,嵌套,传参

Python中万物皆对象,函数也是也是对象,什么是对象呢?可以简单理解为一个模型,想用就调用,不想用就扔那儿。假设我定义一个函数

on_call()
       print(“hello”)

当我使用 new_func = on_call 时,我使用了这个对象。

当我使用

new_func = on_call
new_func()

时,我初始化了一个new_func的变量,并把它指向了on_call这个函数对象,我也可以看作这是实例化的过程。当我使用new_func = on_call()时,我初始化了一个new_func变量并执行on_call函数,将返回值赋值给new_func。

对上面的主要理解是:带括号和不带括号的区别。带括号可以理解为我在执行这个函数;不带括号可以理解为我在使用这个函数对象。当我定义一个函数时

def print_time()
       print(“before inner”)
       def pinrt_time_inner()
              print(“inner”)
       print(“after inner”)

同样,我也可以将函数作为一个参数传递给另一个函数使用。

def print_time(func)
       ptint(func.__name__)

0x03闭包的概念与理解

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。[以上引自维基百科] 再通俗来说,闭包就是一个函数的所有语句与执行这个函数的环境的打包。

是不是还不明白,我来举个栗子说明。

# func1.py
func_name = "func1"

def func1_call(f):
    return f()
#func2.py
import decorator_func1

func_name = "func2"

def func2_print_filename():
    print(func_name)

if __name__ == '__main__':
    decorator_func1.func1_call(func2_print_filename)

执行结果为:func2

在这儿我们可以看出,func2_print_name函数所打印的是它所处环境的变量,尽管是func1调用的func2_print_name。这样应该看清楚了吧。

同时,使用函数嵌套时,这个闭包作用显示的更加明显,通过下面的例子进行说明。

# decorator_ex1.py
import decorator_func1

def outer():
    func_name = "outer"
    def print_filename():  
        #func_name = "inner"
        print(func_name)
    decorator_func1.func1_call(print_filename)

if __name__ == '__main__':
    outer()

此时输出结果为:outer;若取消func_name = inner的注释,输出结果为innner。当执行print(func_name)的时候,python解释器会按照LEGB顺序查找func_name变量:

L—–local 局部名字空间

E—–enclosing 直接外围空间

G—–global 全局名字空间

B—–builtin 内建名字空间

即LEGB规则。

最后总结,闭包的价值在于可以打包函数执行的上下文环境。

0x04装饰器引入: 通过函数嵌套和显示调用说明,装饰器调用过程

根据上面说到的,我如下定义一个核心功能函数,用于输出时间。我想使用装饰器添加日志。那我应该这么做。

import time

def decorator_log_ex(func):   
    print("before: %s" % (func.__name__))
    func() 
    print("after: %s" % (func.__name__))

def Time_func_ex():
    print("time: %s"%time.ctime())

if __name__ == '__main__':
    decorator_log_ex(Time_func_ex)

这样执行结果为:

before: Time_func_ex
time: Tue Jun 20 09:24:25 2017
after: Time_func_ex

这样,只要将核心功能函数传递进去就可以实现插入日志了。这就是装饰器的基础实现。

0x05装饰器简单实例,语法糖@,说明装饰器怎么用

按照上面的调用,每次都这么写,是不是太麻烦了。Python提供了一种更加简洁优雅的写法,人们称之为语法糖,如下代码所示:

import time

def decorator_log_ex(func):
    print("before: %s" % (func.__name__))
    func()
    print("after: %s" % (func.__name__))

@ decorator_log_ex
def Time_func_ex():
    print("time: %s"%time.ctime())

if __name__ == '__main__':
    Time_func_ex()

这样,写法什么的就简洁多了。但执行的时候,仍然是转换为

decorator_log_ex(Time_func_ex)

执行。上面用来插入日志的装饰器,写成类的形式,可以这么写。

import time

class decorator_log_ex:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args):
        print("before: %s" % (self.func.__name__))
        self.func(*args)
        print("after: %s" % (self.func.__name__))

@decorator_log_ex
def Time_func_ex():
    print("time: %s" % time.ctime())

if __name__ == '__main__':
    Time_func_ex()

利用类的内建函数__call__(),同时类初始化要添加函数参数。这样一来,装饰器的功能强大了不少。

0x06多个装饰器调用顺序

那我同时使用多个装饰器的时候,装饰器的调用顺序是怎样的呢?

通过以下的例子进行说明:

def decorated_a(func):
    def wapper(*args, **kwargs):
        func()
        print(", 1", end="")
    return wapper

def decorated_b(func):
    def wapper(*args, **kwargs):
        func()
        print(", 2", end="")
    return wapper

def decorated_c(func):
    def wapper(*args, **kwargs):
        func()
        print(", 3", end="")
    return wapper

# 装饰器的执行顺序
@decorated_c
@decorated_b
@decorated_a
def on_call():
    print("报数", end="")

if __name__ == '__main__':
    on_call()

最后执行结果:报数, 1, 2, 3。所以说以上on_call函数的实际上时这样执行的:

on_call = decorator_c(decorator_b(decorator__a(on_call)))

0x07带参装饰器

如果装饰器本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

# 带参装饰器
def log(action):
    def decorator_log(func):
       def set_log(*args, **kwargs):
           print("状态:%s  函数对象:%s" %(action, func.__name__))
           return func()
       return set_log
   return decorator_log


#带参装饰器使用
@log("Running")
def print_time():
   print(time.ctime())

if __name__ == '__main__':
    print_time()

最后输出结果为:

状态:Running 函数对象:print_time
Tue Jun 20 09:57:06 2017

相比于不带参数的两层嵌套的装饰器,带参数的装饰器实际调用是这样的:

log(“Running”)(print_time)

首先调用log(“Running”),返回值是decorator_log函数,再调用返回的decorator_log函数,参数是print_time,此次返回值为:set_log函数。然后执行set_log函数。

0x08装饰器作用于类中函数

主讲使用外部定义的装饰器,作用于类内部函数,同时在在装饰器中调用类中成员函数。

使用一个捕获异常例子进行说明。
下面有一个类:

# -*-encoding:utf-8-*-
def catch_exception(func):
    def handle_exception(*args, **kwargs):
        try:
            u = func(*args, **kwargs)
            return u
        except BaseException as e:
            return "Catch an exception."

    return handle_exception


class my_deco:
    def __init__(self):
        pass

    def print_info(self):
        print("这是个测试类")

    @catch_exception
    def get_result(self, val):
        print("result: ", 10 / val)


if __name__ == '__main__':
    t = my_deco()
    t.get_result(1)

定义一个catch_exception函数,用以处理这个函数接收的参数也就是func执行时所抛出的异常。定义在类外面,可以正常在类里面使用。

但是当我想要抛出异常时,采用类成员函数进行处理,这时候catch_exception函数需要访问类的成员函数,只要在catch_exception的参数列表中,加入self参数即可。上面的例子可以修改为:

# -*-encoding:utf-8-*-
def catch_exception(func):
    def handle_exception(self, *args, **kwargs):
        try:
            u = func(self, *args, **kwargs)
            return u
        except BaseException as e:
            self.print_info()
            return "Catch an exception."

    return handle_exception


class my_deco:
    def __init__(self):
        pass

    def print_info(self):
        print("这是个测试类")

    @catch_exception
    def get_result(self, val):
        print("result: ", 10 / val)


if __name__ == '__main__':
    t = my_deco()
    t.get_result(0)

这样就可以用类成员函数正常处理抛出异常了,而使用时不需要进行任何修改。

0x09类装饰器

类装饰器类似于函数装饰器的概念,但它应用于类,它们可以用于管理类自身,或者用来拦截实例创建调用以管理实例。

00 管理类实例

类装饰器可以拦截实例创建调用,所以它们可以用来管理一个类的所有实例,或者扩展这些实例的接口。下面的类装饰器例子实现了传统的单体编码模式,即最多只有一个类的一个实例存在。

instances = {}  # 全局变量,管理实例


def getInstance(aClass, *args):
    if aClass not in instances: #如果不存在此实例便记录;否则直接返回已创建的实例
        instances[aClass] = aClass(*args)
    return instances[aClass]  # 每一个类只能存在一个实例


def singleton(aClass):
    def onCall(*args):
        return getInstance(aClass, *args)

    return onCall


@singleton  # Person = singleton(Person)
class Person:
    def __init__(self, name, hours, rate):
        self.name = name
        self.hours = hours
        self.rate = rate

    def pay(self):
        return self.hours * self.rate


@singleton  # Spam = singleton(Spam)
class Spam:
    def __init__(self, val):
        self.attr = val


if __name__ == '__main__':
    bob = Person('Bob', 40, 10)
    print(bob.name, bob.pay())

    sue = Person('Sue', 50, 20)
    print(sue.name, sue.pay())

    X = Spam(42)
    Y = Spam(99)
    print(X.attr, Y.attr)

当Person或Spam类用来创建一个实例的时候,装饰器把实例构建调用指向了onCall,它反过来调用getInstance,以针对每个类管理并分享一个单个实例,而不管进行了多少次构建调用。运行结果:

C:\Anaconda3\python.exe E:/ProgramList/PYCHARM/CorePython/decorator_class.py
Bob 400
Bob 400
42 42

Process finished with exit code 0

01私有属性禁止访问
实现类似于java/c++中私有属性禁止访问的功能,不接受装饰器这个类外部对私有属性的访问,但是类中可以自由的对这些私有属性进行访问。

def Private(*privates):
    def onDecorator(aClass):
        class onInstance:
            def __init__(self, *args, **kargs):
                self.wrapped = aClass(*args, **kargs)

            def __getattr__(self, attr):
                print('get:', attr)
                if attr in privates: #如果在私有变量列表中提示错误;否则可以访问
                    raise TypeError('private attribute fetch:' + attr)
                else:
                    return getattr(self.wrapped, attr)

            def __setattr__(self, attr, value):
                print('set:', attr, value)
                if attr == 'wrapped':  # 这里捕捉对wrapped的赋值
                    self.__dict__[attr] = value
                elif attr in privates:
                    raise TypeError('private attribute change:' + attr)
                else:  # 这里捕捉对wrapped.attr的赋值
                    setattr(self.wrapped, attr, value)

        return onInstance

    return onDecorator


@Private('data', 'size')
class Doubler:
    def __init__(self, label, start):
        self.label = label
        self.data = start

    def size(self):
        return len(self.data)

    def double(self):
        for i in range(self.size()):
            self.data[i] = self.data[i] * 2

    def display(self):
        print('%s => %s' % (self.label, self.data))


if __name__ == '__main__':
    traceMe = True

    X = Doubler('X is', [1, 2, 3])
    Y = Doubler('Y is', [-10, -20, -30])

    print(X.label)
    X.display()
    X.double()
    X.display()

    print(Y.label)
    Y.display()
    Y.double()
    Y.label = 'Spam'
    Y.display()

    # 下面这些访问都会引发异常
    print(X.size())
    print(X.data)

    X.data = [1, 1, 1]
    X.size = lambda S: 0
    print(Y.data)
    print(Y.size())

运行结果:

C:\Anaconda3\python.exe E:/ProgramList/PYCHARM/CorePython/decorator_class_2.py
set: wrapped <__main__.Doubler object at 0x000001E43165C7F0>
set: wrapped <__main__.Doubler object at 0x000001E43165C898>
get: label
X is
get: display
X is => [1, 2, 3]
get: double
get: display
X is => [2, 4, 6]
get: label
Y is
get: display
Y is => [-10, -20, -30]
get: double
set: label Spam
get: display
Spam => [-20, -40, -60]
get: size
Traceback (most recent call last):
  File "E:/ProgramList/PYCHARM/CorePython/decorator_class_2.py", line 64, in <module>
    print(X.size())
  File "E:/ProgramList/PYCHARM/CorePython/decorator_class_2.py", line 10, in __getattr__
    raise TypeError('private attribute fetch:' + attr)
TypeError: private attribute fetch:size

Process finished with exit code 1

产生了报错,错误类型及错误信息是自己定义的那样。

0x0a几个实用的装饰器

超时函数

这个函数的作用在于可以给任意可能会hang住的函数添加超时功能,这个功能在编写外部API调用 、网络爬虫、数据库查询的时候特别有用

timeout装饰器的代码如下:

import signal,functools #下面会用到的两个库 
class TimeoutError(Exception): pass #定义一个Exception,后面超时抛出 

def timeout(seconds, error_message = 'Function call timed out'):
  def decorated(func):
    def _handle_timeout(signum, frame):
      raise TimeoutError(error_message)
    def wrapper(*args, **kwargs):
      signal.signal(signal.SIGALRM, _handle_timeout)
      signal.alarm(seconds)
      try:
        result = func(*args, **kwargs)
      finally:
        signal.alarm(0)
      return result
    return functools.wraps(func)(wrapper)
  return decorated

使用:

@timeout(5) #限定下面的slowfunc函数如果在5s内不返回就强制抛TimeoutError Exception结束 
def slowfunc(sleep_time):
  import time
  time.sleep(sleep_time) #这个函数就是休眠sleep_time秒 

slowfunc(3) #sleep 3秒,正常返回 没有异常 


slowfunc(10) #被终止 

## 输出 
---------------------------------------------------------------------------
TimeoutError                              Traceback (most recent call last)

 

Trace函数

有时候出于演示目的或者调试目的,我们需要程序运行的时候打印出每一步的运行顺序 和调用逻辑。类似写bash的时候的bash -x调试功能,然后Python解释器并没有 内置这个时分有用的功能,那么我们就“自己动手,丰衣足食”。

Trace装饰器的代码如下:

import sys,os,linecache
def trace(f):
  def globaltrace(frame, why, arg):
    if why == "call": return localtrace
    return None
  def localtrace(frame, why, arg):
    if why == "line":
      # record the file name and line number of every trace 
      filename = frame.f_code.co_filename
      lineno = frame.f_lineno
      bname = os.path.basename(filename)
      print "{}({}): {}".format(  bname,
        lineno,
        linecache.getline(filename, lineno).strip('\r\n')),
    return localtrace
  def _f(*args, **kwds):
    sys.settrace(globaltrace)
    result = f(*args, **kwds)
    sys.settrace(None)
    return result
  return _f

使用:

@trace
def xxx():
  print 1
  print 22
  print 333

xxx() #调用 

## 输出 
<ipython-input-4-da50741ac84e>(3):     print 1 # @trace 的输出 
1
<ipython-input-4-da50741ac84e>(4):     print 22 # @trace 的输出 
22
<ipython-input-4-da50741ac84e>(5):     print 333 # @trace 的输出 
333

0x0b参考资料、文章

装饰器-廖雪峰的官方网站

如何理解Python装饰器? – 知乎

Python 里为什么函数可以返回一个函数内部定义的函数?

Python编程(四):两个实用的Python的装饰器 – 知乎专栏

Python 装饰器装饰类中的方法

Python–编写类装饰器 – Gavin – 博客道 – CSDN.NET

Python–编写函数装饰器 – Gavin – 博客频道 – CSDN.NET

0 0 投票数
文章评分
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论