1

I have a class in which I find myself define similar groups of variables many times, to the point that I believe it would be clearer if I were able to do so in a loop, as follows:

import tkinter as tk
from tkinter import ttk

class MyClass:
    def __init__(self):
        self.initializeVariables()

    def initializeVariables(self):
        varnames = ['a', 'b', 'c', 'd']
        for var in varnames:
            exec("self.bg{0} = ttk.Frame(self.Frame)".format(var))
            exec("self.tb{0} = ttk.Entry(bg{0}, width=4)".format(var))
            exec("self.tb{0}.grid(row=i)".format(var))

I have more elements than this, this example is just meant to be illustrative. I would like to know:

  1. Is there a way to accomplish this without using the exec function? I tried with globals(), but was not successful
  2. Is doing this inadvisable? In my opinion, this makes the code easier to read, but I am not aware of typical conventions in this arena.
1
  • Just setattr(self, 'tb' + var, ttk.Entry(self.Frame, width=4) Commented Sep 18, 2015 at 14:34

2 Answers 2

3

There's no need to have individual object attributes for each widget. Creating attributes in a loop adds complexity without providing any real value in this case.

I suggest using a list or dictionary to store the references:

def initializeVariables(self):
    varnames = ['a', 'b', 'c', 'd']
    self.frames = {}
    self.entries = {}
    for var in varnames:
        self.frames[var] = ttk.Frame(...)
        self.entries[var] = ttk.Entry(...)

You can then later access them by their name. for example:

self.entries["c"].get()

Unless you need to access the frames in other parts of the code, you can use a local variable for the frame:

for var in varnames:
    frame = tk.Frame(...)
    self.entries[var] = ttk.Entry(frame, ...)
Sign up to request clarification or add additional context in comments.

1 Comment

Accepting this answer based on the comments from Martijn Pieter's answer.
2

You have several options far better than exec:

  • Use the setattr() function to set arbitrary attributes, given a string name for the attribute:

    varnames = ['a', 'b', 'c', 'd']
    for var in varnames:
        frame = ttk.Frame(self.Frame)
        entry = ttk.Entry(frame, width=4)
        entry.grid(row=i)
        setattr(self, 'bg{0}'.format(var), frame)
        setattr(self, 'tb{0}'.format(var), entry)
    
  • Use vars(self) to access the instance namespace as a dictionary:

    namespace = vars(self)
    varnames = ['a', 'b', 'c', 'd']
    for var in varnames:
        frame = ttk.Frame(self.Frame)
        entry = ttk.Entry(frame, width=4)
        entry.grid(row=i)
        namespace['bg{0}'.format(var)] = frame
        namespace['tb{0}'.format(var)] = entry
    
  • Access the self.__dict__ namespace directly:

    varnames = ['a', 'b', 'c', 'd']
    for var in varnames:
        frame = ttk.Frame(self.Frame)
        entry = ttk.Entry(frame, width=4)
        entry.grid(row=i)
        self.__dict__['bg{0}'.format(var)] = frame
        self.__dict__['tb{0}'.format(var)] = entry
    

vars(self) is basically a forward-compatible and API-friendly way of spelling self.__dict__. I prefer using setattr() as this is compatible with classes using __slots__ or hooking into __setattr__.

Setting attributes in a loop is fine (great even, as you are applying the DRY principle), using exec is not the best way to go about this however.

Do consider using a dictionary or list for repeated or grouped attributes. A dictionary or list makes it much easier to later access the whole group of elements in go:

frames = self.frames = {}
framed_entries = self.framed_entries = {}
varnames = ['a', 'b', 'c', 'd']
for var in varnames:
    frame = ttk.Frame(self.Frame)
    entry = ttk.Entry(frame, width=4)
    entry.grid(row=i)
    frames['bg{0}'.format(var)] = frame
    framed_entries['tb{0}'.format(var)] = entry

Now you can access those same frames and entries with a simple loop over a dictionary, or directly address each one with a suitable key.

5 Comments

This is great, thank you! However, can you please address my second question too?
@wesanyer: there, added a sentence.
Perfect. I will accept this as an answer as soon as SO allows me too. Thanks again.
While I think this is a good, literal answe for the question that was asked, I think creating object attributes in this way will lead to hard-to-understand code.
@BryanOakley: true; using a dictionary or list lets you group the whole lot under a common 'namespace' separate from the instance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.