2011-12-17 2 views
25

Можно ли добавить базовый класс к экземпляру объекта (а не классу!) Во время выполнения? Что-то вдоль линий того, как Object#extend работ в Ruby:Динамически смешивать базовый класс с экземпляром в Python

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" 
+0

Зачем это делать без комментариев? Может быть, потому что слово Ruby появляется в тексте? oO –

+1

Uuuuurrgghhh !!!!! Изменение экземпляра без изменения класса - это рецепт катастрофы. Более удобный способ сделать это - сделать подкласс «Человек» и смешать «Джентльмен» с этим. – katrielalex

+1

@katrielalex: Вероятно, вы правы в большинстве случаев. Тем не менее, мне нужна эта функциональность, потому что я хочу добавить функциональность в стороннюю библиотеку, интерфейс которой я не могу изменить. Мне пришлось выбирать между mixins или прокси-шаблоном, из которых последнее мне не очень нравится. –

ответ

32

Это динамически определяет новый класс GentlePerson и переназначение p «s класс к нему:

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» s, но не изменяет класс pPerson. Таким образом, другие экземпляры Person не затронуты (и поднимут AttributeError, если были вызваны introduce_self).


Хотя это не было прямой вопрос в вопросе, я добавлю для 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 
+0

Очень приятно. Я действительно пытался назначить '__class__', но это, похоже, работает только с динамически создаваемыми классами. Это решение, на которое я надеялся, спасибо. –

+0

Ницца! _Directly_ из 'object' является ключом iirc; стандартным обходным путем является определение объекта класса (объекта): pass'. – katrielalex

+0

Я немного наткнулся на это, так как я хотел, чтобы класс 'Gentleman' переопределял свойство класса Person. В этом случае я переключил порядок на «type (« GentlePerson », (Gentleman, Person), {})'. Надеюсь, что не приносит никакого зла. – letmaik

5

Хотя это уже ответил, вот функция:

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

Хорошо иметь рабочее решение, но, пожалуйста, помните, что использование черной магии должно быть хорошо обдумано, поскольку это может быть путь к черту труднодоступного и трудно отлаживаемого кода. –

7

Немного уборщик версия:

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),{}) 
+1

хорошее решение. Я использую это, чтобы переопределить методы в существующих экземплярах, и в этом случае порядок типа должен быть '(cls, base_cls)' – miraculixx

Смежные вопросы