使 isinstance(obj, cls) 与装饰类一起工作

Posted

技术标签:

【中文标题】使 isinstance(obj, cls) 与装饰类一起工作【英文标题】:Make isinstance(obj, cls) work with a decorated class 【发布时间】:2014-12-28 05:57:09 【问题描述】:

我有几个类需要执行以下操作:

当构造函数被调用时,如果一个相等的对象(也就是一个具有相同 id 的对象)已经存在,则返回那个对象。否则,创建一个新实例。 基本上,

>>> cls(id=1) is cls(id=1)
True

为了实现这一点,我编写了一个类装饰器,如下所示:

class Singleton(object):
    def __init__(self, cls):
        self.__dict__.update('instances': ,
                                'cls': cls)

    def __call__(self, id, *args, **kwargs):
        try:
            return self.instances[id]
        except KeyError:
            instance= self.cls(id, *args, **kwargs)
            self.instances[id]= instance
            return instance

    def __getattr__(self, attr):
        return getattr(self.cls, attr)
    def __setattr__(self, attr, value):
        setattr(self.cls, attr, value)

这是我想要的,但是:

@Singleton
class c(object):
    def __init__(self, id):
        self.id= id

o= c(1)
isinstance(o, c) # returns False

我该如何解决这个问题?我找到了related question,但我似乎无法让这些解决方案适应我的用例。


我知道有人会要求我发布一些不起作用的代码,所以你去吧:

def Singleton(cls):
    instances= 
    class single(cls):
        def __new__(self, id, *args, **kwargs):
            try:
                return instances[id]
            except KeyError:
                instance= cls(id, *args, **kwargs)
                instances[id]= instance
                return instance
    return single
# problem: isinstance(c(1), c) -> False

def Singleton(cls):
    instances= 
    def call(id, *args, **kwargs):
        try:
            return instances[id]
        except KeyError:
            instance= cls(id, *args, **kwargs)
            instances[id]= instance
            return instance
    return call
# problem: isinstance(c(1), c) -> TypeError

【问题讨论】:

我自己并没有真正研究过它,但是有一篇关于“通用装饰器”的 10 部分博客文章非常好。 blog.dscpl.com.au/2014/01/how-you-implemented-your-python.html 是第一个。此处引用的集合:github.com/GrahamDumpleton/wrapt/tree/master/blog 致那些投反对票的人:我很感激您的评论。如果你不告诉我我做错了什么,我无法提出更好的问题。 @abarnert:为什么不试试呢? :) 它将产生一个RuntimeError: maximum recursion depth exceeded,因为__setattr__ 试图检索self.cls @Rawing:啊,对。通常我会通过让__setattr__ 变得聪明来处理这个问题,而不是背着它,但我想这也可以。 @Rawing 在新式类中它是recommended to use 类似于object.__setattr__(self, 'instances', );object.__setattr__(self, 'cls', cls),因为这尊重描述符协议。 【参考方案1】:

作为使用装饰器创建单例类的替代解决方案,您可以改用元类来创建您的类。元类可用于向类添加功能,就像子类可以从其超类继承功能一样。元类的优点是名称 c 实际上将直接引用类 c 而不是 Singleton 对象或包装对 c 的构造函数的调用的函数。

例如:

class SingletonMeta(type):
    """SingletonMeta is a class factory that adds singleton functionality to a 
    class. In the following functions `cls' is the actual class, not 
    SingletonMeta."""

    def __call__(cls, id, *args, **kwargs):
        """Try getting a preexisting instance or create a new one"""
        return cls._instances.get(id) or cls._new_instance(id, args, kwargs)

    def _new_instance(cls, id, args, kwargs):
        obj = super(SingletonMeta, cls).__call__(*args, **kwargs)
        assert not hasattr(obj, "id"), " should not use 'id' as it is " \
            "reserved for use by Singletons".format(cls.__name__)
        obj.id = id
        cls._instances[id] = obj
        return obj        

    def __init__(cls, classname, bases, attributes):
        """Used to initialise `_instances' on singleton class"""
        super(SingletonMeta, cls).__init__(classname, bases, attributes)    
        cls._instances = 

你可以这样使用它:

# python 2.x
class MySingleton(object):
    __metaclass__ = SingletonMeta

# python 3.x
class MySingleton(object, metaclass=SingletonMeta):
    pass

与你的装饰器对比使用:

class IDObject(object):
    def __str__(self):
        return "(id=)".format(type(self).__name__, self.id)

@Singleton
class A(IDObject):
    def __init__(self, id):
        self.id = id

class B(IDObject, metaclass=SingletonMeta):
    pass

format_str = """4 class is 0
an instance: 1
1 is 1 = 2
isinstance(1, 0.__name__) = 3"""
print(format_str.format(A, A(1), A(1) is A(1), isinstance(A(1), A), "decorator"))
print()
print(format_str.format(B, B(1), B(1) is B(1), isinstance(B(1), B), "metaclass"))

输出:

decorator class is <__main__.Singleton object at 0x7f2d2dbffb90>
an instance: A(id=1)
A(id=1) is A(id=1) = True
isinstance(A(id=1), A) = False

metaclass class is <class '__main__.B'>
an instance: B(id=1)
B(id=1) is B(id=1) = True
isinstance(B(id=1), B) = True

【讨论】:

【参考方案2】:

您可以在装饰器类中添加自定义 __instancecheck__ 钩子:

def __instancecheck__(self, other):
    return isinstance(other, self.cls)

【讨论】:

嗯,这比我预期的要容易得多。谢谢。

以上是关于使 isinstance(obj, cls) 与装饰类一起工作的主要内容,如果未能解决你的问题,请参考以下文章

isinstance和issubclass

假期(面向对象相关)

面向对象编程进阶版

面向进阶补充

面向对象进阶二

内置方法