简体   繁体   English

如何更改活动对象的 isinstance() 行为?

[英]How to change a live object's isinstance() behavior?

I want to change the behavior of isinstance for a live python object.我想更改实时 python 对象的isinstance行为。

One solution is to create a simple wrapper like the following, but I do not like it:一种解决方案是创建一个如下所示的简单包装器,但我不喜欢它:

class Widget:
    def __init__(self, obj):
        self.inner_self = obj

lizard = ['head', 'nose', 'tail']
wlizard = Widget(lizard)
assert(isinstance(wlizard, Widget)) # no assertion error thrown 

What I don't like about this particular wrapper, is that we must extract the lizard from wlizard before we can use the lizard again我不喜欢这个特殊包装器的一点是,我们必须先从wlizard提取lizard ,然后才能再次使用lizard

try:
    wlizard[0]
except:
    print('sorry, wlizard doesn\'t behave like a lizard')

lizard = wlizard.inner_self
print(lizard[0]) # works fine

What I really want is for wlizard to behave exactly like lizard except that isinstance returns True for wlizard and it returns false for lizard .我真正想要的是wlizard的行为与 lizard 完全一样,只是isinstancewlizard返回True而它为lizard返回 false 。

The following sort-of works, but has some drawbacks:以下类型的作品,但有一些缺点:

class Widget:
    pass

def MakeAWidget(obj):

    class Blah(type(obj), Widget):
        pass
        # inherits type(obj)'s __init__ method

    wobj = Blah(obj)  # calls type(obj)'s copy constructor
    return wobj

One problem is that this only works if type(obj) 's __init__() method takes in more than just self ;一个问题是,这只适用于type(obj)__init__()方法不仅仅是self in particular, that __init__ can take in an instance of type(obj) , and when it does, it copies the attributes of obj into self .特别是, __init__可以接收type(obj)的实例,当它接收时,它将obj的属性复制到self I would like something that works even if obj does not have a copy constructor.即使obj没有复制构造函数,我也想要一些可以工作的东西。 Something like the following might be possible to force the existence of a copy-constructor:像下面这样的东西可能会强制复制构造函数的存在:

import copy
class Blah(type(obj), Widget):
    def __init__(*args, **kwargs):
        if isinstance(args[0], type(obj)):
            self = copy.deepcopy(args[0])
            return self
        return super(type(self), self).__init__(*args, **kwargs)

However, I would rather not copy the object, only modify it in-place.但是,我宁愿不复制对象,而是就地修改它。 Something like the following might be possible, but I am not sure what __BLAH__ would be:可能会出现以下情况,但我不确定__BLAH__会是什么:

obj = ['apple', 'pear', 'banana']
assert(not isinstance(obj, Widget)) # no error thrown    
obj.__BLAH__.append('Widget')
assert(isinstance(obj, Widget))  # no error thrown

Here's something I think does what you want.这是我认为可以满足您的要求的东西。 The wrap() function dynamically creates a class which is derived from the class of the obj argument passed to it, and then returns an instance of that class created from it. wrap()函数动态创建一个类,该类派生obj它的obj参数的类,然后返回从它创建的该类的实例。 This assumes the class of obj supports copy construction (initialization from an instance of the same — or derived — class).这假设obj类支持复制构造(从同一类或派生类的实例初始化)。

def wrap(obj):
    class MetaClass(type):
        def __new__(mcls, classname, bases, classdict):
            wrapped_classname = '_%s_%s' % ('Wrapped', type(obj).__name__)
            return type.__new__(mcls, wrapped_classname, (type(obj),)+bases, classdict)

    class Wrapped(metaclass=MetaClass):
        pass

    return Wrapped(obj)

lizard = ['head', 'nose', 'tail']
wlizard = wrap(lizard)
print(type(wlizard).__name__)     # -> _Wrapped_list
print(isinstance(wlizard, list))  # -> True

try:
    wlizard[0]
except Exception as exc:
    print(exc)
    print("sorry, wlizard doesn't behave like lizard")
else:
    print('wlizard[0] worked')

I think this is exactly what you want.我认为这正是你想要的。 This solution allows you to decorate any object so that the wrapper instance gets all the methods and attributes of the wrapped one.此解决方案允许您装饰任何对象,以便包装器实例获得包装器实例的所有方法和属性。

This is a metaclass of the wrapper:这是包装器的元类:

class WrapperMeta(type):
    @classmethod
    def __new_getattr(mcs, method, inst):
        if method is None:
            method = object.__getattribute__

        def new_method(self, key):
            try:
                return method(self, key)
            except AttributeError:
                return method(inst.wrappee.fget(self), key)
        return new_method

    def __new__(mcs, name, bases, kwargs):
        if not bases:
            bases = (object,)
        if len(bases) != 1:
            raise TypeError("Wrapper can wrap only one class")
        if type(kwargs.get("wrappee")) != property:
            raise AttributeError("wrapper class must have a \"wrappee\" property")

        inst = type.__new__(mcs, name, bases, kwargs)
        inst.__getattribute__ = mcs.__new_getattr(inst.__getattribute__, inst)
        return inst

It requires a wrapper to have exactly one parent class (the one you want to wrap), to have "wrappee" property and overrides __getattribute__ in a way you need.它需要一个包装器来拥有一个父类(您想要包装的那个),拥有“wrappee”属性并以您需要的方式覆盖__getattribute__

This is a base class:这是一个基类:

class VeryImportantClass:
    def __init__(self):
        self.a = 1

    def very_important_function(self, n):
        return n + self.a

This is a wrapper class:这是一个包装类:

class Wrapper(VeryImportantClass, metaclass=WrapperMeta):
    def __init__(self, vii):
        self._vii = vii

    @property
    def wrappee(self):
        return self._vii

    def very_important_addition(self, n):
        return n - self.a * 4

And that is the result:这就是结果:

vii = VeryImportantClass()
vii = Wrapper(vii)
print(vii.very_important_function(5))  # 6
print(vii.very_important_addition(1))  # -3
print(isinstance(vii, VeryImportantClass))  # True

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM