元编程

在函数上添加包装器

问题:

  你想在函数上添加一个包装器,增加额外的操作处理(比如日志、计时等)

解决方案:

  如果你想使用额外的代码包装一个函数,可以定义一个装饰器函数,例如:

 1 import  time
 2 from functools import wraps
 3 
 4 
 5 def timethis(func):
 6     @wraps(func)
 7     def wrapper(*args,**kwargs):
 8         start = time.time()
 9         result = func(*args,**kwargs)
10         end = time.time()
11         print(func.__name__,end-start)
12         return result
13     return wrapper
14 
15 @timethis           #等同于timethis(countdown)
16 def countdown(n):
17     while n > 0:
18         n -= 1
19 
20 countdown(100000)
21 countdown(1000000)

以上代码的执行结果为:

countdown 0.008307218551635742
countdown 0.10066509246826172

创建装饰器时保留函数元信息

问题:

  你写了一个装饰器作用在某个函数上,但是这个函数的重要的元信息比如名字、文档字符串、注解和参数签名都丢失了

解决方案:

  任何时候你定义装饰器的时候,都应该使用functools 库中的@wraps 装饰器来注解底层包装函数。例如:

 1 import time
 2 from functools import wraps
 3 
 4 def timethis(func):
 5     @wraps(func)
 6     def wrapper(*args, **kwargs):
 7         start = time.time()
 8         result = func(*args, **kwargs)
 9         end = time.time()
10         print(func.__name__, end-start)
11         return result
12     return wrapper
13 
14 @timethis
15 def countdown(n:int):
16     """
17     装饰器内部调用的函数
18     :param n:
19     :return:
20     """
21     while n>0:
22         n -= 1
23 
24     return 'countdown'
25 
26 countdown(100000)
27 print('name:', countdown.__name__)
28 print('doc:', countdown.__doc__)
29 print('annotations:', countdown.__annotations__)
30 
31 '''
32 @wraps 有一个重要特征是它能让你通过属性wrapped 直接访问被包装函数。例如:
33 '''
34 print('wrapped:', countdown.__wrapped__(100000))
35 
36 '''
37 wrapped 属性还能让被装饰函数正确暴露底层的参数签名信息。例如:
38 '''
39 from inspect import signature
40 print('signatrue:', signature(countdown))

以上代码的执行结果为:

countdown 0.008561134338378906
name: countdown
doc: 
    装饰器内部调用的函数
    :param n:
    :return:
    
annotations: {'n': <class 'int'>}
wrapped: countdown
signatrue: (n:int)

解除一个装饰器

问题:

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

解决方案:

  假设装饰器是通过@wraps来实现的,那么你可以通过访问__wrapped__ 属性来访问原始函数:

 1 from functools import wraps
 2 
 3 
 4 def decorator(func):
 5     @wraps(func)
 6     def wrapper(x, y):
 7         result = func(x, y)
 8         return result
 9     return wrapper
10 
11 @decorator
12 def add(x, y):
13     return x + y
14 
15 orig_add = add.__wrapped__
16 print('orig_add:', orig_add(3, 5))

以上代码的执行结果为:

orig_add: 8

定义一个带参数的装饰器

问题:

  你想定义一个可以接受参数的装饰器

解决方案:

  我们用一个例子详细阐述下接受参数的处理过程。假设你想写一个装饰器,给函数添加日志功能,当时允许用户指定日志的级别和其他的选项。下面是这个装饰器的定义和使用示例:

 1 from functools import wraps
 2 import logging
 3 
 4 def logged(level, name=None, message=None):
 5     """
 6     Add logging to a function. level is the logging
 7     level, name is the logger name, and message is the
 8     log message. If name and message aren't specified,
 9     they default to the function's module and name.
10     """
11     def decorate(func):
12         logname = name if name else func.__module__
13         log = logging.getLogger(logname)
14         logmsg = message if message else func.__name__
15 
16         @wraps(func)
17         def wrapper(*args, **kwargs):
18             log.log(level, logmsg)
19             return func(*args, **kwargs)
20         return wrapper
21     return decorate
22 
23 @logging(logging.DEBUG)         #等同于logging(loggin.DEBUG)(add)(x,y)
24 def add(x, y):
25     return x +y
26 
27 
28 @logged(logging.CRITICAL, 'example')
29 def spam():
30     print('Spam!')

可自定义属性的装饰器

问题:

  你想写一个装饰器来包装一个函数,并且允许用户提供参数在运行时控制装饰器行为

解决方案:

  引入一个访问函数,使用nolocal 来修改内部变量。然后这个访问函数被作为一个属性赋值给包装函数

 1 def logged(level, name=None, message=None):
 2     '''
 3     Add logging to a function. level is the logging
 4     level, name is the logger name, and message is the
 5     log message. If name and message aren't specified,
 6     they default
 7     '''
 8     def decorate(func):
 9         logname = name if name else func.__module__
10         log = logging.getLogger(logname)
11         logmsg = message if message else func.__name__
12 
13         @wraps(func)
14         def wrapper(*args, **kwargs):
15             log.log(level, logmsg)
16             return func(*args, **kwargs)
17 
18         @attch_wrapper(wrapper)
19         def set_level(newlevel):
20             nonlocal level
21             level = newlevel
22 
23         @attch_wrapper(wrapper)
24         def set_message(newmsg):
25             nonlocal logmsg
26             logmsg = newmsg
27 
28         return wrapper
29 
30     return decorate
31 
32 @logged(logging.DEBUG)
33 def add(x, y):
34     return x + y
35 
36 @logged(logging.CRITICAL, 'example')
37 def spam():
38     print('Spam!')
39 
40 import logging
41 logging.basicConfig(level=logging.DEBUG)
42 print(add(10, 11))

以上代码的执行结果为:

21
DEBUG:__main__:add

带可选参数的装饰器

 问题:

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

解决方案:

  下面是日志装饰器的一个修改版本:

 1 from functools import wraps, partial
 2 import logging
 3 
 4 
 5 def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
 6     if func is None:
 7         return partial(logged, level=level, name=name, message=message)
 8 
 9     logname = name if name else func.__module__
10     log = logging.getLogger(logname)
11     logmsg = message if message else func.__name__
12 
13     @wraps(func)
14     def wrapper(*args, **kwargs):
15         log.log(level, logmsg)
16         return func(*args, **kwargs)
17 
18     return wrapper
19 
20 @logged
21 def add(x, y):
22     return x + y
23 
24 @logged(level=logging.CRITICAL, name='example')
25 def spam():
26     print('Spam!')
27 
28 print(add(10, 20))
29 print('*'*40)
30 spam()

以上代码的执行结果为:

30
spam
****************************************
Spam!

总结:

  这里提到的这个问题就是通常所说的编程一致性问题。当我们使用装饰器的时候,大部分程序员习惯了要么不给它们传递任何参数,要么给它们传递确切参数。其实从技术上来讲,我们可以定义一个所有参数都是可选的装饰器,就像下面这样: 

@logged
def add(x, y):
    """
    等同于 add = logged(add)
    这时候,被装饰函数会被当做第一个参数直接传递给logged 装饰器。因此,logged() 中的第一个参数就是被包装函数本身。所有其他参数都必须有默认值
    """
    return x + y

@logged(level=logging.CRITICAL, name='example')
def spam():
    """
    等同于 spam = logged(level=logging.CRITICAL, name='example')(spam)
    
    初始调用logged() 函数时,被包装函数并没有传递进来。因此在装饰器内,它必
    须是可选的。这个反过来会迫使其他参数必须使用关键字来指定。并且,但这些参数
    被传递进来后,装饰器要返回一个接受一个函数参数并包装它的函数。为了这样做,我
    们使用了一个技巧,就是利用functools.partial 。它会返回一个未完全初始化自
    身,除了被包装函数外其他参数都已经确定下来了。
    """
    print('Spam!')

利用装饰器强制函数上的类型检查

 问题:

   作为某种编程规约,你想在对函数参数进行强制类型检查

解决方案:

  在演示实际代码前,先说明我们的目标:能对函数参数类型进行断言,类似下面这样:

 1 from inspect import signature
 2 from functools import wraps
 3 
 4 
 5 def typeassert(*ty_args, **ty_kwargs):
 6     def decorate(func):
 7         if not __debug__:
 8             return func
 9 
10         sig = signature(func)
11         bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments
12 
13         @wraps(func)
14         def wrapper(*args, **kwargs):
15             bound_values = sig.bind(*args, **kwargs)
16             for name, value in bound_values.arguments.items():
17                 if name in bound_types:
18                     if not isinstance(value, bound_types[name]):
19                         raise TypeError(
20                                 'Argument {} must be {}'.format(name, bound_types[name])
21                                 )
22             return func(*args, **kwargs)
23         return wrapper
24     return decorate
25 
26 
27 @typeassert(int, z=int)
28 def spam(x, y, z=42):
29     print(x, y, z)
30 
31 spam(1, 2, 3)
32 spam(1, 'hello')
33 '''
34 报错
35 spam(1, 'hello', 'world')
36 TypeError: Argument z must be <class 'int'>
37 '''

以上代码的执行结果为:

1 2 3
1 hello 42

其次, 这里还对被包装函数的参数签名进行了检查, 我们使用了inspect.signature() 函数。简单来讲,它运行你提取一个可调用对象的参数签名信息。例如:

 1 from inspect import signature
 2 
 3 
 4 def spam(x, y, z=42):
 5     return x + y
 6 
 7 sig = signature(spam)
 8 print('sig:', sig)
 9 print('sig parameters:', sig.parameters)
10 print('sig paramenters z:', sig.parameters['z'].name)
11 print('sig paramenters name:', sig.parameters['z'].default)
12 print('sig paramenters kind:', sig.parameters['z'].kind)

以上代码的执行结果为:

sig: (x, y, z=42)
sig parameters: OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">), ('z', <Parameter "z=42">)])
sig paramenters z: z
sig paramenters name: 42
sig paramenters kind: POSITIONAL_OR_KEYWORD

将装饰器定义为类的一部分

 问题:

  你想在类中定义装饰器,并将其作用在其他函数或方法上

解决方案

  在类里面定义装饰器很简单,但是你首先要确认它的使用方式。比如到底是作为一个实例方法还是类方法。下面我们用例子来阐述它们的不同:

 1 from functools import wraps
 2 
 3 
 4 class A:
 5     def decorator1(self, func):
 6         @wraps(func)
 7         def wrapper(*args, **kwargs):
 8             print('Decorator 1')
 9             return func(*args, **kwargs)
10         return wrapper
11 
12     @classmethod
13     def decorator2(cls, func):
14         @wraps(func)
15         def wrapper(*args, **kwargs):
16             print('Decorator 2')
17             return func(*args, **kwargs)
18         return wrapper
19 
20 a = A()
21 
22 @a.decorator1
23 def spam():
24     pass
25 
26 @A.decorator2
27 def grok():
28     pass

为类和静态方法提供装饰器

问题:

  你想给类或静态方法提供装饰器

解决方案:

  给类或静态方法提供装饰器是很简单的,不过要确保装饰器在@classmethod 或@staticmethod 之前。例如:

 1 import time
 2 from functools import wraps
 3 
 4 
 5 def timethis(func):
 6     @wraps(func)
 7     def wrapped(*args, **kwargs):
 8         start = time.time()
 9         result = func(*args, **kwargs)
10         end = time.time()
11         print(end-start)
12         return result
13     return wrapped
14 
15 
16 class Spam:
17     @timethis
18     def instnce_method(self, n):
19         print(self, n)
20         while n > 0:
21             n -= 1
22 
23     @classmethod
24     @timethis
25     def class_method(cls, n):
26         print(cls, n)
27         while n > 0:
28             n -= 1
29 
30 s = Spam()
31 s.instnce_method(100000)
32 Spam.class_method(100000)

以上代码的执行结果为:

<__main__.Spam object at 0x101445630> 100000
0.011115074157714844
<class '__main__.Spam'> 100000
0.009546995162963867

装饰器为被包装函数增加参数

问题:

  你想在装饰器中给被包装函数增加额外的参数,但是不能影响这个函数现有的调用规则

解决方案:

  可以使用关键字参数来给被包装函数增加额外参数。考虑下面的装饰器:

 1 from  functools import wraps
 2 
 3 
 4 def optional_debug(func):
 5     @wraps(func)
 6     def wrapper(*args,debug=False, **kwargs):
 7         if debug:
 8             print('Calling', func.__name__)
 9         return func(*args, **kwargs)
10 
11     return wrapper
12 
13 @optional_debug
14 def spam(a, b, c):
15     print(a, b, c)
16 
17 spam(1, 2, 3)
18 spam(1, 2, 3, debug=True)

以上代码的执行结果为:

1 2 3
Calling spam
1 2 3

使用装饰器扩充类的功能

问题:

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

解决方案:

  这种情况可能是类装饰器最好的使用场景了。例如,下面是一个重写了特殊方法getattribute 的类装饰器,可以打印日志:

 1 def log_getattribute(cls):
 2     orig_getattribute = cls.__getattribute__
 3 
 4     def new_getattribute(self, name):
 5         print('getting:', name)
 6         return orig_getattribute(self, name)
 7 
 8     cls.__getattribute__ = new_getattribute
 9     return cls
10 
11 
12 @log_getattribute
13 class A:
14     def __init__(self, x):
15         self.x = x
16 
17     def spam(self):
18         pass
19 
20 a = A(50)
21 print(a.x)
22 a.spam()

以上代码的执行结果为:

getting: x
50
getting: spam

使用元类控制实例的创建

问题:

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

解决方案:

  你可以定义一个元类并自己实现call () 方法。为了演示,假设你不想任何人创建这个类的实例:

 1 class NoInstances(type):
 2     def __call__(self, *args, **kwargs):
 3         raise TypeError("Can't instantiate directly")
 4 
 5 
 6 class Spam(metaclass=NoInstances):
 7     @staticmethod
 8     def grok(x):
 9         print('不允许实例化~')
10         print('Spam.grok')
11 
12 Spam.grok(50)
13 
14 '''
15 s = Spam()
16 s.grok()
17 实例化这种不能调用,报错~
18 '''
19 
20 #实现单例模式
21 class Singleton(type):
22     def __init__(self, *args, **kwargs):
23         self.__instance = None
24         super().__init__(*args, **kwargs)
25 
26     def __call__(self, *args, **kwargs):
27         if self.__instance is None:
28             self._instance = super().__call__(*args, **kwargs)
29             return self.__instance
30         else:
31             return self.__instance
32 
33 
34 class Spam(metaclass=Singleton):
35     def __init__(self):
36         print('Creating Spam')
37 
38 a = Spam()
39 b = Spam()
40 c = Spam()
41 print(a is b and b is c)

以上代码的执行结果为:

不允许实例化~
Spam.grok
Creating Spam
Creating Spam
Creating Spam
True

定义有可选参数的元类

问题:

  你想定义一个元类,允许类定义时提供可选参数,这样可以控制或配置类型的创建过程

解决方案:

  在定义类的时候,Python 允许我们使用‘‘metaclass‘‘关键字参数来指定特定的元类。例如使用抽象基类:

 1 from abc import ABCMeta, abstractclassmethod
 2 
 3 
 4 class IStream(metaclass=ABCMeta):
 5     @abstractclassmethod
 6     def read(self, maxsize=None):
 7         pass
 8 
 9     @abstractclassmethod
10     def write(self, date):
11         pass
12 
13 
14 class MyMeta(type):
15     @classmethod
16     def __prepare__(cls, name, bases, *, debug=False, synchronize=False):
17         pass
18         super().__prepare__(name, bases)
19 
20     def __new__(cls, name, bases, ns, *, debug=False, synchronize=False):
21         pass
22         return super().__new__(cls, name, bases, ns)
23 
24     def __init__(cls, name, bases, ns, *, debug=False, synchronize=False):
25         pass
26         super().__init__(name, bases, ns)
27 
28 
29 class Spam(metaclass=MyMeta, debug=True, synchronize=True):
30     pass

*args 和**kwargs 的强制参数签名

问题:

  你有一个函数或方法,它使用*args 和**kwargs 作为参数,这样使得它比较通用,但有时候你想检查传递进来的参数是不是某个你想要的类型

解决方案: 

  对任何涉及到操作函数调用签名的问题,你都应该使用inspect 模块中的签名特性。我们最主要关注两个类:Signature 和Parameter 。下面是一个创建函数前面的交互例子: 

 1 from inspect import Signature, Parameter
 2 
 3 
 4 parms = [
 5         Parameter('x', Parameter.POSITIONAL_OR_KEYWORD),
 6         Parameter('y', Parameter.POSITIONAL_OR_KEYWORD, default=42),
 7         Parameter('z', Parameter.KEYWORD_ONLY, default=None)
 8         ]
 9 
10 sig = Signature(parms)
11 print(sig)
12 
13 
14 def func(*args, **kwargs):
15     bound_values = sig.bind(*args, **kwargs)
16     for name, value in bound_values.arguments.items():
17         print(name, value)
18 
19 func(1, 2, z=3)
20 print(''.center(30, '*'))
21 func(1)
22 print(''.center(30, '*'))
23 func(1, z=3)
24 print(''.center(30, '*'))
25 func(y=2, x=1)
26 print(''.center(30, '-'))
27 '''
28 报错鸟,为啥,因为只能传递三个参数呀,你四不四傻~
29 '''
30 #func(1, 2, 3, 4)
31 
32 
33 
34 '''
35 另外一种写法
36 '''
37 print('另外一种写法'.center(30, '*'))
38 
39 from inspect import Signature, Parameter,signature
40 def make_sig(*names):
41     parms = [Parameter(name,Parameter.POSITIONAL_OR_KEYWORD) for name in names]
42     return Signature(parms)
43 
44 
45 class Structure:
46     __signature__ = make_sig()
47 
48     def __init__(self, *args, **kwargs):
49         bound_values = self.__signature__.bind(*args, **kwargs)
50         for name, value in bound_values.arguments.items():
51             setattr(self, name, value)
52 
53 
54 class Stock(Structure):
55     __signature__ = make_sig('name', 'shares', 'price')
56 
57 
58 class Point(Structure):
59     __signature__ = make_sig('x', 'y')
60 
61 
62 print(signature(Stock))
63 s1 = Stock('ACME', 100, 490.1)
64 '''
65 报错
66 '''
67 #s2 = Stock('ACME', 100)
68 #s3 = Stock('ACME', 100, 490.1, shares=50)

以上代码的执行结果为:

(x, y=42, *, z=None)
x 1
y 2
z 3
******************************
x 1
******************************
x 1
z 3
******************************
x 1
y 2
------------------------------
************另外一种写法************
(name, shares, price)

避免重复的属性方法

问题:

  你在类中需要重复的定义一些执行相同逻辑的属性方法,比如进行类型检查,怎样去简化这些重复代码呢?

解决方案:

  考虑下一个简单的类,它的属性由属性方法包装:

 1 class Person:
 2     def __init__(self, name, age):
 3         self.name = name
 4         self.age = age
 5 
 6     @property
 7     def name(self):
 8         return self._name
 9 
10     @name.setter
11     def name(self, value):
12         if not isinstance(value, str):
13             raise TypeError('name must be a string')
14         self._name = value
15 
16     @property
17     def age(self):
18         return self._age
19 
20     @name.setter
21     def age(self, value):
22         if not isinstance(value, int):
23             raise TypeError('age must be an int')
24         self._age = value
25 
26 
27 #针对上面的问题,我们需要改良一下
28 def typed_property(name, expected_type):
29     storage_name = '_' + name
30 
31     @property
32     def prop(self):
33         return getattr(self, storage_name)
34 
35     @prop.setter
36     def prop(self, value):
37         if not isinstance(value, expected_type):
38             raise TypeError('{} must be a {}'.format(name, expected_type))
39         setattr(self, storage_name, value)
40 
41     return prop
42 
43 
44 #测试实例
45 class Person:
46     name = typed_property('name', str)
47     age = typed_property('age', int)
48 
49     def __init__(self, name, age):
50         self.name = name
51         self.age = age
52 
53 '''
54 报错
55 p = Person('这尼玛的是大佬', '18')
56 '''
57 
58 #正常显示输出
59 p = Person('这尼玛的是大佬', 18)

在局部变量域中执行代码

问题:

  你想在使用范围内执行某个代码片段,并且希望在执行后所有的结果都不可见

解决方案: 

  为了理解这个问题,先试试一个简单场景。首先,在全局命名空间内执行一个代码片段:

 1 a = 13
 2 exec('b = a + 10')
 3 print('b的结果为为:', b)
 4 
 5 '''
 6 #执行下面的函数会报错NameError: name 'c' is not defined
 7 def test():
 8     a = 13
 9     exec('c = a + 5')
10     print(c)
11 test()
12 '''
13 
14 #解决方法,local()可以获取函数内部的所有变量并以字典的方式返回,
15 # 变量名称是字典的key,变量的赋值是value,通过获取local中的c的局部变量在赋值给变量即可
16 def test():
17     a = 13
18     loc = locals()
19     print('before:', loc)
20     exec('c = a + 5')
21     c = loc['c']
22     print('c:', c)
23     print('after:', loc)
24 test()

以上代码的执行结果为:

b的结果为为: 23
before: {'a': 13}
c: 18
after: {'a': 13, 'loc': {...}, 'c': 18}
原文地址:https://www.cnblogs.com/demon89/p/7325941.html