2

This is a follow-up question to How do you programmatically set an attribute?.

I see how I can set an attribute value using setattr(x, attr, value).

How might I invoke a method on attr?

For one example, I want to create and set the value of Tkinter Entry objects corresponding to a dictionary wherein the keys are attributes and the values are values to be assigned to the attributes.

My brute force approach works:

from tkinter import Tk, Button, filedialog, Label, Frame, BOTH, Entry, END, StringVar


class Example(Frame):
    def __init__(self, master=None):
        """Initialize UI and set default values."""
        # https://stackoverflow.com/questions/285061/how-do-you-programmatically-set-an-attribute.

        Frame.__init__(self, master)
        self.master = master

        myDict = {'a': 'String A', 'b': 'String B', 'c': 'String C'}

        self.a = StringVar()
        self.a.set(myDict['a'])
        self.aEntry = Entry(self, textvariable=self.a)
        self.aEntry.grid(row=0, column=1)

        self.b = StringVar()
        self.b.set(myDict['b'])
        self.bEntry = Entry(self, textvariable=self.b)
        self.bEntry.grid(row=1, column=1)

        self.c = StringVar()
        self.c.set(myDict['c'])
        self.cEntry = Entry(self, textvariable=self.c)
        self.cEntry.grid(row=2, column=1)

        self.pack(fill=BOTH, expand=1)

root = Tk()
app = Example(root)
root.mainloop()

A loop approach would look something like this:

from tkinter import Tk, Button, filedialog, Label, Frame, BOTH, Entry, END, StringVar


class Example(Frame):
    def __init__(self, master=None):
        """Initialize UI and set default values."""
        # https://stackoverflow.com/questions/285061/how-do-you-programmatically-set-an-attribute.
        Frame.__init__(self, master)
        self.master = master

        myDict = {'a': 'String A', 'b': 'String B', 'c': 'String C'}

        rowCount = 0
        for key, value in myDict.items():
            setattr(self, key, StringVar()) # Works

            # self.a.set('String A')
            self.key.set(value) # Does not work

            # self.aEntry = Entry(self, textvariable=self.a)
            setattr(self, key + 'Entry', Entry(self, textvariable=???)) # Does not work

            # self.aEntry.grid(row=0, column=1)
            self.key + 'Entry'.grid(row=rowCount, column=1) # Does not work
            rowCount += 1

        self.pack(fill=BOTH, expand=1)


root = Tk()
app = Example(root)
root.mainloop()

How might I rewrite the lines labelled "# Does not work" so the loop works?

This isn't just a Tkinter question. I can think of other cases where one might want to invoke a method on an attribute, the name of which is assigned to a string variable. I would appreciate coaching.

Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • You should have a very good reason to have separate `aEntry`, `bEntry`, and `cEntry` attributes instead of a single `dict` of `Entry` objects. – chepner Mar 27 '18 at 17:08
  • There is almost never, ever a good reason to dynamically add attributes to an object. The better solution is to use a dictionary to keep track of dynamic objects. It would help if you could clarify why you think dynamic attributes are the solution to your problem. We can answer the literal question that you asked, but I doubt it would get you closer to a proper solution to your real problem, whatever that is. – Bryan Oakley Mar 27 '18 at 17:27

2 Answers2

1

I wouldn't use separate attributes for this; I'd use a dict of associated pairs of Entry and StringVar objects.

getattr and setattr have their places, but not to replace the reasonable use of a dict.

class Example(Frame):
    def __init__(self, master=None):
        """Initialize UI and set default values."""
        # https://stackoverflow.com/questions/285061/how-do-you-programmatically-set-an-attribute.
        Frame.__init__(self, master)
        self.master = master

        myDict = {'a': 'String A', 'b': 'String B', 'c': 'String C'}

        self.entries = {}

        for rowCount, (key, value) in enumerate(myDict.items()):
            sv = StringVar()
            sv.set(value)

            e = Entry(self, textvariable=sv)
            e.grid(row=rowCount, column=1)

            self.entries[key] = (e, sv)

        self.pack(fill=BOTH, expand=1)
chepner
  • 497,756
  • 71
  • 530
  • 681
1

To literally answer the question that you asked, you would use getattr to get the reference to the attribute, at which point you can treat it like the real attribute.

setattr(self, key, StringVar())
var = getattr(self, key)
var.set(...)

However, this is a bad idea. Your code will be much easier to write, understand, and modify if you use a dictionary to keep track of your dynamic content.

For example:

rowCount = 0
self.vars = {}
self.entries = {}
for key, value in myDict.items():

    self.vars[key] = StringVar()
    self.vars[key].set(value)
    self.entries[key] = Entry(self, textvariable=self.vars[key])
    self.entries[key].grid(row=rowCount, column=1)
    rowCount += 1
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685