加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Python > 正文

python3-cookbook笔记:第九章 元编程

发布时间:2020-12-20 10:07:34 所属栏目:Python 来源:网络整理
导读:python3-cookbook中每个小节以问题、解决方案和讨论三个部分探讨了Python3在某类问题中的最优解决方式,或者说是探讨Python3本身的数据结构、函数、类等特性在某类问题上如何更好地使用。这本书对于加深Python3的理解和提升Python编程能力的都有显著帮助,特

python3-cookbook中每个小节以问题、解决方案和讨论三个部分探讨了Python3在某类问题中的最优解决方式,或者说是探讨Python3本身的数据结构、函数、类等特性在某类问题上如何更好地使用。这本书对于加深Python3的理解和提升Python编程能力的都有显著帮助,特别是对怎么提高Python程序的性能会有很好的帮助,如果有时间的话强烈建议看一下。
本文为学习笔记,文中的内容只是根据自己的工作需要和平时使用写了书中的部分内容,并且文中的示例代码大多直接贴的原文代码,当然,代码多数都在Python3.6的环境上都验证过了的。不同领域的编程关注点也会有所不同,有兴趣的可以去看全文。
python3-cookbook:https://python3-cookbook.readthedocs.io/zh_CN/latest/index.html

?

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

通常,在定义装饰器函数时,总是应该记得使用functools.wraps来注解被包装的函数,虽然在平时不加这个wraps装饰器感觉也工作的很好,但其实不加的话被装饰函数的元信息是被丢失了的,比如__name__、__doc__等信息,为了被装饰函数的元信息不被丢失,就需要加上这个wraps装饰器。

需要注意的是wraps应该放在包装原始函数的那个函数定义之上,即参数为func的函数的返回值函数定义之上,特别是带参数的装饰器定义之中更要注意。

import time
from functools  wraps


def timethis(func):
    """普通的装饰器"""

    def wrapper(*args,**kwargs):
        start = time.time()
        result = func(*args,1)">kwargs)
        end = time.time()
        print(func.__name__,end - start)
        return result

     wrapper


 timethis_wraps(func):
    有wraps的装饰器"""

    @wraps(func)
     wrapper


@timethis
 countdown(n):
    timethis装饰函数"""
    while n > 0:
        n -= 1


@timethis_wraps
 countdown_wraps(n):
    timethis_wraps装饰函数


countdown(1000)
print(countdown.__name____doc__)

countdown_wraps(1000print(countdown_wraps.__doc__)
countdown 0.0
wrapper
None
countdown_wraps 0.0
countdown_wraps
timethis_wraps装饰函数

?

9.6 带可选参数的装饰器

你想定义一个装饰器,但使用的时候既可以传递参数给它,也可以不传任何参数给它,那么可以参考以下示例,利用functools.partial来实现。

 logging
 wraps,partial


# 星号*的作用是迫使其后面的参数,即level,name,message三个参数,都必须使用关键字参数的形式传参
def logged(func=None,*,level=logging.DEBUG,name=None,message=None):
    if func is None:
         此处partial的作用为返回一个函数,但它的level,name,message三个参数是已经指定好了的
         使用的时候只需要传入剩下的参数即可,相当于:def logged(func=None):...
        return partial(logged,level=level,name=name,1)">message)

    logname = name if name else func.__module__
    log = logging.getLogger(logname)
    logmsg = message if message __name__

     使用wraps装饰器保证func函数的元信息是正确完整的
    @wraps(func)
    kwargs):
        log.log(level,logmsg)
        return func(*args,1)">kwargs)

     不传参示例
@logged
 add(x,y):
    return x + y


 传参示例
@logged(level=logging.CRITICAL,name='example' spam():
    print(Spam!)


print(add(2,3))   输出:5
spam()   输出:Spam!

?

?

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

如果需要在装饰器中记录信息或绑定信息时,那么可以考虑将装饰器定义在类中,需要注意的是装饰器方法为类方法和实例方法时使用的也是对应的类或者实例,而且functools.wraps包装的函数是不用传递额外的cls或self参数的。

class A:
     实例方法
     decorator1(self,func):
         wrapper函数不用定义参数self
        @wraps(func)
        kwargs):
            Decorator 1)
            kwargs)

         wrapper

     类方法
    @classmethod
     decorator2(cls,1)"> wrapper函数不用定义参数cls
Decorator 2 wrapper


a = A()


 使用实例进行调用
@a.decorator1
pass


 使用类进行调用
@A.decorator2
 grok():
    pass

?

?

9.21 避免重复的属性方法

如果在类中定义许多重复的对于属性的逻辑代码,可以考虑如下示例进行简化代码:需要检查name属性是否为str类型,检查age属性是否为int类型。

普通方法定义属性的检查:

 Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    @property
     name(self):
         self._name

    @name.setter
     name(self,value):
        if not isinstance(value,str):
            raise TypeError(name must be a string)
        self._name = value

    @property
     age(self):
         self._age

    @age.setter
     age(self,int):
            age must be an int)
        self._age = value

简化后的代码:

 此函数返回一个属性对象,用于检查赋的值是否为指定的类型
# 在类中使用这个函数时,跟把这个函数中的代码放到类中定义是一样的,作用就只是把重复使用的代码提出来了
 typed_property(name,expected_type):
    storage_name = _' + name

    @property
     prop(self):
         getattr(self,storage_name)

    @prop.setter
     prop(self,expected_type):
            {} must be a {}.format(name,expected_type))
        setattr(self,storage_name,value)

     prop


 Person:
    name = typed_property(name,str)
    age = typed_property(age name
        self.age = age

使用functools.partial改良的代码:

 partial

String = partial(typed_property,expected_type=str)
Integer = partial(typed_property,1)">int)


 Person:
    name = String()
    age = Integer()

     name
        self.age = age

?

?

9.22 定义上下文管理器的简单方法

自定义上下文管理器有两种方式,一种是使用contextlib.contextmanager装饰器,但是这种方式应该只用来定义函数的上下文管理器,如果是对象,比如文件、网络连接或者锁等,就应该使用另一种方式,即给对应的类实现__enter__()方法和__exit__()方法,在进入with语句时会调用__enter__()方法,退出时调用__exit__()方法,对象的方式容易懂一点,所以这里就只给出函数方式的示例。

from contextlib  contextmanager


@contextmanager
 list_transaction(orig_list):
    working = list(orig_list)
    yield working
     如果没有报错,对列表orig_list的任何修改才会生效
    orig_list[:] = working


items = [1,2,3]
with list_transaction(items) as working:
    working.append(4)
    working.append(5print(items)

items = [1,1)">)
    raise RuntimeError(oops!print(items)
[1,3,4,5]
Traceback (most recent call last):
  File "Z:/Projects/Daily Test/test.py",line 120,in <module>
    raise RuntimeError('oops!')
RuntimeError: oops!

?

?

9.23 在局部变量域中执行代码

在使用exec()时,都应该想一下是否有更好的方案或者可以替代的方案。当然,如果是必须要使用exec(),那么需要注意exec()执行时,它使用的局部变量域是拷贝自真实局部变量域,而不是直接使用的真实局部变量域,如果需要获取exec局部变量域中的值,可以使用locals(),locals()返回一个真实局部变量域的拷贝,也是指向的exec()拷贝的局部变量域。

参考以下测试代码和示例:

>>> a = 13
>>> exec(b = a + 1)
>>> b
14
>>>  test():
    a = 13
    (b)

    
>>>  b并没有在真实局部变量域中,所以会报错
>>> test()
Traceback (most recent call last):
  File "<pyshell#3>",line 1,in <module>
    test()
  File <pyshell#1>in test
    (b)
NameError: name b' is  defined
>>> 
>>>  test1():
    x = 0
    x += 1(x)

    
>>> test1()   x的值并没有被修改
0
>>> 
>>>  test2():
    x = 0
    loc = locals()
    before:after:x = 想要获取修改后的值,可以从locals()中获取
    x = loc[x]
     注意,再次运行locals()时,原来的值会被覆盖掉
    x = 444
    loc =new: test2()
before: {: 0}
after: {': 1,loc: {...}}
x = 0
x = 1
new: {': 444,1)">: {...}}
>>> 

?

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读