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

Ada*_*kin 68 python inheritance dynamic

本文有一个片段__bases__,通过向类继承的类的现有类集合添加一个类来动态更改某些Python代码的继承层次结构.好的,这很难读,代码可能更清晰:

class Friendly:
    def hello(self):
        print 'Hello'

class Person: pass

p = Person()
Person.__bases__ = (Friendly,)
p.hello()  # prints "Hello"
Run Code Online (Sandbox Code Playgroud)

也就是说,Person不从Friendly源级别继承,而是通过修改__bases__Person类的属性在运行时动态添加此继承关系.但是,如果您更改FriendlyPerson成为新的样式类(通过继承自object),则会收到以下错误:

TypeError: __bases__ assignment: 'Friendly' deallocator differs from 'object'
Run Code Online (Sandbox Code Playgroud)

关于这一点的谷歌搜索似乎表明,在运行时更改继承层次结构时,新样式类和旧样式类之间存在一些不兼容性.具体来说:"新式类对象不支持赋予其基础属性".

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

免责声明:我完全意识到这是一个模糊的代码.我完全意识到,在实际的生产代码中,这样的技巧往往难以理解,这纯粹是一个思想实验,并且可以让人们了解Python如何处理与多重继承相关的问题.

Ada*_*kin 34

好的,再次,这不是您通常应该做的事情,这仅供参考.

Python在实例对象上查找方法的位置取决于__mro__定义该对象的类的属性(M ethod R esolution O rder属性).因此,如果我们可以修改__mro__of Person,我们就会得到理想的行为.就像是:

setattr(Person, '__mro__', (Person, Friendly, object))
Run Code Online (Sandbox Code Playgroud)

问题是这__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!
Run Code Online (Sandbox Code Playgroud)

这不做的是修改任何以前创建的Person实例以获得该hello()方法.例如(只是修改main()):

def main():
    oldperson = Person()
    ModifyPersonToBeFriendly()
    p = Person()
    p.hello()  
    # works!  But:
    oldperson.hello()
    # does not
Run Code Online (Sandbox Code Playgroud)

如果电话的细节type不清楚,那么请阅读e-satisf'关于'什么是Python中的元类?'的优秀答案..

  • `-1`你首先完全错过了例外的原因.你可以愉快地在Python 2和3中修改`Person .__ class __.__ bases__`,只要`Person`不直接从`object`**继承**.请参阅下面的akaRem和Sam Gulve的答案.这种解决方法只能解决您自己对问题的误解. (8认同)
  • 这是一个非常有问题的“解决方案”。在此代码的其他问题中,[它破坏了结果对象的`__dict__` 属性](https://ideone.com/6OyEcw)。 (2认同)

Mit*_*del 21

我也一直在努力解决这个问题,并且对你的解决方案很感兴趣,但Python 3将它从我们手中夺走:

AttributeError: attribute '__dict__' of 'type' objects is not writable
Run Code Online (Sandbox Code Playgroud)

我实际上需要一个装饰器来替换装饰类的(单个)超类.这里需要包含太长的描述(我尝试过,但是无法将其设置为合理的长度和有限的复杂性 - 它出现在基于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'
Run Code Online (Sandbox Code Playgroud)


aka*_*Rem 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"
Run Code Online (Sandbox Code Playgroud)

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