如何使用exec()函数正确创建类的实例?

时间:2018-09-06 16:01:32

标签: python python-3.x tkinter class-method

我正在使用Python的TKinter库制作GUI。我希望用户从组合框中选择一个选项,然后按一个按钮,这将创建一个名为所选选项的类的实例。为了保存代码,我决定以这种方式使用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(),但比起使用它,我浪费了更多的代码,尤其是当我想创建很多类似{{1 }}和Example1。就像以前的大脚本一样,只是更改了Example2函数:

callback()

仅此而已。我仅在2个月前才开始使用Python编程,而且我在stackoverflow中还是一个新手,因此,如果我在解释或任何内容上出错了,请告诉我,我将对其进行修复。 谢谢你的时间。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:3)

问题不是您的语法;是您要尝试做非法的事情。您无法使用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存在同样的问题。)

答案 1 :(得分:1)

您不应为此目的使用execexec是功能强大的工具,但这是这项工作的错误工具。

一种更简单的方法是创建从用户输入到类的映射。然后,您可以将该映射用于组合框和回调。

示例:

...
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()
...

您甚至可以通过遍历一列类来自动生成映射,尽管对于本示例而言,这似乎有些过分。