好的,所以这里比我预期的要多一点(……好吧,也许多一点……),但我会假设你会简单地从中拿走你需要的东西或找到价值。
简短的回答是,当您的 Checkbutton 调用 cb_check 时,它传递的参数如下:
cb_check(self = self, v = v, cb = opt, opt = [])
我认为当我们这样写出 IndexError 时,很明显为什么您会收到 IndexError:您正在使用 opt 列表的长度作为索引,以便在未提供 opt 时函数使用的空列表上使用;换句话说,如果您有 5 个选项,它将尝试访问空列表 [] 上的索引 [0…4](显然,一旦访问索引 0 失败,它就会停止)。你的函数不知道你传递给它的东西叫做 v 和 opt:它只是接受你给它的一些随机引用,并将它们按位置参数的顺序放置,然后按顺序填充关键字参数,然后然后使用您告诉它使用的任何默认值填写其余的关键字参数。
半快速旁白:
在尝试修复错误时,如果我不知道出了什么问题,我会先在它与断行中涉及的所有引用中断之前插入一个 print 语句,这通常会告诉您哪些引用不包含您认为它们具有的值。如果这看起来不错,那么我将进一步介入,检查任何查找/函数返回是否有错误。例如:
def cb_check(self, v = [], cb = [], opt = []):
for ix in range(len(cb)):
print(ix, opt, v) ## First check, for sanity’s sake
print(v[ix]) ## Second Check if I still can’t figure it out, but
## this is a lookup, not an assignment, so it
## shouldn’t be the problem
print(v[ix].get()) ## Third Check, again, not an assignment
print(opt[ix]) ## “opt[ix]={something}” is an assignment, so this is
## (logically) where it’s breaking. Here we’re only
## doing a lookup, so we’ll get a normal IndexError
## instead (it won’t say “assignment”)
opt[ix]=(v[ix].get()) ##point in code where IndexError was raised
简单的解决方法是将 Checkbutton 命令更改为“lambda: self.cb_check(v,cb,opt)”或更明确(这样我们就可以进行健全性检查)“lambda: self.cb_check(v = v , cb = cb, opt = opt)。” (我会进一步提到,您可以将“lambda:”更改为“lambda v = v, cb = cb, opt = opt:”,以进一步确保您将永远引用相同的列表,但这应该无关紧要,尤其是因为我将在下面建议的更改)
[剩下的内容是:第一部分——明确解释你的代码在做什么并对其进行批评;第二部分-您如何布局的另一种方法。如前所述,以上解决了你的问题,所以剩下的只是一个改进练习]
关于您的参考名称-
有一句古老的格言“代码的阅读次数比编写的次数多得多”,Zen of Python 的一部分说:“显式优于隐式。[...] 可读性很重要。”所以不要害怕输入更多内容,以便更容易看到发生了什么(在上面的解决方案中,同样的逻辑适用于显式地将变量传递给 cb_check)。 v 可以是 varis; cb 可以是cbuttons; ix 会更好(在我看来)作为 ind 或只是简单的 index; f(在 ntkb_render 中)应该是 parent 或 master。
进口-
看起来您正在为 tkinter 执行星号 (*) 导入,或者显式导入其中的一部分。出于两个原因,我不鼓励你做这些事情中的任何一个。第一个原因与上述相同:如果只需多敲几下按键就可以更轻松地查看所有内容的来源,那么从长远来看,这是值得的。如果您需要稍后通过代码查找每个 tkinter Widget/Var/etc,那么简单地搜索“tk”比搜索“Frame”然后“Checkbutton”然后 IntVar 等等要容易得多。其次,导入偶尔会发生冲突:所以如果-例如-你这样做了
import time ## or from time import time, even
from datetime import time
生活对你来说可能有点毛茸茸。因此,“将 tkinter 作为 tk 导入”(例如)比您目前的做法更好。
cb_check-
关于这个函数,我要指出几点:
1) v、cb和opt都是函数正常工作所必需的;如果改为使用空列表引用,那么它将失败,除非您创建了 0 个 Checkbuttons(因为在“for 循环”中不会有任何东西可以迭代;无论如何,这似乎不应该发生) .这意味着它们最好只是作为位置参数(没有默认值)。如果你以这种方式编写它们,该函数会给你一个错误,说明你没有给它足够的信息来处理它,而不是一个半任意的“IndexError”。
2) 因为您为函数提供了它需要的所有信息,所以没有实际理由(根据提供的代码,无论如何)为什么函数需要成为某个对象的方法。
3) 每次选择 Checkbutton 时都会调用此函数,但会重新更新所有 Checkbuttons(而不仅仅是选中的那个)的记录值(在 opt 中)。
4) opt 列表在技术上是多余的:您已经拥有对所有 IntVar (v) 列表的引用,这些列表会实时更新/维护无需您做任何事情;执行 v[ix].get() 和执行 opt[ix 基本上一样容易strong>]:作为“.get()”调用的交换,当您最终需要该值时,您必须包含一个完整的额外函数并重复运行它以确保您的 opt 列表是最新的.更复杂的是,还有一个观点认为 v 也是多余的,但我们稍后会讨论。
另外说明一下:我不知道你为什么包装了 IntVar (v[ix] 的整数值。 get()) 带括号;它们似乎无关紧要,但我不知道您是否尝试以与 C/Java/etc 相同的方式转换值。
ntbk_render-
再次,请注意,该函数几乎提供了它需要执行的所有内容,因此感觉不像是一个方法,而是一个独立的函数(此时;同样,我们将在最后谈到这一点)。它的设置方式也意味着它需要所有这些信息,因此最好将它们作为上述位置参数。
cb 参考-
与 v 和 opt 不同,cb 引用可以提供给函数。如果我们沿着 cb 的路径通过代码,我们会发现它的长度必须始终等于 v 和 opt。假设,我们可能希望将 cb 传递给此方法而不是 v 或 opt 的原因是因为我们只关心对 的引用>cb 在我们的其余代码中。但是,请注意 cb 始终是带有 append 方法的空迭代(似乎可以安全地假设它始终是空列表)。所以要么我们应该在开始对它做任何事情之前测试它以确保它是空的(因为如果它不是,它会破坏我们的代码),或者我们应该在创建它的同时创建它 v 和 选择。不知道您的代码是如何设置的,我个人认为将它与其他两个一起初始化然后简单地在方法末尾返回它是最简单的(将“return cb”放在这个函数的结尾和“cb=[whatever].ntbk_render(f = someframe, ntbklabel = “somethug”, msg = argList)”)。回到 opt 和 v 的冗余(cb_check 中的第 4 点),因为我们将所有 Checkbuttons 保留在 cb,我们可以在需要时使用它们来访问它们的 IntVar。
消息-
你将 msg 传递给函数,然后将其用于 get_thug_args 中“argList”的值,并将其替换为结果。我认为将传递 ntbk_render 的关键字称为“argList”会更有意义,因为这就是它的用途,然后简单地让 msg 成为get_thug_args 的返回值。 (同样的思路也适用于关键字“ntbkLabel”,记录一下)
迭代-
我不确定使用索引引用 (x) 是否只是从 C 和 Java 等更严格的编程语言中养成的习惯,但迭代可能是我最喜欢的优势之一(主观的,我知道) Python 在这些类型的语言上的优势。除了使用 x,要从 msg 中取出您的选项,您可以简单地逐步检查 msg 中的每个单独选项。我们遇到无法克服的问题的唯一地方是当我们使用 self.rowTracker 时(在这个问题上,您的代码中没有更新;我们现在会解决这个问题,但和以前一样,我们稍后会处理)。我们可以做的修改是利用 Python 内置的 enumerate 函数;这将创建一个包含当前索引的元组,后跟迭代索引处的值。
此外,由于您将所有内容都保存在列表中,因此您必须继续返回列表的索引以获取参考。相反,只需创建对您正在创建的事物(数据类型/对象)的引用,然后将引用添加到列表中。
以下是迄今为止基于我上面提到的大部分内容对代码的调整:
import tkinter as tk ## tk now refers to the instance of the tkinter module that we imported
def ntbk_render(self, parent, word, argList):
cbuttons=list() ## The use of “list()” here is purely personal preference; feel free to
## continue with brackets
msg = get_thug_args(word = word, argList=argList) ## returns a 2d array [ [{some value},
## checkbutton text,…], …]
for x,option in enumerate(msg):
## Each iteration automatically does x=current index, option=msg[current_index]
variable = tk.IntVar()
## off and on values for Checkbuttons are 0 and 1 respectively by default, so it’s
## redundant at the moment to assign them
chbutton=tk.Checkbutton(parent, variable=variable, text=option[1])
chbutton.variable = variable ## rather than carrying the variable references around,
## I’m just going to tack them onto the checkbutton they
## belong to
chbutton.grid(row = self.rowTracker + x, column=0, sticky=’w’)
chbutton.deselect()
cbuttons.append(chbutton)
self.rowTracker += len(msg) ## Updating the rowTracker
return cbuttons
def get_options(self, cbuttons):
## I’m going to keep this new function fairly simple for clarity’s sake
values=[]
for chbutton in cbuttons:
value=chbutton.variable.get() ## It is for this purpose that we made
## chbutton.variable=variable above
values.append(value)
return values
是的,这部分内容有点冗长,但代码中的任何错误都将更容易发现,因为一切都是明确的。
进一步细化
我要谈的最后一件事 - 不涉及太多细节,因为我无法确定其中有多少是新信息 - 是我之前对你如何传递参考资料的抱怨。现在,我们已经通过将重要部分减少到仅检查按钮列表 (cbuttons) 消除了很多复杂性,但是仍然传递了一些我们可能不需要的引用。与其深入解释,不如考虑每个笔记本选项卡都是它们自己的对象,因此可以做自己的工作:因此,不要让您的程序向每个选项卡添加选项并将所有值传递给选项,而是可以将该工作委托给选项卡本身,然后告诉它如何或添加哪些选项,并在需要时询问它的选项和值(而不是在主程序中完成所有这些工作)。