Search code examples
pythonclassotree

Python: Construct class (and variable names) through a function?


I recently started to work with Python's classes, since I need to work with it through the use of OTree, a Python framework used for online experiment.

In one file, I define the pages that I want to be created, using classes. So essentially, in the OTree system, each class corresponds to a new page. The thing is, all pages (so classes) are basically the same, at the exception to some two parameters, as shown in the following code:

class Task1(Page):
    form_model = 'player'
    form_fields = ['Envie_WordsList_Toy']
    def is_displayed(self):
        return self.round_number == self.participant.vars['task_rounds'][1]
    def vars_for_template(player):
        WordsList_Toy= Constants.WordsList_Toy.copy()
        random.shuffle(WordsList_Toy)
        return dict(
            WordsList_Toy=WordsList_Toy
        )
    @staticmethod
    def live_method(player, data):
        player.WTP_WordsList_Toy = int(data)
    def before_next_page(self):
        self.participant.vars['Envie_WordsList_Toy'] = self.player.Envie_WordsList_Toy
        self.participant.vars['WTP_WordsList_Toy'] = self.player.WTP_WordsList_Toy

So here, the only thing that would change would be the name of the class, as well as the suffix of the variable WordsList_ used throughout this code, which is Toy.

Naively, what I tried to do is to define a function that would take those two parameters, such as this:

def page_creation(Task_Number,name_type):
    class Task+str(Task_Number)(Page):
        form_model = 'player'
        form_fields = ['Envie_WordsList_'+str(name_type)]

        def is_displayed(self):
            return self.round_number == self.participant.vars['task_rounds'][1]

        def vars_for_template(player):
            WordsList_+str(name_type) = Constants.WordsList+str(name_type).copy()
            random.shuffle(WordsList_+str(name_type))
            return dict(
                WordsList_+str(name_type)=WordsList_+str(name_type)
            )

        @staticmethod
        def live_method(player, data):
            player.WTP_WordsList_+str(name_type) = int(data)

        def before_next_page(self):
            self.participant.vars['Envie_WordsList_+str(name_type)'] = self.player.Envie_WordsList_+str(name_type)
            self.participant.vars['WTP_WordsList_+str(name_type)'] = self.player.WTP_WordsList_+str(name_type)

Obviously, it does not work since I have the feeling that it is not possible to construct variables (or classes identifier) this way. I just started to really work on Python some weeks ago, so some of its aspects might escape me still. Could you help me on this issue? Thank you.


Solution

  • You can generate dynamic classes using the type constructor:

    
    MyClass = type("MyClass", (BaseClass1, BaseClass2), {"attr1": "value1", ...})
    

    Thus, according to your case, that would be:

    cls = type(f"Task{TaskNumber}", (Page, ), {"form_fields": [f"Envive_WordList_{name_type}"], ...})
    

    Note that you still have to construct your common methods like __init__, is_displayed and so on, as inner functions of the class factory:

    def class_factory(*args, **kwargs):
        ...
        def is_displayed(self):
            return self.round_number == self.participant.vars['task_rounds']
    
        def vars_for_template(player):
            ...
    
        # Classmethod wrapping is done below
        def live_method(player, data):
            ...
        cls = type(..., {
            "is_displayed": is_displayed, 
            "vars_for_template": vars_for_template,
            "live_method": classmethod(live_method),
            ...,
        }
    
    
    

    @classmethod could be used as a function - {"live_method": classmethod(my_method)}