如何使用 exec() 函数正确创建 class 的实例?
How to correctly create an instance of a class with exec() function?
我正在使用 Python 中的 TKinter 库制作 GUI。我希望用户从组合框中 select 一个选项,然后按下一个按钮,它应该创建一个名为 selected 选项的 class 实例。为了节省代码,我决定这样使用 exec()
函数:
exec('instance = ' + comboExample.get() + '()')
。
这将启动 class 的 __init__()
方法,但是当我尝试使用 instance.method()
调用其他方法(在本例中来自继承的 class)时,它显示以下错误: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()
,但比使用它更让我浪费代码,尤其是如果我想创建很多 class 就像 Example1
和 Example2
。都和之前的大脚本一样,只是改了callback()
函数:
def callback():
if combo.get() == 'Example1':
instance = Example1()
if combo.get() == 'Example2':
instance = Example2()
instance.method()
就是这样。我在 Python 才开始编程才 2 个月前,我也是 Whosebug 的新手,所以如果我在解释或其他方面犯了一些错误,请告诉我,我会改正。
谢谢你的时间。任何帮助将不胜感激。
您不应将 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()
...
您甚至可以通过遍历 类 的列表来自动生成映射,尽管对于这个例子来说,这似乎有点过分了。
问题不在于你的语法;那是你想做一些非法的事情。您不能使用 exec
创建新的局部变量。 (函数外的相同代码起作用的原因是通常您 可以 使用 exec
创建一个新的全局变量,但这仍然是一个坏主意。)
但您也不需要那样做。在Python中,一切都是对象,包括classes。因此,您只需要从名称中获取 class。然后,您可以创建 class 的实例并将其存储在局部变量中,方法是使用与静态实例化 class 相同的常规语法并将其存储在局部变量中。
正确的方法是存储一个映射名称到 class 对象的字典。如果你想变得聪明,你可以写一个装饰器,用那个字典注册 classes,但如果你觉得这听起来像希腊语,那就明确地做:
classes = {'Spam': Spam, 'Eggs': Eggs}
如果你有几十个这样的,你可以通过这样的理解来避免重复:
from your_module import Spam, Eggs
classes = {cls.__name__: cls for cls in (Spam, Eggs)}
…但到那时你可能最好学习如何编写装饰器。
无论哪种方式,您都可以用该字典的键填充您的组合框,而不是在 combo['values']
行中重复自己。
然后,要创建一个实例,您只需这样做:
cls = classes[comboExample.get()]
instance = cls()
(显然你可以将其折叠成一行,但我认为如果我们将两部分分开会更容易理解。)
如果你真的想以一种 hacky 的方式做到这一点,你可以。您在此模块中创建的每个 class 都已按名称存储在字典中——模块的全局命名空间。这与您尝试使用 exec
隐含地找到它的位置相同,但您可以通过在 globals()
中查找来明确地找到它。然而,全局命名空间还包含所有函数、导入模块、顶级常量和变量等的名称,所以这通常不是一个好主意。 (显然,exec
有完全相同的问题。)
我正在使用 Python 中的 TKinter 库制作 GUI。我希望用户从组合框中 select 一个选项,然后按下一个按钮,它应该创建一个名为 selected 选项的 class 实例。为了节省代码,我决定这样使用 exec()
函数:
exec('instance = ' + comboExample.get() + '()')
。
这将启动 class 的 __init__()
方法,但是当我尝试使用 instance.method()
调用其他方法(在本例中来自继承的 class)时,它显示以下错误: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()
,但比使用它更让我浪费代码,尤其是如果我想创建很多 class 就像 Example1
和 Example2
。都和之前的大脚本一样,只是改了callback()
函数:
def callback():
if combo.get() == 'Example1':
instance = Example1()
if combo.get() == 'Example2':
instance = Example2()
instance.method()
就是这样。我在 Python 才开始编程才 2 个月前,我也是 Whosebug 的新手,所以如果我在解释或其他方面犯了一些错误,请告诉我,我会改正。 谢谢你的时间。任何帮助将不胜感激。
您不应将 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()
...
您甚至可以通过遍历 类 的列表来自动生成映射,尽管对于这个例子来说,这似乎有点过分了。
问题不在于你的语法;那是你想做一些非法的事情。您不能使用 exec
创建新的局部变量。 (函数外的相同代码起作用的原因是通常您 可以 使用 exec
创建一个新的全局变量,但这仍然是一个坏主意。)
但您也不需要那样做。在Python中,一切都是对象,包括classes。因此,您只需要从名称中获取 class。然后,您可以创建 class 的实例并将其存储在局部变量中,方法是使用与静态实例化 class 相同的常规语法并将其存储在局部变量中。
正确的方法是存储一个映射名称到 class 对象的字典。如果你想变得聪明,你可以写一个装饰器,用那个字典注册 classes,但如果你觉得这听起来像希腊语,那就明确地做:
classes = {'Spam': Spam, 'Eggs': Eggs}
如果你有几十个这样的,你可以通过这样的理解来避免重复:
from your_module import Spam, Eggs
classes = {cls.__name__: cls for cls in (Spam, Eggs)}
…但到那时你可能最好学习如何编写装饰器。
无论哪种方式,您都可以用该字典的键填充您的组合框,而不是在 combo['values']
行中重复自己。
然后,要创建一个实例,您只需这样做:
cls = classes[comboExample.get()]
instance = cls()
(显然你可以将其折叠成一行,但我认为如果我们将两部分分开会更容易理解。)
如果你真的想以一种 hacky 的方式做到这一点,你可以。您在此模块中创建的每个 class 都已按名称存储在字典中——模块的全局命名空间。这与您尝试使用 exec
隐含地找到它的位置相同,但您可以通过在 globals()
中查找来明确地找到它。然而,全局命名空间还包含所有函数、导入模块、顶级常量和变量等的名称,所以这通常不是一个好主意。 (显然,exec
有完全相同的问题。)