0

I subclass Frame and Label in Tkinter in order to automatically .pack() them (this is just a usage example, my question is not strictly related to Tkinter). The definitions are the same for both classes except for the labels:

class Frame(tk.Frame):
    def __init__(self, parent, **kwargs):
        objparams, packparams = dispatch_parameters(self, **kwargs)
        tk.Frame.__init__(self, parent, objparams)
        self.pack(packparams)

class Label(tk.Label):
    def __init__(self, parent, **kwargs):
        objparams, packparams = dispatch_parameters(self, **kwargs)
        tk.Label.__init__(self, parent, objparams)
        self.pack(packparams)

In order not to repeat the same code for these two classes I am wondering how to reuse it by just varying the "label". I am aware that "Frame" above means different things within the class (a class name, an actual class, ...) so I am trying to understand if it is correct to look at something along the lines of (this is pseudo-code to try to explain my point)

for classname in ["Frame", "Label"]:
  class <<classname>>(tk.<<classname>>):
    def __init__(self, parent, **kwargs):
        objparams, packparams = dispatch_parameters(self, **kwargs)
        tk.<<classname>>.__init__(self, parent, objparams)
        self.pack(packparams)

Is there a pythonic approach to such cases of code reuse? Or should I stick with defining classes one after the others, even if the code is very similar?

Note 1: I believe this question is very similar to another one, but for objective-c

Note 2: I deliberately omitted the tkinter tag as the example I gave is just a specific instance of a general case

2

1 Answer 1

1

You can create classes dynamically with the type() function:

def class_factory(classname):
    base_class = getattr(tk, classname)

    def __init__(self, parent, **kwargs):
        objparams, packparams = dispatch_parameters(self, **kwargs)
        base_class.__init__(self, parent, objparams)
        self.pack(packparams)

    return type(classname, (base_class, object), {'__init__': __init__})

for classname in ["Frame", "Label"]:
    globals()[classname] = class_factory(classname)

When given 3 arguments, the type() function produces a class from those. The first argument is the name, the second a tuple of base classes, and the third is a mapping representing the class body.

The object base is needed here to make this work with the old-style classes from the Tkinter module.

Another option would be to scope the class statement in function:

def class_factory(classname):
    base_class = getattr(tk, classname)

    class CustomClass(base_class):
        def __init__(self, parent, **kwargs):
            objparams, packparams = dispatch_parameters(self, **kwargs)
            base_class.__init__(self, parent, objparams)
            self.pack(packparams)

    CustomClass.__name__ = classname
    return CustomClass

Here we don't have to mix in the object baseclass.

Sign up to request clarification or add additional context in comments.

2 Comments

Ah, there appears to be a problem with my approach and the old-style classes from the Tkinter module; this won't actually work for old-style classes (type() throws an exception here). I'm investigating a work-around.
The second option works perfectly, thanks.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.