如何在运行时dynamic更改实例的基类?

这篇文章有一个片段,显示__bases__用法, __bases__向inheritance的类的现有类集合添加一个类来dynamic地改变某些Python代码的inheritance层次结构。 好吧,这很难读,代码可能更清晰:

 class Friendly: def hello(self): print 'Hello' class Person: pass p = Person() Person.__bases__ = (Friendly,) p.hello() # prints "Hello" 

也就是说, Person不是从源代码级的Friendlyinheritance的,而是在运行时通过修改Person类的__bases__属性来dynamic添加这个inheritance关系。 但是,如果将FriendlyPerson更改为新样式类(通过inheritance对象),则会出现以下错误:

 TypeError: __bases__ assignment: 'Friendly' deallocator differs from 'object' 

有一点谷歌search这似乎表明新型和旧风格的类之间的一些不兼容的问题在运行时改变inheritance层次。 具体来说: “新式类对象不支持赋值给它们的基础属性” 。

我的问题是,是否有可能通过使用__mro__属性来使上面的Friendly / Person例子在Python 2.7+中使用新样式类工作?

免责声明:我完全意识到这是晦涩难懂的代码。 我完全意识到,在实际生产中,像这样的技巧往往会导致无法读取的边界,这完全是一个思想实验,而且有趣的是要学习Python如何处理与多重inheritance相关的问题。

好的,再次,这不是你通常应该做的,这仅仅是为了提供信息。

Python在实例对象上查找方法的位置由定义该对象的类的__mro__属性( M方法parsing器属性)确定。 因此,如果我们可以修改Person__mro__ ,我们会得到所需的行为。 就像是:

 setattr(Person, '__mro__', (Person, Friendly, object)) 

问题是__mro__是只读属性,因此setattr将不起作用。 也许如果你是一个Python大师,那么有一种解决办法,但显然我没有办法想到上师的地位。

一个可能的解决方法是简单地重新定义类:

 def modify_Person_to_be_friendly(): # so that we're modifying the global identifier 'Person' global Person # now just redefine the class using type(), specifying that the new # class should inherit from Friendly and have all attributes from # our old Person class Person = type('Person', (Friendly,), dict(Person.__dict__)) def main(): modify_Person_to_be_friendly() p = Person() p.hello() # works! 

这不会做的是修改任何以前创build的Person实例,使其具有hello()方法。 例如(只是修改main() ):

 def main(): oldperson = Person() ModifyPersonToBeFriendly() p = Person() p.hello() # works! But: oldperson.hello() # does not 

如果type调用的细节不清楚,那么阅读“ e-satis”关于“什么是Python中的元类? 。

我一直在为此苦苦挣扎,并且对你的解决scheme很感兴趣,但是Python 3把它从我们身上拿走了:

 AttributeError: attribute '__dict__' of 'type' objects is not writable 

我实际上有一个装饰器的合法需要,它replace装饰类的(单个)超类。 这需要太长的描述来包含在这里(我试过了,但是不能达到合理的长度和有限的复杂性 – 它出现在许多基于Python的企业服务器的Python应用程序的使用环境中不同的应用程序需要一些代码略有不同的变化。)

在这个页面上的讨论以及其他的讨论提供了一个提示:指定给__bases__的问题只发生在没有定义超类的类中(即它的唯一超类是object)。 我能够解决这个问题(对于Python 2.7和3.2),通过定义我需要替代的超类作为一个普通类的子类的类:

 ## T is used so that the other classes are not direct subclasses of object, ## since classes whose base is object don't allow assignment to their __bases__ attribute. class T: pass class A(T): def __init__(self): print('Creating instance of {}'.format(self.__class__.__name__)) ## ordinary inheritance class B(A): pass ## dynamically specified inheritance class C(T): pass A() # -> Creating instance of A B() # -> Creating instance of B C.__bases__ = (A,) C() # -> Creating instance of C ## attempt at dynamically specified inheritance starting with a direct subclass ## of object doesn't work class D: pass D.__bases__ = (A,) D() ## Result is: ## TypeError: __bases__ assignment: 'A' deallocator differs from 'object' 

我不能担保后果,但是这个代码在py2.7.2中做了你想要的。

 class Friendly(object): def hello(self): print 'Hello' class Person(object): pass # we can't change the original classes, so we replace them class newFriendly: pass newFriendly.__dict__ = dict(Friendly.__dict__) Friendly = newFriendly class newPerson: pass newPerson.__dict__ = dict(Person.__dict__) Person = newPerson p = Person() Person.__bases__ = (Friendly,) p.hello() # prints "Hello" 

我们知道这是可能的。 凉。 但我们永远不会使用它!

蝙蝠的权利,dynamic的类层次结构的所有警告是有效的。

但是,如果必须这样做,很显然, "deallocator differs from 'object" issue when modifying the __bases__ attribute新样式类"deallocator differs from 'object" issue when modifying the __bases__ attribute ,会出现"deallocator differs from 'object" issue when modifying the __bases__ attribute

你可以定义一个类对象

 class Object(object): pass 

它从内置的元类type派生类。 就是这样,现在你的新风格类可以修改__bases__而没有任何问题。

在我的testing中,这实际上工作得非常好,因为它和它的派生类的所有现有的(在改变inheritance之前)实例都感受到包括它们的mro得到更新的改变的效果。

我需要一个这样的解决scheme:

  • 适用于Python 2(> = 2.7)和Python 3(> = 3.2)。
  • dynamic导入依赖关系后,可以更改类库。
  • 让unit testing代码更改类的基础。
  • 与具有自定义元类的types一起使用。
  • 仍然允许unittest.mock.patch正常工作。

以下是我想到的:

 def ensure_class_bases_begin_with(namespace, class_name, base_class): """ Ensure the named class's bases start with the base class. :param namespace: The namespace containing the class name. :param class_name: The name of the class to alter. :param base_class: The type to be the first base class for the newly created type. :return: ``None``. Call this function after ensuring `base_class` is available, before using the class named by `class_name`. """ existing_class = namespace[class_name] assert isinstance(existing_class, type) bases = list(existing_class.__bases__) if base_class is bases[0]: # Already bound to a type with the right bases. return bases.insert(0, base_class) new_class_namespace = existing_class.__dict__.copy() # Type creation will assign the correct '__dict__' attribute. del new_class_namespace['__dict__'] metaclass = existing_class.__metaclass__ new_class = metaclass(class_name, tuple(bases), new_class_namespace) namespace[class_name] = new_class 

在应用程序中使用像这样:

 # foo.py # Type `Bar` is not available at first, so can't inherit from it yet. class Foo(object): __metaclass__ = type def __init__(self): self.frob = "spam" def __unicode__(self): return "Foo" # … later … import bar ensure_class_bases_begin_with( namespace=globals(), class_name=str('Foo'), # `str` type differs on Python 2 vs. 3. base_class=bar.Bar) 

从unit testing代码里面这样使用:

 # test_foo.py """ Unit test for `foo` module. """ import unittest import mock import foo import bar ensure_class_bases_begin_with( namespace=foo.__dict__, class_name=str('Foo'), # `str` type differs on Python 2 vs. 3. base_class=bar.Bar) class Foo_TestCase(unittest.TestCase): """ Test cases for `Foo` class. """ def setUp(self): patcher_unicode = mock.patch.object( foo.Foo, '__unicode__') patcher_unicode.start() self.addCleanup(patcher_unicode.stop) self.test_instance = foo.Foo() patcher_frob = mock.patch.object( self.test_instance, 'frob') patcher_frob.start() self.addCleanup(patcher_frob.stop) def test_instantiate(self): """ Should create an instance of `Foo`. """ instance = foo.Foo() 

如果您需要在运行时更改现有的类,上面的答案是很好的。 但是,如果您只是想创build一个由其他类inheritance的新类,则有一个更清洁的解决scheme。 我从https://stackoverflow.com/a/21060094/3533440得到这个想法,但我认为下面的例子更好地说明了一个合法的用例。;

 def make_default(Map, default_default=None): """Returns a class which behaves identically to the given Map class, except it gives a default value for unknown keys.""" class DefaultMap(Map): def __init__(self, default=default_default, **kwargs): self._default = default super().__init__(**kwargs) def __missing__(self, key): return self._default return DefaultMap DefaultDict = make_default(dict, default_default='wug') d = DefaultDict(a=1, b=2) assert d['a'] is 1 assert d['b'] is 2 assert d['c'] is 'wug' 

纠正我,如果我错了,但是这个策略似乎是非常可读的,我会在生产代码中使用它。 这与OCaml中的仿函数非常相似。