2013-03-06 4 views
56

Например, у меня есть базовый класс следующим образом:Как динамически создавать производные классы от базового класса

class BaseClass(object): 
    def __init__(self, classtype): 
     self._type = classtype 

Из этого класса я получить несколько других классов, например,

class TestClass(BaseClass): 
    def __init__(self): 
     super(TestClass, self).__init__('Test') 

class SpecialClass(BaseClass): 
    def __init__(self): 
     super(TestClass, self).__init__('Special') 

Есть хороший, вещий способ создать эти классы динамически при вызове функции, которая помещает новый класс в мою текущую область, как:

foo(BaseClass, "My") 
a = MyClass() 
... 

Как будут комментарии и вопросы почему Мне нужно это: производные классы имеют одну и ту же внутреннюю структуру с разницей, что конструктор принимает несколько ранее не определенных аргументов. Так, например, MyClass принимает ключевые слова a, а конструктор класса TestClass принимает b и c.

inst1 = MyClass(a=4) 
inst2 = MyClass(a=5) 
inst3 = TestClass(b=False, c = "test") 

Но они никогда не должны использовать тип класса в качестве входного аргумента, как

inst1 = BaseClass(classtype = "My", a=4) 

я получил эту работу, но предпочел бы другой путь, то есть динамически создаваемые объекты класса.

+0

Чтобы убедиться, что тип экземпляра изменяется в зависимости от прилагаемых аргументов? Например, если я даю 'a', он всегда будет' MyClass', а 'TestClass' никогда не примет' a'? Почему бы просто не объявить все 3 аргумента в 'BaseClass .__ init __()', но по умолчанию все они 'None'? 'def __init __ (self, a = None, b = None, C = None)'? – acattle

+0

Я не могу объявить что-либо в базовом классе, так как я не знаю всех аргументов, которые я мог бы использовать. У меня может быть 30 разных класов с 5 разными аргументами, поэтому объявление 150 аргументов в конструкторе не является решением. – Alex

ответ

90

Этот бит кода позволяет создавать новые классы с динамическими именами и именами параметров. Проверка параметров в __init__ просто не позволяет неизвестные параметры, если вам нужны другие проверки, как типа, или что они являются обязательными, просто добавить логику там:

class BaseClass(object): 
    def __init__(self, classtype): 
     self._type = classtype 

def ClassFactory(name, argnames, BaseClass=BaseClass): 
    def __init__(self, **kwargs): 
     for key, value in kwargs.items(): 
      # here, the argnames variable is the one passed to the 
      # ClassFactory call 
      if key not in argnames: 
       raise TypeError("Argument %s not valid for %s" 
        % (key, self.__class__.__name__)) 
      setattr(self, key, value) 
     BaseClass.__init__(self, name[:-len("Class")]) 
    newclass = type(name, (BaseClass,),{"__init__": __init__}) 
    return newclass 

И это работает, как это, например:

>>> SpecialClass = ClassFactory("SpecialClass", "a b c".split()) 
>>> s = SpecialClass(a=2) 
>>> s.a 
2 
>>> s2 = SpecialClass(d=3) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 8, in __init__ 
TypeError: Argument d not valid for SpecialClass 

Я вижу, вы просите для вставки динамических имен в области видимости имен - теперь что не считается хорошим р ractice в Python - вы либо имена переменных, известные во время кодирования или данные - и имена узнали во время выполнения больше «данные», чем «переменные» -

Таким образом, вы можете просто добавить свои классы в словарь и использовать их оттуда:

name = "SpecialClass" 
classes = {} 
classes[name] = ClassFactory(name, params) 
instance = classes[name](...) 

и если ваш дизайн абсолютно необходимы имена прийти в сферу, просто сделать то же самое, но использовать словарь возвращенное globals() вызова вместо произвольного словаря:

name = "SpecialClass" 
globals()[name] = ClassFactory(name, params) 
instance = SpecialClass(...) 

(Действительно, функция фабрики классов могла бы динамически вставлять имя в глобальную область вызывающего абонента, но это еще хуже, и не совместимо с реализацией Python.Способ сделать это - получить кадр выполнения вызывающего абонента через sys._getframe(1) и установить имя класса в глобальном словаре фрейма в его атрибуте f_globals).

update, tl; dr: Этот ответ стал популярным, но тем не менее его очень специфичным для тела вопроса. Общий ответ о том, как «динамически создавать производные классы от базового класса» в Python является простым вызовом type передавая новое имя класса, кортеж с BaseClass (ами) и __dict__ тела для нового класса -подобное это:

>>> new_class = type("NewClassName", (BaseClass,), {"new_method": lambda self: ...}) 

обновления
кто нуждается в этом должны также проверить проект dill - он утверждает, что способно протравить и unpickle классов так же, как маринад делают к обычным объектам, и жил в ней в некоторые из моих тестов.

+1

Если я правильно помню, 'BaseClass .__ init __()' был бы лучше, чем более общий 'super (self .__ class __) .__ init __()', который играет более красиво, когда новые классы подклассы. (Ссылка: http://rhettinger.wordpress.com/2011/05/26/super-considered-super/) – EOL

+0

@EOL: Это было бы для статически объявленных классов, но поскольку у вас нет фактического имени класса для жесткого кода как первый параметр для Super, для которого требуется много танцев. Попробуйте заменить его супер-супер и создайте подкласс динамически созданного класса, чтобы понять его; И, с другой стороны, в этом случае вы можете иметь базовый класс как общий объект, из которого можно вызвать '__init__'. – jsbueno

+0

Теперь у меня было время посмотреть на предлагаемое решение, но это не совсем то, что я хочу. Во-первых, похоже, что '__init__'' BaseClass' вызывается с одним аргументом, но на самом деле 'BaseClass .__ init__' всегда принимает произвольный список аргументов ключевого слова. Во-вторых, решение выше устанавливает все разрешенные имена параметров как атрибуты, чего я не хочу. ЛЮБОЙ аргумент должен перейти в «BaseClass», но тот, который я знаю при создании производного класса. Вероятно, я обновлю вопрос или попробую более точную, чтобы сделать его более ясным. – Alex

60

type() это функция, которая создает классы (и, в частности, подклассов):

def set_x(self, value): 
    self.x = value 

SubClass = type('SubClass', (BaseClass,), {'set_x': set_x}) 
# (More methods can be put in SubClass, including __init__().) 

obj = SubClass() 
obj.set_x(42) 
print obj.x # Prints 42 
print isinstance(obj, BaseClass) # True 
+0

Чтобы понять этот пример с помощью Python 2.7, я получил 'TypeError', который сказал, что' __init __() принимает ровно 2 аргумента (1 задано) '. Я обнаружил, что добавить что-то (что?), Чтобы заполнить пробел, было бы достаточно. Например, 'obj = SubClass ('foo')' работает без ошибок. – DaveL17

+0

Это нормально, так как «SubClass» является подклассом «BaseClass» в вопросе, а «BaseClass» принимает параметр ('classtype', который является' 'foo'' в вашем примере). – EOL

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