鱼C论坛

 找回密码
 立即注册
查看: 2531|回复: 18

[技术交流] Python元编程(跟新中,请不要回复谢谢)

[复制链接]
发表于 2020-1-14 23:03:59 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 Stubborn 于 2020-1-18 00:56 编辑


前言


以上内容来自书籍《Python3-Cookbook第三版中文版》,有兴趣可以下载自己阅读包括(epub,awz3,mobi)。

游客,如果您要查看本帖隐藏内容请回复



# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ Version : ??
@ Author : Alex
@ Software : Pycharm
@ Time : 2020/1/2 下午2:20
"""
import time
from functools import wraps


def timethis(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        start = time.time()
        function(*args, **kwargs)
        print(function.__name__, start - time.time())

    return wrapper


@timethis
def downout(n):
    while n > 0:
        n -= 1


downout(10000)

如果没有`@timethis`装饰器,等价于下面的调用方式。作为入门的小案例,没有很多好理解的,无非就是把函数包装成一个新的函数,同时在新函数里面运行函数,或者额外加点东西,比如上面的代码,实现了函数运行时间的打印功能。
def downout(n):
    while n > 0:
        n -= 1


downout = timethis(downout)
downout(10000)

评分

参与人数 1贡献 +8 收起 理由
zltzlt + 8

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-1-14 23:04:55 | 显示全部楼层

装饰器--02节--编写装饰器时如何保存函数的元数据

本帖最后由 Stubborn 于 2020-1-15 16:14 编辑

目录层,一楼 - 编写装饰器时如何保存函数的元数据

什么叫函数的元数据呢,其实就是和函数一些相关的属性,比如函数的__name__(函数名字),__doc__(函数文档),__dict__(属性字典)等等和函数相关的。

其实`from functools import wraps`  `@wraps(function)`这个装饰器就是用来保存函数的元数据,在这个装饰器的作用下,函数的所有元数据会被保存,你可以试试,如果去掉`@wraps(function)`那么`metadata`函数的元数据,将会丢失。
# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ Version : ??
@ Author : Alex
@ Software : Pycharm
@ Time : 2020/1/8 下午2:20
"""
import time
from functools import wraps


def timethis(function):
    """wraps就是保存函数的元数据"""

    @wraps(function)
    def wrapper(*args, **kwargs):
        start = time.time()
        function(*args, **kwargs)
        print(function.__name__, start - time.time())

    return wrapper


@timethis
def metadata(n: int):
    pass


# 下面演示如何检视函数的元数据
print(metadata.__name__)
print(metadata.__doc__)
print(metadata.__annotations__)
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-15 00:17:02 | 显示全部楼层

解除一个装饰器

本帖最后由 Stubborn 于 2020-1-15 16:14 编辑

03节--解除一个装饰器

问题:一个装饰器已经作用在一个函数上,你想撤销它,直接访问原始的未包装的那个函数。

答案:一般来说我们可以通过访问__wrapped__属性来获取对原始函数的访问
# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ Version : ??
@ Author : Alex
@ Software : Pycharm
@ Time : 2020/1/2 下午2:20
"""
import time
from functools import wraps


def timethis(function):

    @wraps(function)
    def wrapper(*args, **kwargs):
        print(f"this is one {function.__name__}")
        return function(*args, **kwargs)

    return wrapper


def time_this(function):

    @wraps(function)
    def wrapper(*args, **kwargs):
        print(f"this is two {function.__name__}")
        return function(*args, **kwargs)

    return wrapper


@timethis
def metadata(n: int):
    print(f"this is metadata args {n}")

访问__wrapped__属性来获取对原始函数的访问,前提是使用了`@wraps(function)`这个装饰器。
meta = metadata.__wrapped__

meta(5)

直接访问装饰器背后的那个未包装过的函数对于调试、反射(introspection,也有译为“自省”)以及其他一些涉及函数的操作是很有帮助的。但是,这里讨论的技术只有在实现装饰器时利用functools模块中的@wraps对元数据进行了适当的拷贝,或者直接设定了__wrapped__属性时才有用。

如果有多个装饰器已经作用于某个函数上了,那么访问__wrapped__属性的行为目前是未定义的,应该避免这种情况!假如有如下代码:
@timethis
@time_this
def add(x, y):
    return f"{x},{y}"
a = add.__wrapped__  # 解除第一层装饰器
print(a(3, 5))
print(a.__wrapped__(7, 6))  # # 解除第二层装饰器

最后但同样重要的是,请注意并不是所有的装饰器都使用了@wraps,因此有些装饰器的行为可能与我们期望的有所区别。特别是,由内建的装饰器@staticmethod和@classmethod创建的描述符(descriptor)对象并不遵循这个约定(相反,它们会把原始函数保存在__func__属性中)。所以,具体问题需要具体分析,每个人遇到的情况可能不同。

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-15 00:54:23 | 显示全部楼层

定义一个带参数的装饰器

本帖最后由 Stubborn 于 2020-1-15 18:03 编辑

04节 - -  定义一个带参数的装饰器

# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ Version : ??
@ Author : Alex
@ Software : Pycharm
@ Time : 2020/1/2 下午2:20

让我们用一个例子来说明接受参数的过程。假设我们想编写一个为函数添加日志功能的装饰器,
    但是又允许用户指定日志的等级以及一些其他的细节作为参数。下面是定义这个装饰器的可能做法:
"""
import logging
import os
import time
from functools import wraps


def logged(level, name=None, message=None):
    """
    日志记录器
    :param level:日志记录级别
    :param name:记录器名称
    :param message:是日志消息
    # 如果未指定名称和消息,它们默认为函数的模块和名称。
    """
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__
        meg = f"[level:{level}] {os.path.basename(__file__)} {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())} {logmsg}"

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, meg)
            return func(*args, **kwargs)
        return wrapper
    return decorate


# Example use
@logged(logging.WARNING)
def add(x, y):
    return x + y

@logged(logging.CRITICAL, 'example')
def spam():
    print('Spam!')

代码如上,可以接受一些 参数,虽然看起来有点复杂, 核心却是很简单的。最外层的函数 logged() 接受参数并将它们作用在内部的装饰器函数上面。 内层的函数 decorate() 接受一个函数作为参数,然后在函数上面放置一个包装器。 这里的关键点是包装器是可以使用传递给 logged() 的参数的。
但是如果没有@logged()装饰器,它的调用等同
func = logged(level, name=None, message=None)(add)
fun()


想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-15 01:02:42 | 显示全部楼层

可自定义属性的装饰器

本帖最后由 Stubborn 于 2020-1-16 20:37 编辑

05节 -_- 可自定义属性的装饰器

在这之前,稍微介绍`functools.partial` 用于修改,固定,添加函数的参数,返回一个新的函数。下面看简单的例子。
# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ Version : ??
@ Author : Alex
@ Software : Pycharm
@ Time : 2020/1/2 下午2:20
"""
from functools import partial
from inspect import signature

def test(a , b, c=5):
    return a * b * c

print(signature(test)) # -> (a, b, c=5)
test_one = partial(test, 2, c=4) # new func
print(signature(test_one)) #(b, *, c=4)
print(test_one(5)) # return

def test_two(*args, **kwargs):
    return sum(args) + sum(list(kwargs.values()))

print(signature(test_two))
test_three = partial(test_two, 2, 3, d=5,  c=4)
print(signature(test_three))
print(test_three(6))

问题:我们想编写一个装饰器来包装函数,但是可以让用户调整装饰器的属性,这样在运行时能够控制装饰器的行为。

    解答:引入了访问器函数(accessor function),通过使用nonlocal关键字声明变量来修改装饰器内部的属性。之后把访问器函数作为函数属性附加到包装函数上。


代码如下
# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ Version : ??
@ Author : Alex
@ Software : Pycharm
@ Time : 2020/1/2 下午2:20

我们想编写一个装饰器来包装函数,但是可以让用户调整装饰器的属性,这样在运行时能够控制装饰器的行为。

    引入了访问器函数(accessor function),通过使用nonlocal关键字声明变量来修改装饰器内部的属性。
        之后把访问器函数作为函数属性附加到包装函数上。
"""

from functools import wraps, partial
import logging


def attach_wrapper(func_obj, func=None):
    """将函数附加为obj的属性"""
    if func is None:
        return partial(attach_wrapper, func_obj)
    setattr(func_obj, func.__name__, func)
    return func


def logged(level, name=None, message=None):
    """     日志记录器
    :param level:日志记录级别
    :param name:记录器名称
    :param message:是日志消息
    # 如果未指定名称和消息,它们默认为函数的模块和名称。
    """
    def decorate(func):
        logname = name if name else func.__module__
        logmsg = message if message else func.__name__
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[level:{level}]\tname:{logname}\tmessage:{logmsg}")
            return func(*args, **kwargs)

        # Attach setter functions
        @attach_wrapper(wrapper)
        def set_level(newlevel):
            nonlocal level
            level = newlevel

        @attach_wrapper(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg = newmsg

        return wrapper
    return decorate

# Example use
@logged(logging.DEBUG)
def add(x, y):
    return x + y


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


# -----一下是功能演示-------------------
print("---Example use fun__add__")
add(3, 2)
add.set_message("Alex")
add(3, 2)
# 等价调用
print("---Example use fun__spam__")
spam = logged(level=25)(spam) # spam -> wrapper 包含闭包内的局部变量,以及方法
spam(3, 2)
spam.set_message("Alex is good girl") # 通过 nonlocal 修改局部变量
spam(3, 2)

能打到目的(调整装饰器的属性),示例中的关键,就是访问器函数(set_message()和set_level())。它们以属性的形式附加到了包装函数上。每个访问器函数允许对nonlocal变量赋值来调整内部参数。而且访问器函数可以跨越多个装饰器层进行传播(如果所有的装饰器都使用了@functools.wraps 的话)
def time_this(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        start = time.time()
        function(*args, **kwargs)
        print(function.__name__, start - time.time())

    return wrapper


@time_this
@logged(logging.DEBUG)
def countdown(n):
    while n > 0:
        n -= 1


末尾补充:关于上面访问器函数和装饰器,如果看不明白,可以参考下面的代码和第一个介绍partial的代码
def attach_wrapper(func_obj, func=None):
    if func is None:
        return partial(attach_wrapper, func_obj)
    setattr(func_obj, func.__name__, func)
    return func

def function():
    pass

@attach_wrapper(function)
def test(name):
    print(name)

function.test(123)
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-15 16:15:45 | 显示全部楼层
本帖最后由 Stubborn 于 2020-1-16 21:35 编辑

06节 -_-  带可选参数的装饰器

问题:你想写一个装饰器,既可以不传参数给它,比如 @decorator , 也可以传递可选参数给它,比如 @decorator(x,y,z)

例子,05节修改版本:
from functools import wraps, partial


def logged(func=None, *, level=50, name=None, message=None):
    if func is None:
        return partial(logged, level=level, name=name, message=message)

    logname = name if name else func.__module__
    logmsg = message if message else func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[level:{level}]\tname:{logname}\tmessage:{logmsg}")
        return func(*args, **kwargs)

    return wrapper

从示例中可以看到,现在这个装饰器既能够以简单的形式(即@logged)使用,也可以提供可选的参数给它(即,@logged(level=50, name='example'))。

提到的实际上是一种编程一致性(programming consistency)的问题。当使用装饰器时,大部分程序员习惯于完全不使用任何参数,或者就像示例中那样使用参数。从技术上来说,如果装饰器的所有参数都是可选的,那么可以像这样来使用:
@logged()  # 括号可以带,或者不带
def sub(x, y, z):
    return f"this is sub function {x}, {y}, {z}"

要理解代码是如何工作的,就需要对装饰器是如何施加到函数上,以及对它们的调用约定有着透彻的理解才行。以def sub简单的装饰器为例:调用顺序是这样的:
sub = logged(sub)  # -> 只是把sub函数,丢给logged函数,得到一个新的函数地址
# 对于一个可接受参数的装饰器,例如 def spam 调用顺序是这样的:
spam = logged(level=50, name="example")(spam)

在初次调用logged()时,被包装的函数并没有传递给logged。因此在装饰器中,被包装的函数必须作为可选参数。这样一来,反过来迫使其他的参数都要通过关键字来指定。此外,当传递了参数后装饰器应该返回一个新函数,要包装的函数就作为参数传递给这个新函数(具体见05)。
要做到这一点,我们在解决方案中利用functools.partial来实现这个聪明的技巧。具体来说,它只是返回了一个部分完成的版本,除了要被包装的函数之外,其他所有的参数都已经确定好了。




想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-15 18:05:37 | 显示全部楼层
本帖最后由 Stubborn 于 2020-1-17 16:52 编辑

07节 -_- 利用装饰器强制函数上的类型检查

在这之前,稍微介绍`inspect.signature` 这个函数允许我们从一个可调用对象中提取出参数签名信息。下面看简单的例子。
def spam(x, y, z=42):
    pass

from inspect import signature
alex = signature(spam)
print(f"The parameter tuple of sig is->{alex}") # -> (x, y, z=42)

print(f"The parameter OrderedDict of sig is->{alex.parameters}")  # ->OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">), ('z', <Parameter "z=42">)])

print(f"The parameter z.name of sig is -> {alex.parameters['z'].name}") # -> z

print(f"The parameter z.default of sig is -> {alex.parameters['z'].default}") # -> 42

print(f"The parameter z.kind of sig is -> {alex.parameters['z'].kind}") # -> 1

我们想为函数参数添加强制性的类型检查功能,将其作为一种断言或者与调用者之间的契约。
# -*- coding: utf-8 -*-
# !/usr/bin/python3
"""
@ Version : ??
@ Author : Alex
@ Software : Pycharm
@ Time : 2020/1/2 下午2:20
"""

from inspect import signature
from functools import wraps


def typea_ssert(*ty_args, **ty_kwargs):
    def decorate(func):
        # If in optimized mode, disable type checking
        if not __debug__:
            return func

        # Map function argument names to supplied types
        sig = signature(func)
        # 对参数进行绑定
        bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments   #-> OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])

        @wraps(func)
        def wrapper(*args, **kwargs):
            bound_values = sig.bind(*args, **kwargs)  # ->  <BoundArguments (x=1, y=2, z=3)>
            # Enforce type assertions across supplied arguments
            for name, value in bound_values.arguments.items():
                if name in bound_types:
                    if not isinstance(value, bound_types[name]):
                        raise TypeError(
                            'Argument {} must be {}'.format(name, bound_types[name])
                        )
            return func(*args, **kwargs)

        return wrapper

    return decorate


# 我们会发现这个装饰器相当灵活,既允许指定函数参数的所有类型,也可以只指定一部分子集。
#       此外,类型既可以通过位置参数来指定,也可以通过关键字参数来指定。
@typea_ssert(int, z=int)
def spam(x, y, z=42):
    pass

spam(1, 2, 3) # Success
spam(1, "Alex", "bye") # Error

这节展示了一个高级的装饰器例子,引入了一些重要且有用的概念。首先,装饰器的一个特性就是它们只会在函数定义的时候应用一次。在某些情况下,我们可能想禁止由装饰器添加的功能。为了做到这点,只要让装饰器函数返回那个未经过包装的函数即可。在解决方案中,如果全局变量__debug__被设为False,下列代码就会返回未修改过的函数(当Python解释器以-O或-OO的优化模式执行的话,则属于这种情况)。接下来,编写这个装饰器比较棘手的地方在于要涉及对被包装函数的参数签名做检查。在这里,我们可选择的工具应该是inspect.signature()函数。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-16 21:40:55 | 显示全部楼层
本帖最后由 Stubborn 于 2020-1-17 17:15 编辑

08节 -_-  将装饰器定义为类的一部分

在类中定义一个装饰器是很直接的,但是首先我们需要理清装饰器将以什么方式来应用。具体来说就是以实例方法还是以类方法的形式应用。
from functools import wraps


class FunctionEmbellish(object):

    def function_one(self, function):
        """This is the instance decorator for FunctionEmbellish"""

        @wraps(function)
        def wrapper(*args, **kwargs):
            print("Tis is FunctionEmbellish instance decorator")
            return function(*args, **kwargs)

        return wrapper

    @classmethod
    def function_two(cls, function):
        """This is the static method"""

        @wraps(function)
        def wrapper(*args, **kwargs):
            print("Tis is FunctionEmbellish static method")
            return function(*args, **kwargs)

        return wrapper


# 应用展示,你应该要知道,有一个装饰器是来自实例化方法
embellish = FunctionEmbellish()


@embellish.function_one
def spam():
    ...


@FunctionEmbellish.function_two
def grok():
    ...

在类中定义装饰器乍看起来可能有些古怪,但是在标准库中也可以找到这样的例子。尤其是,内建的装饰器@property实际上是一个拥有getter()、setter()和deleter()方法的类, 每个方法都可作为一个装饰器。
示例如下:
class Person:
    # Create a property instance
    first_name = property()

    # Apply decorator methods
    @first_name.getter
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

在类中定义装饰器有个难理解的地方就是对于额外参数 self 或 cls 的正确使用。 尽管最外层的装饰器函数比如 function_one() 或 function_two() 需要提供一个 self 或 cls 参数, 但是在两个装饰器内部被创建的 wrapper() 函数并不需要包含这个 self 参数。 你唯一需要这个参数是在你确实要访问包装器中这个实例的某些部分的时候。其他情况下都不用去管它。

另外额外注意!!就是在涉及到继承的时候。 例如,假设你想让在A中定义的装饰器作用在子类B中。你需要像下面这样写:
class B(A):
    @A.decorator2
    def bar(self):
        pass
也就是说,装饰器要被定义成类方法并且你必须显式的使用父类名去调用它。 你不能使用 @B.decorator2 ,因为在方法定义时,这个类B还没有被创建。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-16 21:47:10 | 显示全部楼层
本帖最后由 Stubborn 于 2020-1-17 18:18 编辑

09节 -_- 将装饰器定义为类

问题->我们想用装饰器来包装函数,但是希望得到的结果是一个可调用的实例。
        我们需要装饰器既能在类中工作,也可以在类外部使用。

解决方法->要把装饰器定义成类实例,需要确保在类中实现__call__()和__get__()方法。
    例如,下面的代码定义了一个类,可以在另一个函数上添加一个简单的性能分析层:

import types
from functools import wraps


class Profiled:
    def __init__(self, func):
        wraps(func)(self)
        self.ncalls = 0

    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)


# 应用展示
@Profiled
def add(x, y):
    return x + y


class Span:
    @Profiled
    def bar(self, x):
        print(self, x)


print("--" * 5)
print(add(2, 3))
print(add(2, 3))
print(add.ncalls) # add 调用两次 -> return 2

print("--" * 5)
s = Span()
s.bar(2)
print(s.bar.ncalls)  # bar 调用一次 -> return 1

把装饰器定义成类通常是简单明了的。
但是,这里有一些相当微妙的细节值得做进一步的解释,尤其是计划将装饰器应用在实例的方法上时。
首先,这里对functools.wraps()函数的使用和在普通装饰器中的目的一样——意在从被包装的函数中拷贝重要的元数据到可调用实例中。
其次,解决方案中所展示的__get__()方法常常容易被忽视。如果省略掉__get__()并保留其他所有的代码,会发现当尝试调用被装饰的实例方法时会出现怪异的行为。
例如:

>>> s = Spam()
>>> s.bar(3)
Traceback (most recent call last):
...
TypeError: spam() missing 1 required positional argument: 'x'

出错的原因在于每当函数实现的方法需要在类中进行查询时,作为描述符协议(descriptor protocol)的一部分,它们的__get__()方法都会被调用,这部分内容在8.9节中已描述过。在这种情况下,__get__()的目的是用来创建一个绑定方法对象(最终会给方法提供self参数)。
下面的例子说明了其中的机理:
>>> s = Spam()
>>> def grok(self, x):
...     pass
...
>>> grok.__get__(s, Spam)
<bound method Spam.grok of <__main__.Spam object at 0x100671e90>>
>>>

在本节中,__get__()方法在这里确保了绑定方法对象会恰当地创建出来。type. MethodType()手动创建了一个绑定方法在这里使用。绑定方法只会在使用到实例的时候才创建。如果在类中访问该方法,__get__()的instance参数就设为None,直接返回Profiled实例本身。这样就使得获取实例的ncalls属性成为可能。
如果想在某些方面避免这种混乱,可以考虑装饰器的替代方案,即(05节 -_- 可自定义属性的装饰器)中描述过的利用闭包和nonlocal变量。示例如下:

def profiled(func):
    ncalls = 0
    @wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal ncalls
        ncalls += 1
        return func(*args, **kwargs)
    wrapper.ncalls = lambda: ncalls
    return wrapper

# Example
@profiled
def add(x, y):
    return x + y
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-16 22:36:47 | 显示全部楼层

10节 -_- 为类和静态方法提供装饰器

本帖最后由 Stubborn 于 2020-1-17 18:23 编辑

10节 -_- 为类和静态方法提供装饰器

将装饰器作用到类和静态方法上是简单而直接的,但是要保证装饰器在应用的时候需要放在@classmethod和@staticmethod之前。

示例如下:
# -*- coding: utf-8 -*-
# !/usr/bin/python3
import time
from functools import wraps


# A simple decorator
def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        print(end - start)
        return r

    return wrapper


# Class illustrating application of the decorator to different kinds of methods
class Spam:
    @timethis
    def instance_method(self, n):
        print(self, n)
        while n > 0:
            n -= 1

    @classmethod
    @timethis
    def class_method(cls, n):
        print(cls, n)
        while n > 0:
            n -= 1

    @staticmethod
    @timethis
    def static_method(n):
        print(n)
        while n > 0:
            n -= 1

如果装饰器的顺序搞错了,那么将得到错误提示。例如,如果像下面这样使用装饰器:
class Spam:
    ...
    @timethis
    @staticmethod
    def static_method(n):
        print(n)
        while n > 0:
            n -= 1
>>> Spam.static_method(1000000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "timethis.py", line 6, in wrapper
    start = time.time()
TypeError: 'staticmethod' object is not callable
>>>

这里的问题在于@classmethod和@staticmethod并不会实际创建可直接调用的对象。相反,它们创建的是特殊的描述符对象。因此,如果尝试在另一个装饰器中像函数那样使用它们,装饰器就会崩溃。确保这些装饰器出现在@classmethod和@staticmethod之前就能解决这个问题。

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-16 23:24:00 | 显示全部楼层

11节 -_- 装饰器为被包装函数增加参数

本帖最后由 Stubborn 于 2020-1-17 19:26 编辑

11节 -_- 装饰器为被包装函数增加参数

先前介绍,`inspect.signature` 这个函数允许我们从一个可调用对象中提取出参数签名信息。同时这个模块可以给我们创建函数的签名信息。示例如下:

from inspect import Signature, Parameter

parms = [
    Parameter("x",Parameter.POSITIONAL_OR_KEYWORD),
    Parameter("y",Parameter.POSITIONAL_OR_KEYWORD, default=5),
]
s = Signature(parms)
print(s)

我们既可以,获取到函数的签名(参数),又可以重新创建签名。所以编写一个装饰器为被包装的函数添加额外的参数,而且添加的参数不能影响到该函数已有的调用约定。就会变的简单起来示例如下。
# -*- coding: utf-8 -*-
# !/usr/bin/python3

from functools import wraps
import inspect

def optional_debug(func):
    """"""
    # 检查fun参数,是否带debug参数
    arg = inspect.signature(func).parameters.get("debug", None)
    if arg is not None :
        raise TypeError("debug argument already defined")

    @wraps(func)
    def wrapper(*args, debug=False, **kwargs):
        if debug: print("Calling", func.__name__)
        return func(*args, **kwargs)

    # 管理函数签名
    sig = inspect.signature(func)
    params = list(sig.parameters.values())
    params.append(inspect.Parameter(
        "debug",
        inspect.Parameter.KEYWORD_ONLY,
        default=False
    ))
    # 把原有的函数签名,附加到wrapper上
    wrapper.__signature__ = sig.replace(parameters=params)
    return wrapper


@optional_debug
def spam(a, b, c):
    return a+b+c

print(inspect.signature(spam))
print(spam(1, 2, 3))
print(spam(1, 2, 3, debug=True))

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-16 23:28:55 | 显示全部楼层
本帖最后由 Stubborn 于 2020-1-17 20:15 编辑

12节 -_- 利用装饰器给类定义打补丁

你想通过反省或者重写类定义的某部分来修改它的行为,但是你又不希望使用继承或元类的方式。

这种情况可能是类装饰器最好的使用场景了。例如,下面是一个重写了特殊方法 __getattribute__ 的类装饰器, 可以打印日志:
# -*- coding: utf-8 -*-
# !/usr/bin/python3

def log_getattribute(cls):
    """Get the original implementation"""
    orig_getattribute = cls.__getattribute__

    # Make a new definition
    def new_getattribute(self, name):
        print(f"getting: {name}")
        return orig_getattribute(self, name)

    # Attach to the class and return
    cls.__getattribute__ = new_getattribute
    return cls

# Example use
@log_getattribute
class test:
    def __init__(self, x):
        self.x = x

    def spam(self):
        """"""

T = test(3)
print(T.x)
T.spam()

或者你也可以使用继承,但是为了去理解它,你就必须知道方法调用顺序、super() 以及其它的继承知识。 某种程度上来讲,类装饰器方案就显得更加直观,并且它不会引入新的继承体系。它的运行速度也更快一些, 因为他并不依赖 super() 函数。

如果你系想在一个类上面使用多个类装饰器,那么就需要注意下顺序问题。 例如,一个装饰器A会将其装饰的方法完整替换成另一种实现, 而另一个装饰器B只是简单的在其装饰的方法中添加点额外逻辑。 那么这时候装饰器A就需要放在装饰器B的前面。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-17 19:26:57 | 显示全部楼层
本帖最后由 Stubborn 于 2020-1-17 20:32 编辑

13节 -_- 使用元类控制实例的创建

你想通过改变实例创建方式来实现单例、缓存或其他类似的特性。

元类(继承自type)可以控制类的实例化步骤,分别实现类的缓存,和单例类
import weakref

class Cached(type): # 缓存实例
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__cache = weakref.WeakValueDictionary()

    def __call__(self, *args):
        if args in self.__cache:
            return self.__cache[args]
        else:
            obj = super().__call__(*args)
            self.__cache[args] = obj
            return obj

# Example
class Spam(metaclass=Cached):
    def __init__(self, name):
        print('Creating Spam({!r})'.format(name))
        self.name = name
>>> a = Spam('Guido')
Creating Spam('Guido')
>>> b = Spam('Diana')
Creating Spam('Diana')
>>> c = Spam('Guido') # Cached
>>> a is b
False
>>> a is c # Cached value returned
True
>>>
class Singleton(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            return self.__instance

# Example
class Spam(metaclass=Singleton): # 单例类
    def __init__(self):
        print('Creating Spam')

>>> a = Spam()
Creating Spam
>>> b = Spam()
>>> a is b
True
>>> c = Spam()
>>> a is c
True
>>>

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-17 19:39:20 | 显示全部楼层
本帖最后由 Stubborn 于 2020-1-17 21:17 编辑

14节 -_-  捕获类的属性定义顺序

你想自动记录一个类中属性和方法定义的顺序, 然后可以利用它来做很多操作(比如序列化、映射到数据库等等)。

利用元类可以很容易的捕获类的定义信息。下面是一个例子,使用了一个OrderedDict来记录描述器的定义顺序:

# -*- coding: utf-8 -*-
# !/usr/bin/python3

from collections import OrderedDict


# 一组用于各自类型的描述符

class Typed:
    _expected_type = type(None)

    def __init__(self, name=None):
        self._name = name

    def __set__(self, instance, value):
        if not isinstance(value, self._expected_type):
            raise TypeError(f"Expected {self._expected_type}")
        instance.__dict__[self._name] = value


class Integer(Typed):
    _expected_type = int


class Float(Typed):
    _expected_type = float


class String(Typed):
    _expected_type = str


# Metaclass that uses an OrderedDict for class body
class OrderedMeta(type):

    def __new__(cls, name, bases, attrs):
        d = dict(attrs)
        order = []
        for name, value in attrs.items():
            if isinstance(value, Typed):
                value._name = name
                order.append(name)
        d["_order"] = order
        return type.__new__(cls, name, bases, d)

    @classmethod
    def __prepare__(mcs, name, bases):
        return OrderedDict()

在这个元类中,执行类主体时描述器的定义顺序会被一个 OrderedDict``捕获到, 生成的有序名称从字典中提取出来并放入类属性 ``_order 中。这样的话类中的方法可以通过多种方式来使用它。 例如,下面是一个简单的类,使用这个排序字典来实现将一个类实例的数据序列化为一行CSV数据:
class Structure(metaclass=OrderedMeta):
    def as_csv(self):
        return ','.join(str(getattr(self,name)) for name in self._order)


class Stock(Structure):
    name = String()
    shares = Integer()
    price = Float()
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

s = Stock("Good", 100, 101.1)
print(s.as_csv())

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-17 19:41:29 | 显示全部楼层
14节 -_-  捕获类的属性定义顺序
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-17 19:46:04 | 显示全部楼层
15节 -_- 定义有可选参数的元类
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-17 19:47:30 | 显示全部楼层
16节 -_- *args和**kwargs的强制参数签名
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-1-17 19:48:06 | 显示全部楼层
17节
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2020-1-17 19:48:38 | 显示全部楼层
19节
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-18 14:36

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表