【问题标题】:How to change a live object's isinstance() behavior?如何更改活动对象的 isinstance() 行为?
【发布时间】:2018-08-11 22:44:27
【问题描述】:

我想为实时 python 对象更改 isinstance 的行为。

一种解决方案是创建一个简单的包装器,如下所示,但我不喜欢它:

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 

我不喜欢这个特殊的包装器,我们必须先从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

我真正想要的是wlizard 的行为与蜥蜴完全一样,除了isinstancewlizard 返回True,它为lizard 返回 false。

以下类型的作品,但有一些缺点:

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

一个问题是,这只有在type(obj)__init__() 方法接受的不仅仅是self 时才有效;特别是,__init__ 可以接收type(obj) 的实例,当它接收时,它会将obj 的属性复制到self。即使obj 没有复制构造函数,我也想要一些可以工作的东西。类似以下的东西可能会强制复制构造函数的存在:

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)

但是,我宁愿不复制对象,只在原地修改它。 可能会出现以下情况,但我不确定 __BLAH__ 会是什么:

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

【问题讨论】:

  • 这听起来像是你为某些潜在问题选择了一个糟糕的解决方案,如果不这样做的话会更好地解决。你为什么要以这种方式改变你的对象?为什么你关心你的对象如何响应isinstance,而不是它们支持的行为?
  • lizard是什么Python对象?
  • @user2357112 我创建了一个包装器,它允许通过__getattribute__ 访问对象的__getitem__ 方法。 wrap_obj.key == obj[key] 但是,我想知道返回的值是来自__getitem__ 还是obj 的属性。一种解决方案似乎是在返回值上调用 isinstance 以查看它是什么。
  • @Blender 在我的示例中,lizardlist。但是在实际应用中,我们不知道lizard会是什么先验的。解决方案需要足够通用以处理大多数类型的对象。
  • @ToothpickAnemone:既然您可以控制底层类,那么您要解决的具体问题是什么?这似乎是一个糟糕的方法。

标签: python python-3.x class inheritance metaclass


【解决方案1】:

我认为这正是您想要的。此解决方案允许您装饰任何对象,以便包装器实例获取包装器实例的所有方法和属性。

这是包装器的元类:

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

它要求包装器只有一个父类(您要包装的那个),具有“wrappee”属性并以您需要的方式覆盖 __getattribute__

这是一个基类:

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

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

这是一个包装类:

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

这就是结果:

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

【讨论】:

    【解决方案2】:

    这是我认为你想要的东西。 wrap() 函数动态创建一个类,该类派生自传递给它的 obj 参数的类,然后返回从它创建的该类的实例。这假设 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')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-30
      • 2014-05-22
      相关资源
      最近更新 更多