Pyhton Cookbook 学习笔记 ch9_02 元编程[通俗易懂]

Pyhton Cookbook 学习笔记 ch9_02 元编程[通俗易懂]【传送门】9.8将装饰器定义为类的一部分问题:想在类中定义装饰器,并作用在其他的函数上方案:在类中定义装饰器首先要确定它的使用方法,是作为一个实例方法还是作为一个类方法fromfunctoolsimportwrapsclassA:#作为一个实例方法defdecorator1(self,func):@wraps(func)…

大家好,又见面了,我是你们的朋友全栈君。

传送门

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

  • 问题:想在类中定义装饰器,并作用在其他的函数上
  • 方案:在类中定义装饰器首先要确定它的使用方法,是作为一个实例方法还是作为一个类方法
from functools import wraps
class A:
    #作为一个实例方法
    def decorator1(self,func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("decorator 1")
            return func(*args,**kwargs)
        return wrapper
    #作为一个类方法
    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("decorator 2")
            return func(*args,**kwargs)
        return wrapper
#作为实列使用
a = A()
@a.decorator1
def spam():
    print('spam')
#作为类方法使用
@A.decorator2
def grok():
    print("grok")
  • 在类中定义装饰器很奇怪,但是标准库中有很多这种方式,@property装饰器实际上是一个类,他内部定义了三个方法getter()、setter()、deleter()每个方法都是装饰器
class Person:
    first_name = property()
    @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

9.9 将装饰器定义为类

  • 问题:想使用一个装饰器去包装函数,但是希望返回一个可以调用的实例。需要让装饰器可以同时工作在类定义的内部和外部
  • 方案:为了将装饰器定义成一个实例,需要确保它实现了__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 Spam:
    @Profiled
    def bar(self, x):
        print(self, x)
add(2,3)
5
add(4,5)
9
add.ncalls
2
s = Spam()
s.bar(1)
<__main__.Spam object at 0x00B65350> 1
s.bar(2)
<__main__.Spam object at 0x00B65350> 2
s.bar(10)
<__main__.Spam object at 0x00B65350> 10
Spam.bar.ncalls
3
  • 也可以使用闭包和nonlocal变量来实现装饰器
import types
from functools import wraps
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
@profiled
def add(x,y):
    return x+y
add(2,3)
5
add(3,4)
7
add.ncalls()
2

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

  • 问题:想要给类或者静态方法提供装饰器
  • 方案:在@classmethod或者@staticmethod之前定义
import time
from functools import wraps
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 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
  • 装饰后的类和静态方法可以正常工作,但是增加了计时功能
s = Spam()
s.instance_method(10000000)
<__main__.Spam object at 0x00B79510> 10000000
0.7324073314666748
Spam.class_method(1000000)
<class '__main__.Spam'> 1000000
0.12408709526062012
Spam.static_method(10000000)
10000000
0.8057591915130615
  • 注意:在使用的时候上面代码中@staticmethod和@timethis的顺序不能调换

  • 如果想要定义一个抽象类方法,可以使用下面的那样:

from abc import ABCMeta, abstractmethod
class A(metaclass=ABCMeta):
    @classmethod
    @abstractmethod
    def method(cls):
        pass
  • 上面的两个@也是不可以交换顺序的

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

  • 问题:想要给被包装的函数增加额外的参数,但是不可以改变该函数的现有调用规则
  • 方案:可以使用关键字参数来给被包装的函数增加额外的参数
from functools import wraps
def optional_debug(func):
    @wraps(func)
    def wrapper(*args, debug=False, **kwargs):
        if debug:
            print('Calling ', func.__name__)
        return func(*args,**kwargs)
    return wrapper
@optional_debug
def spam(a,b,c):
    print(a,b,c)
spam(1,2,3)
1 2 3
spam(1,2,3,debug=True)
Calling  spam
1 2 3
  • 通过装饰器给被包装的函数增加参数并不常见,但有事该方法可以避免代码的重复
def a(x,debug=False):
    if debug:
        print("calling a")
def b(x,y,z,debug=False):
    if debug:
        print("calling b")
def c(x,y,debug=False):
    print("calling c")
# 我们可以将上述代码重写为:
from functools import wraps
import inspect
def optional_debug(func):
    if 'debug' in inspect.getfullargspec(func).args:
        raise TypeError("debug argument already defined")
    @wraps(func)
    def wrapper(*args, debug=False, **kwargs):
        if debug:
            print("calling ",func.__name__)
        return func(*args,**kwargs)
    return wrapper

@optional_debug
def a(x):
    pass
@optional_debug
def b(x,y,z):
    pass
@optional_debug
def c(x,y):
    pass

9.12 使用装饰器扩充类的功能

  • 问题:想通过反省或者重写类定义来修改其部分i行为,但是不想使用继承
  • 方案:使用类装饰器
def log_getattribute(cls):
    orig_getattribute = cls.__getattribute__
    def new_getattribute(self, name):
        print("getting : ",name)
        return orig_getattribute(self, name)
    cls.__getattribute__ = new_getattribute
    return cls

@log_getattribute
class A:
    def __init__(self, x):
        self.x = x
    def spam(self):
        pass
a = A(22)
a.x
getting :  x





22
a.spam()
getting :  spam

9.13 使用元类控制实例的创建

  • 问题:想要通过改变实例创建的方式来实现单例、缓存、等特性
  • 方案:如下
# 我们知道python创建的类可以像函数一样调用它来创建实例
class Spam:
    def __init__(self,name):
        self.name = name
a = Spam("Guido")
b = Spam("Diana")
# 假设我们不想让任何人创建该类的实例
class NoInstance(type):
    def __call__(self,*args,**kwargs):
        raise TypeError("can't instance directly")
class Spam(metaclass=NoInstance):
    @staticmethod
    def grok(x):
        print("Spam.grok")
#这样我们只能调用该类的静态方法,而不能进行实例化
Spam.grok(42)
Spam.grok
s = Spam()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-30-ee1b074714bf> in <module>()
----> 1 s = Spam()


<ipython-input-28-8781af2ac6ac> in __call__(self, *args, **kwargs)
      2 class NoInstance(type):
      3     def __call__(self,*args,**kwargs):
----> 4         raise TypeError("can't instance directly")
      5 class Spam(metaclass=NoInstance):
      6     @staticmethod


TypeError: can't instance directly
  • 假如你仅仅想要创建一个实例(单例模式):
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
class Spam(metaclass = Singleton):
    def __init__(self):
        print("creating Spam")
a = Spam()
creating Spam
b = Spam()
a == b
True
a is b
True
  • 假设想要缓存实例
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
class Spam(metaclass=Cached):
    def __init__(self,name):
        print("creating Spam({!r})".format(name))
        self.name = name
    
a = Spam('Guodo')
creating Spam('Guodo')
b = Spam("Diana")
creating Spam('Diana')
c = Spam("Guodo")
#注意上面并没有重新创建
a is c
True
a is b
False

9.14 捕获类的属性定义顺序

  • 问题:想要自动的记录一个类中属性和方法的定义顺序
  • 方案:利用元类
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('type Error')
        instance.__dict__[self._name] = value

class Integer(Typed):
    _expected_type = int
class Float(Typed):
    _expected_type = float
class String(Typed):
    _expected_type = str
class OrderMeta(type):
    def __new__(cls, clsname, bases, clsdict):
        d = dict(clsdict)
        order = []
        for name, value in clsdict.items():
            if isinstance(value, Typed):
                value._name = name
                order.append(name)
        d['_order'] = order
        return type.__new__(cls, clsname, bases, d)
    @classmethod
    def __prepare__(cls, clsname, bases):
        return OrderedDict()
  • 下面将使用上面代码将一个类实例的数据序列化为一个csv数据
class Structure(metaclass=OrderMeta):
    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("google",100, 234.19)
s.name
'google'
s.as_csv()
'google,100,234.19'
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/136690.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • java的线程安全、单例模式、JVM内存结构等知识学习和整理

    知其然,不知其所以然 !在技术的海洋里,前路漫漫,我一直在迷失着自我。欢迎访问我的csdn博客,我们一同成长!“不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!” 博客首页:http://blog.csdn.net/u010648555在下面的题目来自于我要加的一个QQ群,然后要加这个QQ群,首先要通过进阶考核,也就是下面这些题,当我看到这些题目的时候。发现这些题目很常见,但是细细去研究

    2022年3月1日
    57
  • getElementById 方法及用法

    getElementById 方法及用法[转]顾明思义,get-Element-By-Id,就是通过ID来设置/返回HTML标签的属性及调用其事件与方法。用这个方法基本上可以控制页面所有标签,条件很简单就是给每个标签分配一个ID号:document.getElementById(“link”).href;document.getElementById(“link”).target;document.getElementById(“i

    2022年7月15日
    16
  • 电脑蓝屏错误代码0x000000ED_蓝屏代码0x000000ed

    电脑蓝屏错误代码0x000000ED_蓝屏代码0x000000ed电脑蓝屏的原因很多,显示的电脑蓝屏也不一样,对应的修复电脑蓝屏的方法也不同。最近就有网友反映自己的电脑蓝屏代码0x000000ed怎么办,该怎么修复电脑蓝屏呢?今天小编就教下大家电脑蓝屏代码0x000000ed的解决方法。1、蓝屏0x000000ed代码为加载引导时失败,首先先尝试重启。2、如果不能解决的话,重启电脑按住F8,选择进入安全模式,然后进入安全模式桌面。2、进入安全模式后,选择左下角开始菜单,依次选择“所有程序”-“附件”-“命令提示符”右键选择以管理员打开。或者直接快捷键win

    2022年10月8日
    4
  • 在服务器上排除问题的头五分钟

    在服务器上排除问题的头五分钟

    2021年9月8日
    57
  • pycharm2021年激活码【注册码】

    pycharm2021年激活码【注册码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月18日
    62
  • python cv.imread_为什么cv2里没有imread

    python cv.imread_为什么cv2里没有imread为什么使用Python-OpenCV虽然python很强大,而且也有自己的图像处理库PIL,但是相对于OpenCV来讲,它还是弱小很多。跟很多开源软件一样OpenCV也提供了完善的python接口,非常便于调用。OpenCV的稳定版是2.4.8,最新版是3.0,包含了超过2500个算法和函数,几乎任何一个能想到的成熟算法都可以通过调用OpenCV的函数来实现,超级方便。一、需要工具本…

    2022年10月14日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号