【问题标题】:How to create a Python class that is a subclass of another class, but fails issubclass and/or isinstance tests?如何创建一个 Python 类,它是另一个类的子类,但 issubclass 和/或 isinstance 测试失败?
【发布时间】:2019-11-01 15:45:10
【问题描述】:

我知道这可能是一个糟糕的设计,但我遇到了这样一种情况,我需要即时创建类 Base 的子类 Derived,并使 Derived 的实例失败issubclass(Derived, Base)isinstance(derived_obj, Base) 检查(即返回 False)。

我尝试了很多方法,但都没有成功:

  • Derived (https://stackoverflow.com/a/42958013/4909228) 中创建一个名为__class__ 的属性。这只能用于使检查返回True
  • 覆盖Base__instancecheck____subclasscheck__ 方法。这不起作用,因为 CPython 仅在常规检查返回 False 时调用这些方法。
  • __init__ 期间分配__class__ 属性。这在 Python 3.6+ 中不再允许。
  • 创建Derived 子类object 并将其所有属性和方法(包括特殊方法)分配给Base。这不起作用,因为某些方法(例如__init__)不能在不是Base 子类的实例上调用。

这可能在 Python 中完成吗?该方法可能是特定于解释器的(代码仅在 CPython 中运行),并且只需要针对 Python 3.6+ 版本。


为了说明此要求的潜在用途,请考虑以下函数:

def map_structure(fn, obj):
    if isinstance(obj, list):
        return [map_structure(fn, x) for x in obj]
    if isinstance(obj, dict):
        return {k: map_structure(fn, v) for k, v in obj.items()}
    # check whether `obj` is some other collection type
    ...
    # `obj` must be a singleton object, apply `fn` on it
    return fn(obj)

此方法泛化map 以处理任意嵌套的结构。但是,在某些情况下我们不想遍历某个嵌套结构,例如:

# `struct` is user-provided structure, we create a list for each element
struct_list = map_structure(lambda x: [x], struct)
# somehow add stuff into the lists
...
# now we want to know how many elements are in each list, so we want to
# prevent `map_structure` from traversing the inner-most lists
struct_len = map_structure(len, struct_list)

如果上述功能可以实现,那么上面可以改成:

pseudo_list = create_fake_subclass(list)
struct_list = map_structure(lambda x: pseudo_list([x]), struct)
# ... and the rest of code should work

【问题讨论】:

  • 是的,那是糟糕的设计。您可能应该以某种方式使用组合。
  • 您要求的是没有接口继承的实现继承(或者至少,Python 必须最接近接口继承)。这是组合的完美用例。如果您的对象不应该像list 那样迭代,那么它就不是list。干净利落。使它成为一个列表违反了鸭子类型、Liskov 替换和其他可能的原则。
  • 您是否尝试过向实现__instancecheck__ 的基类添加元类?

标签: python


【解决方案1】:

覆盖Base__instancecheck____subclasscheck__ 方法。这不起作用,因为 CPython 仅在常规检查返回 False 时调用这些方法。

这种说法是一种误解。这些钩子将在 元类 上定义,而不是在基类 (docs) 上。

>>> class Meta(type): 
...     def __instancecheck__(self, instance): 
...         print("instancecheck", self, instance) 
...         return False 
...     def __subclasscheck__(self, subclass): 
...         print("subclasscheck", self, subclass) 
...         return False 
...
>>> class Base(metaclass=Meta):
...     pass
...
>>> class Derived(Base):
...     pass
...
>>> obj = Derived()
>>> isinstance(obj, Base)
instancecheck <class '__main__.Base'> <__main__.Derived object at 0xcafef00d>
False
>>> issubclass(Derived, Base)
subclasscheck <class '__main__.Base'> <class '__main__.Derived'>
False

注意 CPython 性能优化会阻止在某些特殊情况下调用自定义实例检查挂钩(有关详细信息,请参阅 here)。特别是,当存在完全匹配时,您可能不会因为 CPython fast path 而对 isinstance(obj, Derived) 的返回值进行强力武装。

最后一点,我同意评论者的观点,即这听起来不是一个很有前途的设计。在这种情况下,您似乎应该考虑使用组合而不是继承。

【讨论】:

  • 感谢您的精彩回答!还有一个问题:是否可以将元类动态添加到现有类(包括内置类)?
  • 这是不可能的(如果已经创建了元类,那么修改元类没有多大意义,因为元类的主要职责是自定义类的创建)。对于用户定义的类型,你可以自己动态地对实例检查和子类检查方法进行monkeypatch,但你当然不能在内置类型上这样做。
【解决方案2】:

正如其他人指出的那样:使用组合。

创建一个不递归映射的类:

class NotMapped:
    pass

然后使用组合并从中派生您的类:

class Derived(NotMapped):
    pass

然后在函数的开头添加一个案例:

def map_structure(fn, obj):

    if isinstance(obj, NotMapped):
        return obj
    # special-case container types etc.
    ...
    return fn(obj)

将它与多重继承一起用作一种混合。为容器类型创建便利 ctor,因此 NotMapped([1, 2, 3]) 可以按预期工作。

【讨论】:

    猜你喜欢
    • 2011-05-31
    • 2016-02-05
    • 1970-01-01
    • 2020-04-15
    • 2011-02-27
    • 1970-01-01
    • 1970-01-01
    • 2015-02-13
    • 2015-08-19
    相关资源
    最近更新 更多