【问题标题】:How can I make a class decorator not break isinstance function?如何使类装饰器不破坏 isinstance 函数?
【发布时间】:2019-03-25 19:37:57
【问题描述】:

我正在为测试创建一个单例装饰器,但是当我询问一个对象是否是原始类的实例时,它返回 false。

在示例中,我装饰了一个计数器类来创建一个单例,所以每次如果我得到值,它就会返回下一个数字,无论对象的哪个实例调用它。 代码几乎可以工作,但函数 isinstance 似乎坏了,我尝试使用 functools.update_wrapper 但我不知道我是否可以让 isinstance 函数将 Singleton 识别为 Counter (在下面的代码中),只要我要求 Counter代码实际上返回 Singleton。

装饰器

def singleton(Class):

    class Singleton:
        __instance = None

        def __new__(cls):
            if not Singleton.__instance:
                Singleton.__instance = Class()

            return Singleton.__instance

    #update_wrapper(Singleton, Class, 
    #    assigned=('__module__', '__name__', '__qualname__', '__doc__', '__annotation__'), 
    #    updated=()) #doesn't seems to work
    return Singleton

装饰类

@singleton
class Counter:
    def __init__(self):
        self.__value = -1
        self.__limit = 6

    @property
    def value(self):
        self.__value = (self.__value + 1) % self.limit
        return self.__value

    @property
    def limit(self):
        return self.__limit

    @limit.setter
    def limit(self, value):
        if not isinstance(value, int):
            raise ValueError('value must be an int.')

        self.__limit = value

    def reset(self):
        self.__value = -1

    def __iter__(self):
        for _ in range(self.limit):
            yield self.value

    def __enter__(self):
        return self

    def __exit__(self,a,b,c):
        pass

测试

counter = Counter()
counter.limit = 7
counter.reset()
[counter.value for _ in range(2)]

with Counter() as cnt:
    print([cnt.value for _ in range(10)]) #1

print([counter.value for _ in range(5)]) #2
print([val for val in Counter()]) #3

print(Counter) #4
print(type(counter)) #5 
print(isinstance(counter, Counter)) #6

输出:

#1 - [2, 3, 4, 5, 6, 0, 1, 2, 3, 4]
#2 - [5, 6, 0, 1, 2]
#3 - [3, 4, 5, 6, 0, 1, 2]
#4 - <class '__main__.singleton.<locals>.Singleton'>
#5 - <class '__main__.Counter'>
#6 - False

(未注释更新包装器)

#1 - [2, 3, 4, 5, 6, 0, 1, 2, 3, 4]
#2 - [5, 6, 0, 1, 2]
#3 - [3, 4, 5, 6, 0, 1, 2]
#4 - <class '__main__.Counter'>
#5 - <class '__main__.Counter'>
#6 - False

【问题讨论】:

  • 在 Python 2 上,如果你说 class Singleton(Class),它就可以工作。不知道为什么它在 Python 3 上不能正常工作,但也许这可以引导你到那里?
  • 我已经尝试从类、对象和各种(如果不是全部)两者的组合继承,其中任何一个似乎都可以工作 x.x

标签: python python-3.x singleton decorator


【解决方案1】:

您可以在Python Decorator Library 中使用singleton 类装饰器。

之所以有效,是因为它修改了现有类(替换了 __new__() 方法),而不是像您问题的代码中那样用完全独立的类替换它。

import functools

# from https://wiki.python.org/moin/PythonDecoratorLibrary#Singleton
def singleton(cls):
    ''' Use class as singleton. '''

    cls.__new_original__ = cls.__new__

    @functools.wraps(cls.__new__)
    def singleton_new(cls, *args, **kw):
        it =  cls.__dict__.get('__it__')
        if it is not None:
            return it

        cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
        it.__init_original__(*args, **kw)
        return it

    cls.__new__ = singleton_new
    cls.__init_original__ = cls.__init__
    cls.__init__ = object.__init__

    return cls

有了它,我得到以下输出(注意最后一行):

[2, 3, 4, 5, 6, 0, 1, 2, 3, 4]
[5, 6, 0, 1, 2]
[3, 4, 5, 6, 0, 1, 2]
<class '__main__.Counter'>
<class '__main__.Counter'>
True

【讨论】:

  • 这似乎是一个更聪明的想法,但是为什么这段代码需要切换 init 函数,我注意到如果我删除这个更改,第 3 行会重置。我想知道为什么会这样?
  • Kostynha:对不起,我不明白你所说的“第 3 行重置”是什么意思。
  • 我只是觉得这可能是因为 init 将在 new 之后被调用,所以这基本上是避免召回原来的 init,对吗?
  • 不是从 [3,4,5...] 开始,而是从 [0,1,2...] 开始
  • @martineau 这是因为我在“Singleton.__instance = Class()”处实例化了该类。但我理解删除原始 new 和 init 的需要,以确保它们也只被调用一次。泰。
【解决方案2】:

并不比上面的好,但如果您以后需要从记忆中执行此操作,则稍微简单且更容易记住:

def singleton(Class, *initargs, **initkwargs):
    __instance = Class(*initargs, **initkwargs)
    Class.__new__ = lambda *args, **kwargs: __instance
    Class.__init__ = lambda *args, **kwargs: None
    return Class

【讨论】:

  • 确实,这看起来是个不错的方法。不做 functools 包装,但这不是 IMO 的严重限制——我想只是一个小的权衡。
猜你喜欢
  • 2022-12-14
  • 2018-02-08
  • 2012-06-19
  • 2018-06-21
  • 2011-10-04
  • 2019-03-29
  • 2014-12-28
  • 2019-12-05
  • 2011-06-06
相关资源
最近更新 更多