Python3-Cookbook(第九章) - 元编程Part2

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

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2023/8/17 23:23
# @Author  : Maple
# @File    : 10-为类和静态方法提供装饰器.py
import time


def TimeCount(func):

    def wrapper(*args,**kwargs):
        start_time = time.perf_counter()
        r = func(*args,**kwargs)
        print('time takes {}s'.format(time.perf_counter() - start_time))
        return r

    return wrapper

class A:

    # 注意装饰器的顺序不能写错
    @staticmethod
    @TimeCount
    def add(a,b):
        return a + b
    @classmethod
    @TimeCount
    def show(cls,n):
        return n

if __name__ == '__main__':

    # 测试
    a = A()
    result1 = a.add(1,3)
    print(result1)
    """  
     time takes 2.0999577827751637e-06s
     4
    """

    print('--------------------')

    result2 = A.show(10)
    print(result2)
    """
    time takes 7.00005330145359e-07s
    10
    """

二、将装饰器定义为类

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024-04-07 16:23
# @Author  : Maple
# @File    : 09-将装饰器定义为类.py



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
        # 执行被包装的函数func
        return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, cls):
        if instance is None:
            # print(self)
            return self
        else:
            # self其实就指向被包装的函数-->return self.__wrapper__(args,kwargs)
            # 这也是为何要实现__call__,因为能够实现profiled()这种写法-->会执行__call__方法,而__call__方法
            ## 返回的是对fun的调用
            # 为实例instance绑定fun
            return types.MethodType(self, instance)


@Profiled
def add(x,y):
    return x + y

class Spam:

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


if __name__ == '__main__':
    a = add
    print(a) # <__main__.Profiled object at 0x0000018200971E50>

    print(add.ncalls) # 0
    # 本质上调用Profiled类中的call方法,因此ncall会+1
    add(12,2)

    print(add.ncalls) # 1

    s = Spam()
    print('s-->', s) # s--> <__main__.Spam object at 0x0000024770602180>
    # 会进到Profiled中的__get__方法,然后为s绑定 bar方法(同时这个bar是被Profiled包装过的)
    # 因此当执行s.bar时,实际是执行profiled()--> 会执行Profiled中的call--> ncall会被+1
    """
    s.bar(2)的完整执行流程:
    1. 跳转到Profiled中的__get__方法,为s绑定一个profield实例--name为bar(profiled实例中包装了bar函数)
    2. 因此s.bar(2)会执行Profiled类中的call,将ncall + 1 
    3. 然后通过self.__wrapped__(*args, **kwargs) -->执行bar函数本身的逻辑: print(self,x)
    4. 最后return self.__wrapped__(*args, **kwargs)的返回值:此例为null,无返回值
    """

    s.bar(2)
    print(s.bar.ncalls) # 1
    s.bar(3)
    print(s.bar.ncalls)  # 2

    print('****************************')

    s = Spam.bar(0,1)

三、装饰器为包装函数增加参数

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024-04-07 16:23
# @Author  : Maple
# @File    : 11-装饰器为包装函数增加参数.py



import inspect
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 add(x,y):
    return x + y


"""
通过装饰器来给被包装函数增加参数的做法并不常见。 
尽管如此,有时候它可以避免一些重复代码。例如,如果你有下面这样的代码:
"""
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):
    if debug:
        print('Calling c')


def optional_debug2(func):
    if 'debug' in inspect.getfullargspec(func).args:
        print('inspect.getfullargspec(func).args',inspect.getfullargspec(func).args) # ['x', 'y', 'debug']
        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



"""如果函数本身已存在debug参数,会触发装饰器中的raise TypeError
"""
# @optional_debug2
# def add2(x,y,debug):
#     if debug:
#         print('calling add2')
#     return  x + y

@optional_debug2
def add3(x,y):
    return  x + y



def optional_debug3(func):
    if 'debug' in inspect.getfullargspec(func).args:
        print('inspect.getfullargspec(func).args',inspect.getfullargspec(func).args) # ['x', 'y', 'debug']
        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)
    # 获取func函数的参数列表
    param = list(sig.parameters.values())

    # 添加debug参数
    param.append(inspect.Parameter('debug',
                                   inspect.Parameter.KEYWORD_ONLY,
                                   default=False))

    wrapper.__signature__ = sig.replace(parameters=param)
    return wrapper


@optional_debug3
def add4(x,y):
    return  x + y



if __name__ == '__main__':

    # 1.optional_debug测试
    r = add(1,2,debug = True) # calling add
    print(r) # 3

    print('2-1.optional_debug2 -测试1')
    # 2-1.optional_debug2 -测试1
    # try:
    #     add2(1,2)
    # except TypeError as e:
    #     print(e) # debug argument already defined

    print(' 2-2.optional_debug2 -测试2')
    # 2-2.optional_debug2 -测试2
    r2 = add3(2,2,debug = True) # calling add3
    print(r2) # 4

    print('2-3.optional_debug2存在一个问题')
    # 2-3.optional_debug2存在一个问题
    # 被装饰的add3元数据中并没有debug参数(本意是想通过装饰器添加额外参数)
    print(inspect.signature(add3)) # (x, y)

    # 3. 修复被装饰函数debug参数丢失问题
    print(inspect.signature(add4)) # (x, y, *, debug=False)

四、使用类装饰器扩充类的功能

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024-04-07 17:24
# @Author  : Maple
# @File    : 12-使用类装饰器扩充类的功能.py

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

def log_getattribute(cls):

    org_getattribute = cls.__getattribute__

    def new_getattribute(self,name):
        print('getting ',name)
        return org_getattribute(self,name)

    cls.__getattribute__ = new_getattribute

    return cls

@log_getattribute
class A:
    def __init__(self,x):
        self.x = x

    def spam(self):
        pass

if __name__ == '__main__':

    # 1.属性测试
    a = A('Maple') # getting  x
    print(a.x) # Maple

    # 2.方法测试
    a.spam() #getting  spam

五、使用元类控制实例的创建

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024-04-07 17:43
# @Author  : Maple
# @File    : 13-使用元类控制实例的创建.py

"""
元类是type类的子类
"""

class NoInstance(type):
    def __call__(self, *args, **kwargs):
        raise TypeError("can't instanciate directly")

class Person(metaclass=NoInstance):

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

    @staticmethod
    def run():
        print('grok is running')


"""利用元类实现单列模式
"""

class Singleton(type):

    # init方法是完成类(此例中是 Student )的初始化
    # self表示Student类, cls.__instance即为类添加了一个类属性
    def __init__(cls,*args,**kwargs):
        # self代表元类Singleton的实例(注意元类是用来创建类的,因此其实例对象是一个类)
        # 因此,此例中self代表Student类
        cls.__instance = None
        # super()代表type
        """
        *args 和 **kwargs:这些是位置参数和关键字参数的列表,它们在创建类对象时被传递给 type 的构造函数。通常,这些参数包括:
        
        --下面这3个参数其实都位于args元组中--
        name:字符串,代表正在被创建的类的名称。
        bases:元组,包含了类的所有基类。
        class_dict:字典,包含了类定义中的所有属性和方法。
        """
        print('__init__方法中的args')
        for a in args:
            print('a: ',a)

        # 调用type的init方法,初始化需要创建的类:此例是Student
        super().__init__(*args,**kwargs)


    # 当要创建的类(此例中是 Student 类) 实例化时,会调用元类的call方法
    def __call__(cls, *args, **kwargs):
        #
        if cls.__instance is None:
            print('__call__方法中的args')
            """
            a:  Maple
            a:  19
            """
            for a in args:
                print('a: ', a)

            # 调用type的call方法,返回一个实例对象(student),并存放在Student类的__instance属性中
            # cls是Student类
            cls.__instance = super().__call__(*args,**kwargs)
            return cls.__instance
        else:
            return cls.__instance


class Student(metaclass=Singleton):
    def __init__(self,name,age):
        self.name = name
        self.age = age



if __name__ == '__main__':

    # 1. Person不能被实例化,run方法只能通过类调用
    print('******** 1. Person不能被实例化,run方法只能通过类调用***************')
    Person.run() # grok is running

    try:
        p = Person('Maple')
    except TypeError as e:
        print(e) # can't instanciate directly


    print('********2. 单例测试****************')
    ## 2-1 类属性查看
    print(Student.__dict__) #  {...Singleton__instance': None}

    ## 2-2 初始化实例对象
    s1 = Student('Maple',19)
    print(Student.__dict__)  # {..._Singleton__instance': <__main__.Student object at 0x00000180EB622D80>}}
    s2 = Student('Maple', 19)
    print(Student.__dict__)  # {..._Singleton__instance': <__main__.Student object at 0x00000180EB622D80>}}
    print(s1 is s2) # True

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值