Можете ли вы создавать классы в цикле на python?

#python #python-3.x #class #metaclass

#python #python-3.x #класс #метакласс

Вопрос:

У меня ситуация, когда я создаю кучу классов, и многие из них на самом деле в основном одинаковы, поэтому я хотел бы создать их в цикле. Они используются с системой регистрации, поэтому с точки зрения использования проблем нет, но я не уверен, как на самом деле определить класс с переменной, определяющей имя класса…

Простой пример:

 classList = ['foo', 'bar', 'baz']

for className in classList:
    class {{{className}}}_calc(BaseCalc): 
         def __init__ (self, dataFrame):
             self.column = dataFrame[className]
         def calc ():
             return self.column.sum()
 

Очевидно, что это очень упрощенный случай. Я не могу изменить аргументы на init, потому что их уже существует целая куча, которые являются частью более крупной структуры.

В остальной части примера используется синтаксис pandas, просто чтобы дать представление о том, как это используется… но на самом деле он используется с базой данных SQL и намного сложнее… Я просто не хочу защищаться: «почему вы это делаете в первую очередь?» У меня есть веские причины, оставьте все как есть.

имя_класса находится в {{{ }}} в строке класса, чтобы указать, что это переменная, и на самом деле синтаксически не корректно. Вопрос в том, «как мне обозначить, для чего я использовал {{{ }}} ?» Я думаю.

Ответом могут быть метаклассы, но я все еще не уверен, как создать ИМЯ моей переменной класса….

ETA: пытаюсь использовать ответ @python_user:

 classList = ['foooo', 'bar', 'baaz']

class Base ():
    def __init__ (self, buq):
        self.buq = buq

    def getBuq(self):
        return 'buq: '   self.buq
    

for cls in classList:
    class TEMP(Base):
        className = cls
        def __init__ (self):
            self.qux = len(cls)
            Base.__init__(self, cls)

        def blee(self, inpt):
            return inpt  self.qux
    TEMP.__name__ = f'{cls}'
    TEMP.__qualname__ = f'{cls}'
    globals()[cls] = TEMP
f = foo()

f.getBuq()

>>>> 'buq: baaz'
 

Это дает мне только класс baaz. Все три дают baaz … Я делаю что-то действительно глупое?

Комментарии:

1. используйте type() для создания класса с динамически присваиваемым именем, type(f"MyClass{number}", (), {}) . Вам придется соответствующим образом заполнять базы и dict

Ответ №1:

Вы можете сделать это так, используя globals()

 classList = ['foo', 'bar', 'baz']

for className in classList:
    class Temp:
        def __init__ (self, dataFrame):
            self.column = dataFrame[className]
        def calc ():
            return self.column.sum()
    Temp.__name__ = className
    globals()[className] = Temp
 

Затем вы можете сделать foo() , чтобы создать объект класса foo . Вы должны вызвать с аргументами, необходимыми для __init__ метода для фактического примера. Это просто для того, чтобы показать, что это работает.

 print(type(foo()).__name__) # foo
print(type(bar()).__name__) # bar
print(type(baz()).__name__) # baz
 

Если вы хотите изменить имена своих классов, вы можете сделать

 Temp.__name__ = f'{className}_calc'
globals()[f'{className}_calc'] = Temp
 

Как указано wim в комментариях, вам также необходимо установить __qualname__ . Temp.__qualname__ = f'{className}_calc'

Редактировать:

Поскольку поиск имен в методах класса выполняется во время выполнения (а не во время компиляции), фрагмент кода содержит ошибку. className всегда будет ссылаться на последний элемент в classList ( baz в данном случае), это имя существует вне области действия цикла и будет значением для className в методах для ВСЕХ классов. (например : self.column = dataFrame[className] ). <— Это всегда dataFrame['baz']

Чтобы исправить это, нужно объявить вызываемую переменную уровня класса className и присвоить className ей (ту, что из цикла). Таким образом, во время компиляции это значение будет привязано к классу. Все ссылки на className внутренние методы класса необходимо изменить, чтобы self.className код работал должным образом.

 class Temp:
    className = className # note this line
    def __init__ (self, dataFrame):
        self.column = dataFrame[self.className] # and this line with the original snippet
 

Комментарии:

1. Вероятно, это наименее грубое из решений здесь. Вы __qualname__ также должны установить.

2. Мне нравится идея этого ответа, но она не работает. Я добавлю свой код в текст вопроса…

3. Итак, если я заменю 1-ю строку на cls=cls и везде буду использовать self.cls вместо cls, это сработает! Спасибо!

4. да, это то, что я имел в виду, не обнаружил эту ошибку до тех пор, пока через час после публикации ответа, рад узнать, что это сработало

Ответ №2:

Однако вы можете использовать функцию type для создания таких динамических классов. имена = [‘name1’, ‘name2’] Предположим, вам нужно создать 10 объектов класса на python и что-то с ними сделать, например: obj_1 = MyClass() other_object .

Надеюсь, я смогу вам помочь.

Комментарии:

1. Я думаю, что это может сработать, в настоящее время я пытаюсь сделать что-то подобное с помощью: type(className ‘calc’, BaseCalc, {……}. Но словарь становится очень сложным, потому что в нем много лямбд, состоящих из нескольких строк … хм

Ответ №3:

Ну, есть какой-то грязный способ сделать это с помощью функции «exec ()», которая принимает любую строку и выполняет ее; затем вы можете создавать свои классы в виде строки и запускать это. Вот пример, который создает ваши классы, вы можете видеть, что они объявляются, просматривая глобальное пространство имен, которое печатается в конце.

 classList = ['foo', 'bar', 'baz']

class BaseCalc(): 
     def __init__ (self, dataFrame):
         self.column = dataFrame[className]
     def calc ():
         return self.column.sum()

for className in classList:
    template = '''class %s_calc(BaseCalc): 
         def __init__ (self, dataFrame):
             self.column = dataFrame[className]
         def calc ():
             return self.column.sum()''' % className
    exec(template)

print(globals())