This article有一个代码片段,显示__bases__
用于动态更改某些Python代码的继承层次结构的代码段,方法是将类添加到它继承的类的现有类集合中。好吧,这很难读,代码可能更清晰:
class Friendly:
def hello(self):
print 'Hello'
class Person: pass
p = Person()
Person.__bases__ = (Friendly,)
p.hello() # prints "Hello"
也就是说,Person
不会从源级别的Friendly
继承,而是通过修改Person类的__bases__
属性在运行时动态添加此继承关系。但是,如果将Friendly
和Person
更改为新样式类(通过继承自object),则会出现以下错误:
TypeError: __bases__ assignment: 'Friendly' deallocator differs from 'object'
对于在运行时更改继承层次结构的新样式类和旧样式类之间的一些Googling似乎indicate some incompatibilities。具体来说:"New-style class objects don't support assignment to their bases attribute"。
我的问题是,是否可以使用Python 2.7+中的新式类来使上述Friendly / Person示例工作,可能使用__mro__
属性?
免责声明:我完全意识到这是晦涩难懂的代码。我完全意识到,在实际生产代码中,这样的技巧往往难以理解,这纯粹是一个思想实验,并且可以让学生了解Python如何处理与多重继承相关的问题。 < / p>
答案 0 :(得分:33)
好的,再次,这不是您通常应该做的事情,这仅供参考。
Python在实例对象上查找方法的位置取决于定义该对象的类的__mro__
属性( M ethod R esolution O rder属性)。因此,如果我们可以修改__mro__
的{{1}},我们就会得到理想的行为。类似的东西:
Person
问题是setattr(Person, '__mro__', (Person, Friendly, object))
是一个只读属性,因此setattr不起作用。也许如果你是一个Python大师,有一种方法可以解决这个问题,但显然我没有达到大师的地位,因为我无法想到它。
一种可行的解决方法是简单地重新定义该类:
__mro__
这不做的是修改任何以前创建的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!
实例以使用Person
方法。例如(仅修改hello()
):
main()
如果def main():
oldperson = Person()
ModifyPersonToBeFriendly()
p = Person()
p.hello()
# works! But:
oldperson.hello()
# does not
来电的详细信息不明确,请阅读e-satis' excellent answer on 'What is a metaclass in Python?'。
答案 1 :(得分:18)
我也一直在努力解决这个问题,并且对你的解决方案感兴趣,但是Python 3将它从我们手中夺走了:
AttributeError: attribute '__dict__' of 'type' objects is not writable
我实际上需要一个装饰器替换装饰类的(单个)超类。这里需要包含太长的描述(我尝试过,但是无法将其设置为合理的长度和有限的复杂性 - 它出现在基于Python的企业服务器的许多Python应用程序的使用环境中不同的应用程序需要稍微不同的代码变体。)
对此页面和其他类似讨论提供的提示暗示,分配给__bases__
的问题仅发生在没有定义超类的类(即,其唯一的超类是对象)的情况下。通过将我需要替换的超类定义为一个普通类的子类,我能够解决这个问题(适用于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'
答案 2 :(得分:6)
我不能保证后果,但是这段代码在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"
我们知道这是可能的。凉。但我们永远不会使用它!
答案 3 :(得分:2)
蝙蝠的权利,动态地搞乱类层次结构的所有警告都是有效的。
但是如果必须这么做的话,显然,有一个黑客可以在"deallocator differs from 'object" issue when modifying the __bases__ attribute
左右获得新的样式类。
您可以定义类对象
class Object(object): pass
从内置元类type
派生一个类。
就是这样,现在你的新风格类可以毫无问题地修改__bases__
。
在我的测试中,这实际上非常有效,因为所有现有的(在更改继承之前)它的实例及其派生类都感受到了更改的影响,包括它们mro
的更新。
答案 4 :(得分:1)
我需要一个解决方案:
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)
在单元测试代码中使用如下:
# 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()
答案 5 :(得分:1)
从技术上讲,此方法在运行时不会继承,因为 __mro__
无法更改。但是我在这里所做的是使用 __getattr__
来访问某个类的任何属性或方法。 (按照评论前的数字顺序阅读评论,更有意义)
class Sub:
def __init__(self, f, cls):
self.f = f
self.cls = cls
# 6) this method will pass the self parameter
# (which is the original class object we passed)
# and then it will fill in the rest of the arguments
# using *args and **kwargs
def __call__(self, *args, **kwargs):
# 7) the multiple try / except statements
# are for making sure if an attribute was
# accessed instead of a function, the __call__
# method will just return the attribute
try:
return self.f(self.cls, *args, **kwargs)
except TypeError:
try:
return self.f(*args, **kwargs)
except TypeError:
return self.f
# 1) our base class
class S:
def __init__(self, func):
self.cls = func
def __getattr__(self, item):
# 5) we are wrapping the attribute we get in the Sub class
# so we can implement the __call__ method there
# to be able to pass the parameters in the correct order
return Sub(getattr(self.cls, item), self.cls)
# 2) class we want to inherit from
class L:
def run(self, s):
print("run" + s)
# 3) we create an instance of our base class
# and then pass an instance (or just the class object)
# as a parameter to this instance
s = S(L) # 4) in this case, I'm using the class object
s.run("1")
所以这种替换和重定向将模拟我们想要继承的类的继承。它甚至适用于不带任何参数的属性或方法。
答案 6 :(得分:0)
如果您需要在运行时更改现有类,则上述答案很好。但是,如果您只是想创建一个由其他类继承的新类,那么有一个更清晰的解决方案。我从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中的仿函数非常相似。