2

我正在重新设计(“对象”化)一个 Python 3.2 GUI 应用程序,它自动将 tkinter GUI 对象(按钮标签)适应用户的语言(在启动时确定并且永远不会改变)。作为第一步,我将大型的单一源文件分解为多个模块文件。我能够毫无问题地将一些函数移动到它们自己的模块文件中——但是当从它们自己的模块文件调用时,其他“被证明很好”的函数将不起作用。

尝试使用驻留在一个模块中并由不同(兄弟)模块设置的变量(“Database_to_use”,见下文)时发生“IndexError:字符串索引超出范围”错误。当修改变量的过程位于主线模块中时,不会发生该错误。我怀疑存在范围界定问题和/或名称冲突,但从我所阅读和所做的一切来看,似乎已经消除了这两种可能性。

我正在使用“import”、虚线引用,并将全局声明放在修改函数中。点分符号也被用于“重命名”实体,以一种将名称从其所在位置的实现细节中抽象出来的方式 - 并且还将模块名称更改的范围限制为模块内的一行。我检查了这种方法不会导致名称隐藏问题;在我重命名问题模块和问题函数(下面的“SelectSqlDatabase”模块)后发生了同样的问题。顺便说一句,我也很清楚全局变量的优缺点(最终会消失)。

在下面的摘录中,我对代码进行了严格的编辑(例如,仅显示一个按钮的代码)以删除对理解问题不重要的所有内容。

首先是“SetUpLanguageInUse.py”模块;这是有问题的“Database_to_use”(以及没有问题的“Main_Title”)变量的位置:

English  = 'English'
Francais = 'Francais'

Database_to_use = "dummy string forces this variable into the global namespace"
Main_Title      = "dummy string forces this variable into the global namespace"

def SetUpLanguageInUse( user_language ):

    import __main__     # needed to modify the global value 

    if ( user_language == English ):       
        __main__.Main_Title      = 'Monthly Summary of Reports'
        __main__.Database_to_use = ( '', 'Build', 'Staging', 'Production' )
    elif ( user_language == Francais ):
        __main__.Main_Title       = 'Resume Mensuel de Rapports' 
        __main__.Database_to_use  = ( '', 'Construire', 'Relais', 'Production' )

    .....

    return

关于上述模块的一些观察:

a) 问题与用户语言无关。b) 使用 [] 而不是 () 并不能解决问题。c) 在函数内部(和/或之前)插入“global Database_to_use”行没有帮助
d) 删除虚拟字符串分配没有帮助
e) 主线的第一行(见下文)调用此函数。当函数包含“全局”行时,变量没有更新。我明白为什么使用“ main ”。有效 - 但是当存在“全局”行时,为什么修改不起作用?

Next, the "SelectSqlDatabase.py" module, it holds the problem function:

from tkinter import *
from tkinter import ttk

import LanguageInUse

Database_to_use = LanguageInUse.Database_to_use

Database_List  = ( '', 'server1', 'server2', 'server3' )
SQL_Database   = "To be determined"

# ----- the problematic function

def SelectSqlDatabase( SelectDatabaseFrame ) : 

    global SQL_Database

    UserSelection = StringVar( value = "Empty" )

    Build_DB      = Radiobutton( SelectDatabaseFrame, 
                                 text        = Database_to_use[ 1 ],  # the problematic line
                                 variable    = UserSelection, 
                                 value       = Database_List[ 1 ] )

    SQL_Database = UserSelection.get()

    return

Finally the mainline. Notice that inthis file the "SelectSqlDatabase" function is a COMMENT and is IDENTICAL to that in "SelectSqlDatabase.py". When this code is run "AS IS" (i.e. using the function in "SelectSqlDatabase.py"), I get the following error:

File "...\SelectSqlDatabase.py", line 65, in SelectSqlDatabase text = Database_to_use[ 1 ],
IndexError: string index out of range

But when I UN-comment the function (thereby hiding the function in "SelectSqlDatabase.py"), the application runs correctly!

from tkinter  import *
from tkinter  import ttk

import LanguageInUse
import SelectSqlDatabase       

Database_to_use          = LanguageInUse.Database_to_use
Database_List            = SelectSqlDatabase.Database_List
English                  = LanguageInUse.English
SetUpLanguageInUse       = LanguageInUse.SetUpLanguageInUse
SQL_Database             = SelectSqlDatabase.SQL_Database

SelectSqlDatabase        = SelectSqlDatabase.SelectSqlDatabase     # gets overridden

# ----- the problematic function

'''
def SelectSqlDatabase( SelectDatabaseFrame ) : 

    global SQL_Database

    UserSelection = StringVar( value = "Empty" )

    Build_DB      = Radiobutton( SelectDatabaseFrame, 
                                 text        = Database_to_use[ 1 ],  # the problematic line
                                 variable    = UserSelection, 
                                 value       = Database_List[ 1 ] )

    SQL_Database = UserSelection

    return
'''
'''----------'''
def ChooseDataSourceFrame( mainframe ) :

    ChooseSourceFrame   = ttk.LabelFrame( mainframe, ...  )
    SelectDatabaseFrame = ttk.LabelFrame( ChooseSourceFrame )

    SelectSqlDatabase( SelectDatabaseFrame )

    return

'''***** MAINLINE ***** '''

SetUpLanguageInUse( English )      #TODO: make language a startup parameter

mainframe = ttk.Frame( ... )

ChooseDataSourceFrame( mainframe )

To be complete, at one time I was able to eliminate this error (by passing "Database_to_use" as a parameter) but other nasty things happened such as:
a) the GUI displaying the useless dummy titles (effectively ignoring the call to "SetUpLanguageInUse"), b) getting an error message about "Database_to_use" not having a get() function, or c) the value returned in "SQL_Database" being either it's default value ("To be determined") or the default value for User_Choice ("Empty") instead of the database chosen by the user.

To summarize, my objective is to set "SQL_Database" to the database server chosen by the user (i.e. extracted from "Database_List") - with tkinter displaying the (language-dependent) strings passed to it inside "Database_to_use".

I've spent a LOT of time experimenting & researching this problem - all to no avail. Everything I've read (including the Python 3.2.3 Tutorial) suggests that what I'm doing is correct, however I feel I'm overlooking something simple. Where am I going wrong?

4

1 回答 1

2

What you are missing here is that Python does not have variables that are pointers, they are references to objects. Hence, when you change a variable name, that variable name will point to the new object you created. All other variable names will continue to point to the old variable.

Hence, when you import Database_to_use into SelectSqlDatabase it will point to the string object "dummy string forces this variable into the global namespace". When your function later changes the Database_to_use name in your first file to point to another string, this will not modify what SelectSqlDatabase.Database_to_use references. It will continue to reference the original string.

So, what should you do? You should keep your run-time configurations in an object of some sort, and always look up the variables from that object, so that you do not keep your configuration variables in local variables that do not change.

So in this case, to solve the problem, remove the line

Database_to_use = LanguageInUse.Database_to_use

And just reference LanguageInUse.Database_to_use all the time instead, it would probably solve the problem.

It's still a bit ugly though. I would probably just have a configuraion = {} in your main __init__.py, and use that:

Setting:

from mainmodule import configuration

def somefuction():
    configuration['database'] = "mysql:blahblahblah"

Using:

from mainmodule import configuration
configuration['database'] = "mysql:blahblahblah"

def somefuction():
    databaseopener(cofiguration['database'])

Also: PEP8

于 2012-12-04T06:29:32.213 回答