python 装饰器和property

Posted trent-fzq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python 装饰器和property相关的知识,希望对你有一定的参考价值。

一.  理解和实现装饰器

所谓装饰器, 就是在代码执行期间, 在不改变原有代码(类或者函数)的情况下, 为之动态附加功能.

例如, 在调用函数之前做一些计算, 在函数调用之后输出日志.

如何实现一个装饰器呢, 这里就需要使用到前面学习的知识闭包函数了.

1. 装饰器的原型

import time
def decorator(func):  # 将原函数作为参数传入
    def newfunc():
        # 调用之前
        stime = time.perf_counter()
        func()  # 调用原函数
        # 调用之后
        etime = time.perf_counter()
        print(called %s, used %s % (func.__name__, etime-stime))
    # 将新函数返回
    return newfunc


def func():
    print("原始函数被调用了")


func = decorator(func)  # 手动的把新函数赋值给旧函数
func()  # newfunc()

  

2. 装饰器的 @语法糖

在装饰器原型中, 我们是手动的调用 expand 函数, 而在 python 中, 应该使用 @语法糖, 将装饰器 decorator 至于函数的定义处:

@decorator  # 相当于 func = expand(func)
def func(): 
    print("原始函数被调用了")

 这时候, 我们就可以这样调用了

func()

  

3. 被装饰函数带有参数的装饰器

如果一个需要被装饰的函数有参数, 那么, 在装饰器的返回函数中, 也应该有有相应的参数, 否则会产生错误

import time
def decorator(func):  # 将原函数作为参数传入
    def newfunc(arg, *args, **kwargs):
        # 调用之前
        stime = time.perf_counter()
        func(arg, *args, **kwargs)  # 调用原函数
        # 调用之后
        etime = time.perf_counter()
        print(called %s, used %s % (func.__name__, etime-stime))
    # 将新函数返回
    return newfunc


@decorator
def func(arg, *args, **kwargs):
    print("原始函数被调用了")


func(‘‘)  # newfunc()

  

4. 带有返回值的装饰器函数

import time
def decorator(func):
    def newfunc(arg, *args, **kwargs):
        # stime = time.perf_counter()
        return func(arg, *args, **kwargs)  # 执行原函数, 并返回原函数的返回值
        # etime = time.perf_counter()
        # return etime-stime  # 给原函数添加返回值功能
    return newfunc


@decorator
def func(arg, *args, **kwargs):
    return "原始函数被调用了"


print(func(‘‘))  # 0.0005786

  

5. 装饰器需要传参数

如果装饰器本身需要传入参数, 那么就要再嵌套一层闭包

import time
def log(name):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(%s call %s, on %s % (name, func.__name__, time.strftime("%Y.%m.%d %H:%M:%S")))
            return func(*args, **kwargs)  # 执行原函数, 并返回原函数的返回值
        return wrapper  # 实际返回函数
    return decorator


@log(trent)  # 相当于执行了 func = log(‘trent‘)(func)
def func():
    print("原始函数被调用了")


func()

  

6. 原函数丢失, 用 wraps 找回

在以上的案例中, 我们会发现, 原函数被新函数替代, 原函数的签名__name__丢了, 文档__doc__等等也丢了, 取而代之的是返回的新函数的东西

def decorator(func):
    def wrapper(*args, **kwargs):
        pass
    return wrapper

@decorator
def func():
    pass

print(func.__name__)  # wrapper

而我们需要的是原函数, 而不是新的函数, 所以需要使用 functools.wraps 来将原函数的东西赋值给新的函数, 那么我们以上的例子就应该写为:

import time
from functools import wraps
def log(name):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(%s called %s on %s % (name, func.__name__, time.strftime("%Y.%m.%d %H:%M:%S")))
            return func(*args, **kwargs)  # 执行原函数, 并返回原函数的返回值
        return wrapper  # 实际返回函数
    return decorator

 

 7. 装饰器的优化 @decorator

多层嵌套的装饰器, 不是很直观, 甚至很复杂, 那么我们就需要第三方库来优化, 让装饰器更具有可读性.

使用内置的装饰器: @decorator, 基本能够满足需求

import time
from decorator import decorator
def log(name):
    @decorator
    def dcrt(func, *args, **kwargs):
        print(%s called %s on %s %
              (name, func.__name__, time.strftime("%Y.%m.%d %H:%M:%S")))
        return func(*args, **kwargs)
    return dcrt


@log(trent)
def func():
    pass


func()

 被装饰之后, 我们去查看一下它的源码:

import inspect
print(inspect.getsource(func))

 执行结果(是原函数的源码):

@log(trent)
def func():
    pass

  

8. 一个强大的装饰器包 wrapt

wrapt 比较完善, 而且能够实现平常没有想到的装饰器, 使用的原型:

import wrapt
@wrapt.decorator
def pass_through(wrapped, instance, args, kwargs):
    return wrapped(*args, **kwargs)


@pass_through
def function():
    pass

 这里需要注意它的四个参数, 这四个参数是必须要有的, 而且 args 和 kwargs 不带星号, 在返回之时才带星号

由于小弟功力尚浅, 很少用 wrapt 这样的装饰器, 对此暂不做深入的演示, 如果你有兴趣, 可参考 wrapt 的文档: http://wrapt.readthedocs.io/en/latest/quick-start.html

由于在 python 中, 一切皆为对象, 函数形式的装饰器与类形式的装饰器相差不大, 所以对类的装饰器不做赘述.

 

二.  常用的内置装饰器

以上内容的装饰器优化中, 使用的 @decorator 装饰器, 是其中的一个内置装饰器. 以下将介绍更多的常用的内置装饰器

在此, 我们想看看这样一个简单的类

class Person():
    def __init__(self, name):
        self.__name = name
    
    def getName(self):
        return self.__name
    def setName(self, val):
        self.__name = val
    def delName(self):
        del self.__name

    name = property(getName, setName, delName, a document)

 

在此类中, 封装了一个私有属性__name, 并定义了对__name属性的相关操作函数, 那么在我们没有使用 property 之前, 只能通过调用相应的方法才能对属性__name进行相关的操作.

那么我们使用了 property 之后, 我们就可以在外部这样操作:

ps = Person(Trent)
print(ps.name)  # 相当于调用了ps.getName()函数
ps.name = _trent_  # 相当于调用了ps.setName(‘_trent_‘)函数
print(ps.name)
del ps.name  # 相当于调用了ps.delName()函数

 

那么当变量不断的增多时, 我们就要不断的增加一套一套的 get/set/del 和 property , 这样就会显得臃肿, 啰嗦, 而且有些私有的属性我们没有必要设置一套 get/set/del . 因此可以使用以下几个内置的装饰器来解决

1. @property 属性的获取

2. @setter 属性的赋值

3. @deleter 属性的删除

了解至此, 我们可以把以上的 Person 类改写为这样:

class Person():
    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        return self.__name

    @name.setter  # 注意: setter必须带上前缀 公有的属性名.
    def name(self, val):
        self.__name = val

    @name.deleter  # 注意: deleter必须带上前缀 公有的属性名.
    def name(self):
        del self.__name

 

4. @classmethod

5. @staticmethod

class Person():
    def run():
        print(普通方法 run, 只能类调用)
    def walk(self):
        print(‘实例绑定方法 walk, 自动传递实例对象self参数)
    @classmethod
    def smile(cls):
        print(‘类绑定方法 smile, 自动传递类cls参数)
    @staticmethod
    def look():
        print(静态方法 look, 无论类和实例对象, 都能调用的方法)

在外部使用

ps = Person()
# ps.run()  # 不能用实例对象调用
Person.run()
ps.walk()
# Person.walk()  # 不能使用类调用
ps.smile()
Person.smile()
ps.look()
Person.look()

 总结: 类可以调用的方法有 静态方法, 类绑定方法和 普通方法. 实例可以调用的方法有 实例绑定方法, 类绑定方法 和 静态方法.

 

以上是关于python 装饰器和property的主要内容,如果未能解决你的问题,请参考以下文章

python 装饰器:装饰器实例内置装饰器

python 装饰器:装饰器实例内置装饰器

Python学习—— 装饰器和函数闭包

python基础-装饰器和偏函数

Python 3.5 装饰器和函数字段

Python迭代器和生成器,装饰器