是时候上类装饰器及单例了。

Posted sidianok

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了是时候上类装饰器及单例了。相关的知识,希望对你有一定的参考价值。

又学了半个小时,对装饰器的理解感觉又上了一个阶段,装饰器真的是一个牛逼的工具,不改变原函数的基础上想如何折腾原函数,就如何折腾原函数。

@装饰器名称,这个语法糖都知道了,其实@后面的变量名是个可调用的参数就可以,函数可以变调用,当然类也可以被调用,callable函数能够测试该对象能否被调用,粗糙的讲后面有()这括号的就算能被调用

我下面下一个简单的装饰器模块,其实装饰器能够装饰所有能被调用的对象,所有后面会有类装饰器的单例,和函数装饰器的单例。

 

class Demo:

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

    def __call__(self, *args, **kwargs):
        print(‘我进来了‘)
        res = self.func(*args, **kwargs)
        print(‘执行完毕,我出去了‘)
        return res


@Demo
def foo(x, y):
    return x + y


print(foo.__dict__)
print(‘_‘ * 80)
print(foo(3, 4))

 

{‘func‘: <function foo at 0x10db3dc20>}
________________________________________________________________________________
我进来了
执行完毕,我出去了
7

 整个简单的类装饰器,能看懂执行,我后面写的就不用看了,其实还是跟前面讲的一样,foo已经变了,foo不在是前面的函数,foo已经是Demo的实例了。

Python就是这么的无耻,既然foo是实例,我们原来的func就是本来的foo就只能通过__init__初始化实例的时候通过属性放在对象身上。

由于普通的对象没有__call__方法,我们后面执行foo(3, 4)的时候,其实是直接用对象调用方法,为了能够让对象直接被调用,

在类里面加入了__call__方法,让实例可以被调用。

单唯一的遗憾是,我这里没地方找到functools的wrap使用,就是无法保证了被装饰的对象的原来属性。

 

既然是这样,我后面就可以直接写三个单例来练手了,最后来一个带参数的类装饰器,看它是如何将原函数传入实例里面的。(学到后面感觉类只不过就是一个高级版本的闭包函数)

第一种:通过__new__来实现,单例。

前面已经说过了,类的实例过程,首先调用__calll__方法,然后__new__创建对象,最后__init__初始化对象属性,返回给变量。

class Dli:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, ‘_instance‘):         # 如果没有这个属性
            cls._instance = object.__new__(cls)   # 给类一个对象属性,真的好恶心的操作
        return cls._instance


d1 = Dli(1, 2)
d2 = Dli(7, 8)
print(d2 is d1)
print(d2.x, d1.x)

 

7
True
7 7

 Python中没有锁定属性的对象,可以里面随便添加属性,Python就是这么没有节操,越学到后面,感觉越没节操,现在开始基础学JAVA,感觉Java规矩太多了。

第二种,通过函数装饰器来做单例

def doo(cls):  # 装饰的是个类,就写cls,其实写什么无所谓,就传参用而已
    _instance = {}
    # @functools.wraps(cls)
    def wrap(*args, **kwargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)
        return _instance[cls]

    return wrap


@doo
class Demo:
    def __init__(self, x, y):
        self.x = x
        self.y = y

print()
print(Demo)

d1 = Demo(1, 2)
d2 = Demo(7, 8)
print(d2.x, d1.x, d1 is d2)

 

<function doo.<locals>.wrap at 0x1095f4c20>
1 1 True

 通过输出可以看到,用一个函数内部的字典接收实例对象,如果已经存在实例了,后面产生的实例全部参考指向第一个。

functools.wrap还是可以使用的,能够基本保证类的内部一些属性。

第三,写两个类,用一个类装饰另外一个类来产生一个单例。

class Warp:
    def __init__(self,cls):
        self.cls = cls

    def __call__(self, *args, **kwargs):
        if not hasattr(Warp, ‘_instance‘):
            # 产生实例直接放到类属性,更加方便
            Warp._instance = self.cls(*args, **kwargs)
        return Warp._instance

@Warp
class Demo:
    ‘‘‘This is Demo docstring‘‘‘
    def __init__(self, x, y):
        self.x = x
        self.y = y

d3 = Demo(1, 5)
d4 = Demo(6, 9)
print(d3 is d4)
print(d3.x, d4.x)

 上下两个类,上面哪个类装饰下面这个类,用自身的类属性来保存及返回实例。

 

最后说一个带参数的类装饰器如何使用,其实用类的装饰器还是非常强大的,因为可以直接调用对象里面的很多方法。本来还想试一下有没有类的多层装饰器,看后续,有时间就写个玩玩,应该跟函数的差不多。

class NewWarp:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __call__(self, func):
        # 这个在call里面传递func进来,而且竟然可以用@functools.wraps保护原函数属性
        @functools.wraps(func)
        def warp(*args, **kwargs):
            # 由于传入的产生是对象属性,哪里使用都很方便。
            print(f‘My name is {self.name}, age is {self.age}‘)
            res = func(*args, **kwargs)
            return res
        return warp


@NewWarp(‘sidian‘, 5)
def foo(x, y):
    return x + y

exec(‘print();‘ * 3)
print(foo.__name__)
print(foo(3, 5))

 

foo
My name is sidian, age is 5
8

 从上面看出,带参数的装饰器类,传入的func地址要注意,是在__call__的地方传入。

 

最后我尝试写一个,多类装饰器的模式。

class Demo2:

    def __init__(self, func):
        print(self.__class__.__name__)
        self.func = func

    def __call__(self, *args, **kwargs):
        print(‘demo2_call我进来了‘)
        res = self.func(*args, **kwargs)
        print(‘demo2_call执行完毕,我出去了‘)
        return res

class Demo1:

    def __init__(self, func):
        print(self.__class__.__name__)
        self.func = func


    def __call__(self, *args, **kwargs):
        print(‘demo1_call我进来了‘)
        res = self.func(*args, **kwargs)
        print(‘demo1_call执行完毕,我出去了‘)
        return res

@Demo2
@Demo1
def foo(x, y):
    return x + y

# print(foo.__name__)
print(foo.__dict__)
print(‘_‘ * 80)
print(foo(3, 4))

 

Demo1
Demo2
{‘func‘: <__main__.Demo1 object at 0x10e3e53d0>}
________________________________________________________________________________
demo2_call我进来了
demo1_call我进来了
demo1_call执行完毕,我出去了
demo2_call执行完毕,我出去了
7

 从输出来看,逻辑跟多层的函数装饰器差不多,首先也是进行方法调用,这里是类,就是先进行初始化

foo = Demo1(foo)

然后传递给Demo2

foo = Demo2(Demo1(foo))

所以执行f00就是先执行Demo2的实例对象,Demo2里面的func就是Demo1(foo)实例,执行Demo1(foo)实例,就是执行foo,然后通过return层层返回出来。

 

总算写完了,感觉脑细胞死了好多。

以上是关于是时候上类装饰器及单例了。的主要内容,如果未能解决你的问题,请参考以下文章

网络加载数据及单例工具类的代码抽取

面向对象知识点续及单例模式

面向接口及单例工厂随笔

Python3——装饰器及应用(这个属于详细的)

python 3层装饰器及应用场景

装饰器生成器迭代器及python中内置函数的使用