Yasha Doe
Yasha Doe

Reputation: 33

Calling function from another file, which contains variable declared in main file

I'm creating simple notepad program in Tkinter. I decided to put functions in separate file. Is it possible if the functions are operating on variables declared in main file? This is snippet of the code: main.py

from tkinter import *
from otherfile import cut
root = Tk()
....
menu_edit.add_command(label='Cut', compound='left', command=cut)
...
main_text = Text(root, wrap ='word')
main_text.pack(expand='yes', fill = 'both')

now I have otherfile.py

def cut():
    main_text.event_generate('<<Cut>>')
    return 'break'

Once I run it I'll get: Exception in Tkinter callback

Traceback (most recent call last):
  File "C:...\tkinter\__init__.py", line 1699, in __call__
    return self.func(*args)
  File "C:\....otherfile.py", line 3, in cut
    main_text.event_generate('<<Cut>>')
NameError: name 'main_text' is not defined

So I guess otherfile.py does not understand main_text which is defined in main.py. Is there a way to bypass it and allow me to put all the functions in different py file?

Upvotes: 0

Views: 790

Answers (3)

Hai Vu
Hai Vu

Reputation: 40803

It is a bad practice and tons of headache to use global variables. May I suggest to modify cut() to take a parameter:

# otherfile.py
def cut(text_control):
    text_control.event_generate('<<Cut>>')
    return 'break'

Then in the main module, call it as such:

# main.py
menu_edit.add_command(label='Cut', compound='left', command=lambda: cut(main_text))

This way, you don't have to deal with troubles later. Besides, you can now use function cut() for other text boxes if you want.

Upvotes: 0

e.s.
e.s.

Reputation: 1371

cut is trying to use a global variable from another file. Even if you found a way to get around circular imports, it's a messy way to go about things. It's better to write functions that operate independent of global variables. For one thing, it makes them much easier to modify and test. When you need to deal with assigning command=function and function takes variables, functools.partial is your friend.

def cut(tk_text_obj):
    tk_text_obj.event_generate('<<Cut>>')
    return 'break'

and then in main file, first declare main_text and then use functools.partial to create a callable that takes no arguments.

from functools import partial
from tkinter import *
from otherfile import cut
root = Tk()
....
main_text = Text(root, wrap ='word')
cut_main_text = partial(cut, main_text)
menu_edit.add_command(label='Cut', compound='left', command=cut_main_text)
# or just combine the above two lines using command=partial(cut, main_text)
...
main_text.pack(expand='yes', fill = 'both')

Upvotes: 1

mtr
mtr

Reputation: 11

It is possible. You should import main in otherfile or modify otherfile.cut method to accept main_text as method argument. Second option depends on that if menu_edit.add_command allows passing arguments to command.

I think you have two problems.

  1. Circular imports which are a real pain.
  2. Everything which is declared on module level is called during module import.

I believe below example is moreover situation you have.

a.py:

import b
commands = []

def add_command(cmd):
    commands.append(cmd)

def run_commands():
    for cmd in commands:
        print cmd()

def local_cmd():
    return 'local cmd output'

if __name__ == '__main__':
    add_command(local_cmd)
    add_command(b.b_cmd)
    run_commands()

b.py:

import a

def b_cmd():
   l = a.local_cmd()
   return 'B %s B' % l

Above snippet works as expected when running with python a.py.

But when you skip if __name__ == '__main__': you will observe similar situation. Script fails because when you import a in b, add_command(b.b_cmd) in a is called, but b was not imported yet.

Upvotes: 0

Related Questions