【问题标题】:Dynamically mixin a base class to an instance in Python在 Python 中将基类动态混合到实例中
【发布时间】:2012-01-22 14:50:21
【问题描述】:

是否可以在运行时将基类添加到对象实例(不是类!)?类似于 Object#extend 在 Ruby 中的工作方式:

class Gentleman(object):
  def introduce_self(self):
    return "Hello, my name is %s" % self.name

class Person(object):
  def __init__(self, name):
    self.name = name

p = Person("John")
# how to implement this method?
extend(p, Gentleman)
p.introduce_self() # => "Hello, my name is John"

【问题讨论】:

  • 呜呜呜呜呜呜!!!!!!更改实例而不更改类是灾难的根源。更好的方法是创建Person 的子类并将Gentleman 混合到其中。
  • @katrielalex:在大多数情况下你可能是对的。尽管如此,我还是需要该功能,因为我想将功能添加到我无法更改其接口的第三方库。我不得不在 mixins 或代理模式之间做出选择,我不太喜欢后者。

标签: python oop mixins


【解决方案1】:

这会动态定义一个新类GentlePerson,并将p的类重新分配给它:

class Gentleman(object):
  def introduce_self(self):
    return "Hello, my name is %s" % self.name

class Person(object):
  def __init__(self, name):
    self.name = name

p = Person("John")
p.__class__ = type('GentlePerson',(Person,Gentleman),{})
print(p.introduce_self())
# "Hello, my name is John"

根据您的要求,这会修改 p 的基础,但不会更改 p 的原始类 Person。因此,Person 的其他实例不受影响(如果调用了 introduce_self,则会引发 AttributeError)。


虽然问题中没有直接提出,但我会为 googlers 和好奇者补充说,也可以动态更改类的基础,但 (AFAIK) 仅当类不直接从 object 继承时:

class Gentleman(object):
  def introduce_self(self):
    return "Hello, my name is %s" % self.name

class Base(object):pass
class Person(Base):
  def __init__(self, name):
    self.name = name

p = Person("John")
Person.__bases__=(Gentleman,object,)
print(p.introduce_self())
# "Hello, my name is John"

q = Person("Pete")
print(q.introduce_self())
# Hello, my name is Pete

【讨论】:

  • 非常好。我实际上试图分配给__class__,但这似乎只适用于动态创建的类。这是我希望的解决方案,谢谢。
  • 不错!来自object直接是密钥iirc;标准的解决方法是定义class object(object): pass
  • 我偶然发现了它,因为我希望 Gentleman 类覆盖 Person 类的属性。对于这种情况,我将订单切换为type('GentlePerson',(Gentleman,Person),{})。希望这不会带来任何邪恶。
  • @neo:没关系。 Gentleman 不是 Person 的子类,反之亦然,因此任何一个都可以排在基础列表的首位。
【解决方案2】:

虽然已经回答了,但这里有一个函数:

def extend(instance, new_class):
    instance.__class__ = type(
          '%s_extended_with_%s' % (instance.__class__.__name__, new_class.__name__), 
          (instance.__class__, new_class), 
          {},
          )

【讨论】:

  • 有一个可行的解决方案很好,但请记住,这种黑魔法的使用应该好好思考,因为它可能是通往难以理解和难以调试的地狱之路代码。
【解决方案3】:

稍微干净的版本:

def extend_instance(obj, cls):
    """Apply mixins to a class instance after creation"""
    base_cls = obj.__class__
    base_cls_name = obj.__class__.__name__
    obj.__class__ = type(base_cls_name, (base_cls, cls),{})

【讨论】:

  • 不错的解决方案。我用它来覆盖现有实例的方法,在这种情况下,类型顺序应该是(cls, base_cls)
猜你喜欢
  • 1970-01-01
  • 2021-12-30
  • 2012-02-17
  • 2020-07-17
  • 2011-04-23
  • 2015-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多