【问题标题】:How to update a variable in tkinter app without using global?如何在不使用全局的情况下更新 tkinter 应用程序中的变量?
【发布时间】:2021-09-21 16:09:00
【问题描述】:

我正在尝试制作一个程序,每次用户按下按钮时,都会在日期变量中添加一个月。

这是我的代码:

import datetime
from dateutil.relativedelta import relativedelta
from tkinter import *

gameDate = datetime.datetime(1985, 9, 1)

def nextMonth(gameDate, worldDateLabel):
    gameDate = gameDate + relativedelta(months=1)
    worldDateLabel.config(text=gameDate.strftime("%B %Y"))
    return gameDate

window = Tk()

worldDateLabel = Label(window, text=gameDate.strftime("%B %Y"))
worldDateLabel.grid(row=0, column=0)

next_btn = Button(window, text="Next Month", command=lambda:nextMonth(gameDate, worldDateLabel))
next_btn.grid(row=1, column=0)

window.mainloop()

日期在我第一次点击按钮时更新,但之后会停留到 10 月,不会移动到下个月。

我认为这是因为程序只更新了函数内部的局部变量,而不是主要的gameDate 一个。我试图通过最后的 return 来解决这个问题,但这并没有帮助。

我不确定我做错了什么……

P.S.:我尽量不使用global

【问题讨论】:

  • 这是声明gameDate的本地副本的参数,它正在更新而不是全局。您可以包装 gameDate (例如,在一个类、一个元组、单个项目列表中)并将其传递给您的 nextMonth 函数。当你在函数中更新它时,实际的变量将会改变。
  • 没错,如果你想让gameDate作为一个全局变量,你需要将它声明为一个全局变量。您可以将该类创建为一个类并调用一个可以修改该对象的方法,然后调用任何可能需要的标签更新函数来使其工作。

标签: python python-3.x tkinter


【解决方案1】:

在这种情况下,您需要全局。避免全局变量的一般规则有很多例外。全局变量的问题在于它倾向于以一种不明显的方式耦合不同的函数,这可能会更好地相互独立。想想更简单的编程和更少的错误。

脚本越简单,这个问题就越少。在您的情况下,您有一个带有单个主事件循环的程序,有一些额外的全局变量是可以的。但是随着代码的增长,您会发现 nextMonth 函数有不同的用途,这时就会出现问题。

解决问题的一种方法是使用课程。类实例方法会记住它的实例,因此您可以定义一个类来执行有用的游戏日期内容。类实例本身仍然是全局的,但 Tk 窗口也是如此。但是您现在已经封装了该功能,并且可以根据需要在其他地方使用该类。

import datetime
from dateutil.relativedelta import relativedelta
from tkinter import *

class GameDate:

    def __init__(self):
        self.gameDate = datetime.datetime(1985, 9, 1)

    def nextMonth(self, worldDateLabel):
        self.gameDate = self.gameDate + relativedelta(months=1)
        worldDateLabel.config(text=self.gameDate.strftime("%B %Y"))

window = Tk()
date = GameDate()

worldDateLabel = Label(window, text=gameDate.strftime("%B %Y"))
worldDateLabel.grid(row=0, column=0)

next_btn = Button(window, text="Next Month", command=lambda:date.nextMonth(worldDateLabel))
next_btn.grid(row=1, column=0)

window.mainloop()

【讨论】:

  • 你的回答是矛​​盾的,或者充其量是混淆 IMO。如果使用一个类,不需要需要一个全局变量——尽管无可否认,类的实例实际上是一个。
  • @martineau - 你仍然需要一些地方来存放类实例。我想你可以把它放在Tk() 窗口实例上......但这对我来说似乎并不明智。它可以去的地方不多。
  • 甚至可以避免将类实例设为全局或至少将其分配给命名变量 — 有关详细信息,请参阅我刚刚发布的 answer。当然,由于 Python 中的所有内容都是一个对象,因此 class 对象本身在技术上是一个全局对象(在我的回答中分配了名称 TkinterApp)。
【解决方案2】:

在这种情况下,您绝对不需要需要使用全局变量。在大多数程序中避免或至少最小化使用它们(which are known to be bad,甚至是harmful)的方法是简单地遵循面向对象的编程范式或方法,也称为OOP

以下是如何根据将其原理应用于您问题中的代码来制作 tkinter 应用程序。我还努力避免将许多常量值硬编码到其中以使其更灵活。

我注意到您在代码中使用了mixedCase 变量和函数名称,我大部分都保留了这些名称,但强烈建议您开始遵循PEP 8 - Style Guide for Python Code 的建议,尤其是那些与naming styles 有关的,因为我认为它使代码更易于阅读。

import datetime
from dateutil.relativedelta import relativedelta
import tkinter as tk


class TkinterApp:

    def __init__(self, year, month, day, time_delta, date_format):
        self.start_date = datetime.datetime(year, month, day)
        self.time_delta = time_delta
        self.date_format = date_format
        self.window = tk.Tk()
        self.window.grid_columnconfigure((0), weight=1)  # Center column in window.
        self.createWidgets()
        self.window.mainloop()

    def createWidgets(self):
        self.gameDate = self.start_date
        self.textVar = tk.StringVar(value=self.gameDate.strftime(self.date_format))
        worldDateLabel = tk.Label( textvariable=self.textVar)
        worldDateLabel.grid(row=0, column=0)
        next_btn = tk.Button(self.window, text='Next Month', command=self.nextMonth)
        next_btn.grid(row=1, column=0)

    def nextMonth(self):
        self.gameDate += self.time_delta
        self.textVar.set(self.gameDate.strftime(self.date_format))


if __name__ == '__main__':
    TkinterApp(1985, 9, 1, relativedelta(months=1), '%B %Y')

【讨论】:

    猜你喜欢
    • 2020-08-02
    • 2019-07-05
    • 1970-01-01
    • 1970-01-01
    • 2022-11-13
    • 2013-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多