Making 'isinstance' work with decorators

只谈情不闲聊 提交于 2019-12-08 02:53:36

问题


How does the Python isinstance function work internally? Is there anything I can do to alter its results, like define a special function inside a class or something? Here's my use case:

class Decorator:
    def __init__(self, decorated):
        self._decorated = decorated

    def __call__(self):
        return self._decorated()

@Decorator
class Foo:
    pass

f = Foo()

# How can I make this be true?
isinstance(f, Foo)

Decorator acts almost like a mixin, except a mixing wouldn't be appropriate here. Is there any way I can make the above code work? I should also note that the isinstance line also gives the following error:

    isinstance(f, Foo)
TypeError: isinstance() arg 2 must be a type or tuple of types


回答1:


How about the following:

def Decorator(decorated):
    class Dec(decorated):
        def __call__(self):
            print 'in decorated __call__'
            return decorated.__call__(self)
    return Dec

@Decorator
class Foo(object):
    def __call__(self):
        print 'in original __call__'

f = Foo()

# How can I make this be true?
print isinstance(f, Foo)

With the above code:

  • isinstance(f, Foo) works;
  • f() calls the decorated method which then forwards to the original method.

The basic idea is to make sure that the decorated Foo is still a class, and to also make sure that the decorated Foo is a subclass of the original Foo.

P.S. The purpose of all this is not entirely clear to me; it might be that metaclasses are a better way to achieve what you're trying to do.




回答2:


The problem is that Foo in your example isn't a class.

This code:

@Decorator
class Foo:
    pass

is equivalent to:

class Foo:
    pass
Foo = Decorator(Foo)

Which means that Foo is an instance of class Decorator. Because Foo is not a clas or type, isinstance complains.




回答3:


When decorating a class, it's often useful or desirable to for the decorated return value to also be type; The most obvious way of achieving this is to have the decorator construct and return a new class directly.

That functionality is already handled by metaclasses; In fact, metaclasses are a bit more powerful than decorators, since you get to describe the new class before a decorated class has even been constructed.

Another option is to return the same object that was passed in; but with some changes. That's a better use for decorators, since it works well when you nest decorators. Since you're modifying the behavior when Foo() is used, then you probably want to modify Foo's __init__, which might look like this:

>>> def Decorator(cls):
...     assert isinstance(cls, type)
...     try:
...         old_init = cls.__init__.im_func
...     except AttributeError:
...         def old_init(self): pass
...     def new_init(self):
...         # do some clever stuff:
...         old_init(self)
...     cls.__init__ = new_init
...     return cls
... 
>>> @Decorator
... class Foo(object):
...     def __init__(self): pass
... 
>>> @Decorator
... class Bar(object):
...     pass
... 
>>> f = Foo()
>>> isinstance(f, Foo)
True



回答4:


You can't get type of the object that Foo returns without calling Foo.

isinstance complains about its second argument because it is an instance - in you case instance of Decorated. Although you think of Foo like a class but actually it is just a callable object and it is not a class.

Maybe the next will help you to rethink/solve your problem:

>>> isinstance(f, Foo._decorated)
True


来源:https://stackoverflow.com/questions/7514968/making-isinstance-work-with-decorators

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!