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

Community
  • 1
  • 1
WoJ
  • 27,165
  • 48
  • 180
  • 345

1 Answers1

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 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. – Martijn Pieters Aug 04 '14 at 14:28
  • The second option works perfectly, thanks. – WoJ Aug 04 '14 at 15:10