Nested Class factory with tkinter

If I'm getting it right I think you mean to have some sort of a Base class that has some configuration which a set of frames have in common like for example you want to have 10 frames of 300x400 geometry and of a brown background in common and later having another set of frames with a different configuration, which can be accessed in an organised way. Then I would say you have an interesting way but I would rather use a list or a dictionary anyway.

Here are some approaches to achieve this goal.

Approach 1

In this approach, I've created a function that returns a dictionary with all the frames created and contained in it like in format ({..., 'F20': tkinter.frame, ...})

import tkinter as tk

def get_base_frames(num, master, cnf={}, **kw):
    """
    Create list of frames with common configuration options.

    Args:
        num (int): Number of frames to be created.
        master (tk.Misc): Takes tkinter widget or window as a parent for the frames.
        cnf (dict): configuration options for all the frames.
        kw: configuration options for all the frames.

    Return:
        Dictionary of frames ({..., 'F20': tkinter.frame, ...}).
    """
    return {f'F{n+1}': tk.Frame(master, cnf=cnf, **kw) for n in range(num)}

if __name__ == "__main__":
    root = tk.Tk()
    frame_holder = get_base_frames(10, root, width=50, height=50, bg='brown')

    # Frames can be accessed through their names like so.
    print(frame_holder.get('F1'))

Approach 2

Here I've used class and objects. Where I made this class Frames though you can name it anything you want. I also added some important method like cget() and configure(), through these methods once get a value to an option and configure options for all the frames respectively. There are more useful methods like bind() and bind_all() if you need those just modify this class as per your need.

import tkinter as tk

class Frames(object):
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__()
        num = cnf.pop('num', kw.pop('num', 0))
        for n in range(num):
            self.__setattr__(f'F{n+1}', tk.Frame(master, cnf=cnf, **kw))

    def configure(self, cnf={}, **kw):
        """Configure resources of a widget.

        The values for resources are specified as keyword
        arguments. To get an overview about
        the allowed keyword arguments call the method keys.
        """
        for frame in self.__dict__:
            frame = self.__getattribute__(frame)
            if isinstance(frame, tk.Frame):
                if not cnf and not kw:
                    return frame.configure()
                frame.configure(cnf=cnf, **kw)
    config = configure

    def cget(self, key):
        """Return the resource value for a KEY given as string."""
        for frame in self.__dict__:
            frame = self.__getattribute__(frame)
            if isinstance(frame, tk.Frame):
                return frame.cget(key)
    __getitem__ = cget


if __name__ == "__main__":
    root = tk.Tk()
    frame_holder = Frames(root, num=10, width=10, 
                          bd=2, relief='sunken', bg='yellow')

    # Frames can be accessed through their naems like so.
    print(frame_holder.F4) 
    print(frame_holder['bg'])
    frame_holder.config(bg='blue')
    print(frame_holder['bg'])

Approach 3

If you want to have differently configured frames contained in one class, where all those frames have some method in common or some attribute in common.

import tkinter as tk

class BaseFrame(tk.Frame):
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master=master, cnf={}, **kw)

    def common_function(self):
        """This function will be common in every 
        frame created through this class."""
        # Do something...

class FrameHolder(object):
    def __init__(self, master=None, cnf={}, **kw):
        kw = tk._cnfmerge((cnf, kw))
        num = kw.pop('num', len(kw))

        for n in range(num):
            name = f'F{n+1}'
            cnf = kw.get(name)
            self.__setattr__(name, BaseFrame(master, cnf))

if __name__ == "__main__":
    root = tk.Tk()

    holder = FrameHolder(root, 
                    F1=dict(width=30, height=40, bg='black'),
                    F2=dict(width=50, height=10, bg='green'),
                    F3=dict(width=300, height=350, bg='blue'),
                    F4=dict(width=100, height=100, bg='yellow'),
                    )
    print(holder.F1)
    print(holder.__dict__)

Approach 4

This is the approach that OP is trying to achieve.

import tkinter as tk


class BaseClass(tk.Frame):
    def __init__(self, master, cnf={}, **kw):
        kw = tk._cnfmerge((cnf, kw))
        cnf = [(i, kw.pop(i, None))
               for i in ('pack', 'grid', 'place') if i in kw]
        tk.Frame.__init__(self, master, **kw)
        self.master = master

        if cnf:
            self.__getattribute__(cnf[-1][0])(cnf=cnf[-1][1])


class Container(tk.Frame):
    """Container class which can contain tkinter widgets. 
    Geometry (pack, grid, place) configuration of widgets 
    can also be passed as an argument.

    For Example:-

    >>> Container(root, widget=tk.Button,
              B5=dict(width=30, height=40, bg='black',
                      fg='white', pack=(), text='Button1'),
              B6=dict(width=50, height=10, bg='green', text='Button2',
                      place=dict(relx=0.5, rely=1, anchor='s')))
    """
    BaseClass = BaseClass

    def __init__(self, master=None, cnf={}, **kw):
        kw = tk._cnfmerge((cnf, kw))
        wid = kw.pop('widget', tk.Frame)
        for name, cnf in kw.items():
            geo = [(i, cnf.pop(i, None))
                   for i in ('pack', 'grid', 'place') if i in cnf]
            setattr(Container, name, wid(master, cnf))
            if geo:
                manager, cnf2 = geo[-1]
                widget = getattr(Container, name)
                getattr(widget, manager)(cnf=cnf2)


if __name__ == "__main__":
    root = tk.Tk()

    Container(root, widget=Container.BaseClass,
              F1=dict(width=30, height=40, bg='black', relief='sunken',
                      pack=dict(ipadx=10, ipady=10, fill='both'), bd=5),
              F2=dict(width=50, height=10, bg='green',
                      pack=dict(ipadx=10, ipady=10, fill='both')),
              )

    Container(root, widget=tk.Button,
              B5=dict(width=30, height=40, bg='black',
                      fg='white', pack={}, text='Button1'),
              B6=dict(width=50, height=10, bg='green', text='Button2',
                      place=dict(relx=0.5, rely=1, anchor='s')),
              )

    print(Container.__dict__)
    root.mainloop()

A lot can be done and can be modified according to one's needs, these are just some approaches that I think will work very well to automate and keep a set of frames in shape and together.

There can be multiple ways to do this or maybe something better and efficient than these, feel free to give suggestions and share something new.


One solution to this problem, I think, as I don't fully understand your question, but this here was my solution:

import tkinter as tk
from tkinter import Frame,Button

class BaseClass(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.master = master
        self.pack()


class Holder_frame(tk.Frame):
    def __init__(self, master, frames=2):
        tk.Frame.__init__(self, master)
        self.master = master
        self.frame_names = []
        for i in range(frames):
            Holder_frame.create_frames("F"+str(i+1), self)

    @classmethod
    def create_frames(cls, name, master):
        setattr(cls, name, tk.Frame(master))

if __name__ == "__main__":
    root = tk.Tk()
    def raise1():
        print(type(Holder_frame.F1))
    def raise2():
        print(type(Holder_frame.F2))

    holder=Holder_frame(root,frames=2)
    holder.grid(row=1,column=0)
    b1 = tk.Button(root, text='1', command=raise1)
    b1.grid(row=0,column=0)
    b2 = tk.Button(root, text='2', command=raise2)
    b2.grid(row=0,column=1)
    print(Holder_frame.__dict__.items())

    root.mainloop()

The use of setattr allows one to add variables to the class, just like if you were to type a function into the code. This allows you to access frames from outside the class as somewhat of a "global variable"

I used a file to test if it work outside as an imported module too:

# main.py
from nested_class import Holder_frame
import tkinter as tk

root = tk.Tk()
holder=Holder_frame(root,frames=1000)
holder.grid(row=1,column=0)
print(Holder_frame.__dict__.items())

root.mainloop()

I hope this answers your question,

James

EDIT:

After thinking there is, what I think, to be a cleaner system for what you want. With the code from this post one can see that your my written system could be replaced by a ttk.Notebook, and by removing the top bar by using style.layout('TNotebook.Tab', []), one can see that you would get a frame widget that could have frame widgets inside of it:

import tkinter as tk
import tkinter.ttk as ttk

class multiframe_example:
    def __init__(self, master):
        self.master = master

        style = ttk.Style()
        style.layout('TNotebook.Tab', [])   
        notebook = ttk.Notebook(self.master)
        notebook.grid(row=0, column=0)

        self.master.grid_rowconfigure(0, weight=1)
        self.master.grid_columnconfigure(0, weight=1)

        tab1 = tk.Frame(self.master,  width=500, height=500, background="green")
        tab2 = tk.Frame(self.master,  width=500, height=500)
        tab3 = tk.Frame(self.master,  width=500, height=500)


        notebook.add(tab1)
        notebook.add(tab2)
        notebook.add(tab3)

        notebook.select(0) # select tab 1
        notebook.select(1) # select tab 2
        notebook.select(2) # select tab 3

def main():
    root = tk.Tk()
    root.geometry("500x500")
    multiframe_example(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Hope this code can support you and does as you would like!