Как правильно создать экземпляр класса с функцией exec ()? - PullRequest
0 голосов
/ 06 сентября 2018

Я делаю графический интерфейс с использованием библиотеки TKinter из Python. Я хочу, чтобы пользователь выбрал опцию в Combobox и затем нажал кнопку, которая должна создать экземпляр класса с именем выбранной опции. Чтобы сохранить код, я решил использовать функцию exec() следующим образом: exec('instance = ' + comboExample.get() + '()'). Это запускает метод __init__() класса, но когда я пытаюсь вызвать другой метод (в данном случае из унаследованного класса), используя instance.method(), он отображает следующую ошибку: NameError: name 'instance' is not defined. Вот вам пример скрипта:

from tkinter import *
from tkinter import ttk

master = Tk()

#Create classes
class Base():
    def method(self):
        self.label = Label(master, text = self.sentence)
        self.label.pack()

class Example1(Base):
    def __init__(self):
        print('Example1 created')
        self.sentence = 'This is example 1.'

class Example2(Base):
    def __init__(self):
        print('Example2 created')
        self.sentence = 'This is example 2'

#Create Combobox and Button
combo = ttk.Combobox(master, state = 'readonly')
combo['values'] = ['Example1', 'Example2']
combo.pack()

def callback():
    exec('instance = ' + combo.get() + '()')
    #Here is the error
    instance.method()

button = Button(master, command = callback, text = 'Button')
button.pack()

master.mainloop()

Я не знаю почему, но когда я пытаюсь использовать следующий код, он работает правильно:

class Example():
    def __init__(self):
        self.text = 'This is an example'
    def add_text(self):
        print(self.text)

exec('instance = Example()')
instance.add_text()

На данный момент я нашел только одно решение, которое заключается в том, чтобы не использовать exec(), но заставляет меня тратить больше кода, чем использовать его, особенно если я хочу создать много классов, таких как Example1 и Example2. Это все как в предыдущем большом скрипте, но изменение функции callback():

def callback():
    if combo.get() == 'Example1':
        instance = Example1()
    if combo.get() == 'Example2':
        instance = Example2()
    instance.method()

Вот и все. Я начал программировать на Python только 2 месяца назад, и я также новичок в stackoverflow, поэтому, если я допустил какую-то ошибку в объяснении или что-то еще, пожалуйста, сообщите мне, и я исправлю это. Спасибо за ваше время. Любая помощь будет оценена.

Ответы [ 2 ]

0 голосов
/ 06 сентября 2018

Проблема не в вашем синтаксисе; это то, что вы пытаетесь сделать что-то незаконное. Вы не можете создавать новые локальные переменные с exec. (Причина того, что тот же код за пределами функции работает, заключается в том, что в целом вы можете создать новую глобальную переменную с exec, но это все еще плохая идея.)

Но вам также не нужно , чтобы сделать это. В Python все является объектом, включая классы. Итак, вам просто нужно получить класс из имени. Затем вы можете создать экземпляр этого класса и сохранить его в локальной переменной, просто используя тот же обычный синтаксис, который вы использовали бы для статического создания экземпляра класса и сохранения его в локальной переменной.


Правильный способ сделать это - сохранить словарь, отображающий имена для объектов классов. Если вы хотите стать умным, вы можете написать декоратор, который регистрирует классы в этом словаре, но если для вас это звучит как греческий, просто сделайте это явно:

classes = {'Spam': Spam, 'Eggs': Eggs}

Если у вас есть десятки из них, вы можете избежать повторения с таким пониманием:

classes = {cls.__name__: cls for cls in ('Spam', 'Eggs')}

… но в этот момент вам, вероятно, лучше научиться писать декоратор.

В любом случае, вы можете заполнить поле со списком клавишами этого словаря, а не повторять себя в строке combo['values'].

А затем, чтобы создать экземпляр, вы просто делаете это:

cls = classes[comboExample.get()]
instance = cls()

(Очевидно, вы можете свернуть это в одну строку, но я подумал, что было бы легче понять, если бы мы разделили две части.)


Если вы действительно хотите сделать это хакерским способом, вы можете. Каждый класс, созданный вами в этом модуле, уже хранится в словаре по имени - глобальном пространстве имен модуля. Это то же самое место, где вы пытались найти его неявно с помощью exec, но вы можете найти его явно, просто просмотрев его в globals(). Однако в глобальном пространстве имен также есть имена всех ваших функций, импортированных модулей, констант и переменных верхнего уровня и т. Д., Поэтому обычно это плохая идея. (Очевидно, что exec имеет точно такие же проблемы.)

0 голосов
/ 06 сентября 2018

Вы не должны использовать exec для этой цели. exec - мощный инструмент, но он не подходит для этой работы.

Гораздо более простой подход заключается в создании сопоставления пользовательских входных данных с классами. Затем вы можете использовать это отображение как для комбинированного списка, так и для обратного вызова.

Пример:

...
mapping = {"Example1": Example1, "Example2": Example2}

#Create Combobox and Button
combo = ttk.Combobox(master, state = 'readonly')
combo['values'] = sorted(mapping.keys())
combo.pack()

def callback():
    class_name = combo.get()
    cls = mapping[class_name]
    instance = cls()
    instance.method()
...

Вы можете даже автоматически сгенерировать отображение, перебирая список классов, хотя для этого примера это выглядит как перебор.

...