Nikita Aleksandrov
Nikita Aleksandrov

Reputation: 33

How to get rid of exec()

Is there a way to use something other than exec() and to make this shorter/look more pro?

for list_ in lists_to_activate:
    for widget in list_:
        exec(
            '{1} = tk.Label({0}, text="{2}", background="{3}", fg="{4}", font={5})'.format(widget[0],
                                                                                           widget[1],
                                                                                           widget[2],
                                                                                           widget[3],
                                                                                           widget[4],
                                                                                           widget[5]))
        exec('{}.pack({})'.format(widget[1],
                                  widget[7]))
        exec('{0}.bind("<Button-1>", lambda event, self=self, object={0}: self.button(object, "{0}"))'.format(
            widget[1]))

        if widget[6] == 1:
            exec('{0}.bind("<Enter>", lambda event, self=self, object={0}: self.is_focus(object, True))'.format(
                widget[1]))
            exec(
                '{0}.bind("<Leave>", lambda event, self=self, object={0}: self.is_focus(object, False))'.format(
                    widget[1]))

This is how lists_to_activate looks like. lists_to_activate is a list of lists

HEADER_WLIST = [
                ['self.header_frame', 'self.title', 'EVIL MARKET ANALYZER', 'black', 'white', '("FuturaBookC", 21)', 0,
                 'side="left",padx=35,pady=13'],
                ['self.header_frame', 'self.settings', 'settings', 'black', 'white', '("FuturaLightC", 22)', 1,
                 'side="right",padx=20,pady=10'],
                ['self.header_menu', 'self.menu_1', '  TODAY  ', '#ff402f', 'white', '("FuturaBookC", 35)', 1,
                 'side="left", fill="both", expand = 1'],
                ['self.header_menu', 'self.menu_2', '   NEWS   ', 'white', 'black', '("FuturaBookC", 35)', 1,
                 'side="left",fill="both", expand = 1'],
                ['self.header_menu', 'self.menu_3', '  TWITTER ', 'white', 'black', '("FuturaBookC", 35)', 1,
                 'side="left", fill="both", expand = 1'],
                ['self.header_menu', 'self.blank', ' ', 'white', 'black', '("FuturaBookC", 0)', 0,
                 'side="left",pady=25'],
                ['self.header_menu', 'self.menu_4', 'CONCLUSION', 'white', 'black', '("FuturaBookC", 35)', 1,
                 'side="right", fill="both", expand = 1']
]
CNBC_WLIST = []...

Thank you in advance :)

Upvotes: 2

Views: 110

Answers (2)

TheEagle
TheEagle

Reputation: 5992

You could use getattr and setattr:

for list_ in lists_to_activate:
    for widget in list_:
        cur_wid = tk.Label(getattr(self, widget[0].replace("self.", "")), text=widget[2], background=widget[3], fg=widget[4], font=widget[5])
        cur_wid.pack(widget[7])
        cur_wid.bind("<Button-1>", lambda event, self=self, object=cur_wid: self.button(object, widget[1]))

        if widget[6] == 1:
            cur_wid.bind("<Enter>", lambda event, self=self, object=cur_wid: self.is_focus(object, True))
            cur_wid.bind("<Leave>", lambda event, self=self, object=cur_wid: self.is_focus(object, False))
        setattr(self, widget[1].replace("self.", ""), cur_wid)

Upvotes: 0

Ted Klein Bergman
Ted Klein Bergman

Reputation: 9756

Warning of exec or eval

Whenever you use exec or eval, you should reconsider what you're doing, as those functions should almost never be used. Using them indicates a severe design flaw as code and data becomes the same thing, when they should be separated. Code is logic, and data is data.

If you start coding using either of those functions, it's going to be hard to change your design afterwards. So avoid them from the beginning!

What to do instead

It's hard to know the solution for your specific problem, so I had to guess. But this shouldn't be too different from what you could do.

First, create a class that contains the actual data you want to operate on, with good names. This makes things clearer as you'll write widget.text instead of widget[2].

class Widget:
    def __init__(self, text, background, foreground, font, config, is_focusable):
        self.text = text
        self.background = background
        self.foreground = foreground
        self.font = font
        self.config = config
        self.is_focusable = is_focusable

Then create a function that encapsulates the logic, i.e. the common operations, and pass in the relevant data.

def create_label(frame, widget, parent):
    label = tk.Label(frame, text=widget.text, background=widget.background, fg=widget.foreground, font=widget.font)
    label.pack(**widget.config)
    label.bind("<Button-1>", lambda event, self=parent, object=label: self.button(object, label.__name__))

    if widget.is_focusable:
        label.bind("<Enter>", lambda event, self=parent, object=label: self.is_focus(object, True))
        label.bind("<Leave>", lambda event, self=parent, object=label: self.is_focus(object, False))
    
    return label

Store the data in a proper data structures. It seems like you're trying to create labels for some frame. Using a dictionary allows you to associate a name (like the name of your attribute) with some data.

WIDGET_LIST = [
    'title':    Widget('EVIL MARKET ANALYZER', 'black', 'white', '("FuturaBookC", 21)', False, {'side': "left", 'padx': 35, 'pady': 13}),
    'settings': Widget('settings', 'black', 'white', '("FuturaLightC", 22)', True, {'side': "right", 'padx': 20, 'pady': 10})
    # And so on...
]

Then you can do iterate over the dictionary and use our function to create labels. This assumes that you use a dictionary self.labels instead of individual attributes like self.title, self.menu_1, self.settings, and so on...

for name, widget in WIDGET_LIST.items():
    self.labels[name] = create_label(self.header_menu, widget, self)

Full code

class Widget:
    def __init__(self, text, background, foreground, font, config, is_focusable):
        self.text = text
        self.background = background
        self.foreground = foreground
        self.font = font
        self.config = config
        self.is_focusable = is_focusable


WIDGET_LIST = [
    'title':    Widget('EVIL MARKET ANALYZER', 'black', 'white', '("FuturaBookC", 21)', False, {'side': "left", 'padx': 35, 'pady': 13}),
    'settings': Widget('settings', 'black', 'white', '("FuturaLightC", 22)', True, {'side': "right", 'padx': 20, 'pady': 10})
    # And so on...
]


def create_label(frame, widget, parent):
    label = tk.Label(frame, text=widget.text, background=widget.background, fg=widget.foreground, font=widget.font)
    label.pack(**widget.config)
    label.bind("<Button-1>", lambda event, self=parent, object=label: self.button(object, label.__name__))

    if widget.is_focusable:
        label.bind("<Enter>", lambda event, self=parent, object=label: self.is_focus(object, True))
        label.bind("<Leave>", lambda event, self=parent, object=label: self.is_focus(object, False))
    
    return label

# Then use this in your class which contains the different labels, don't have this globally.
for name, widget in WIDGET_LIST.items():
    self.labels[name] = create_label(self.header_menu, widget, self)

Upvotes: 4

Related Questions