3

I have tried using several different similar solutions that I have found online, but none seem to quite do what I am aiming for.

I want to call an external script (helloworld.py) into my tkinter gui. I want this called script (helloworld.py) to execute all the functions that are contained in it upon a button press in the gui and print the resulting outputs into the gui, not the console. I have found some solutions which will print the output to the console, but I am unable to get it to display in the gui. Any solutions that I have found that print to the gui do not work when I try to get the output to come from a called external script.

I appreciate any help. I am definitely a novice, so I apologize for what is probably a basic question and the inability to connect the dots for myself on similar questions asked on here. Below is one of the versions of code that I am currently working with. Thank you in advance for your help!

import Tkinter
import sys
import subprocess
sys.path.append('/users/cmbp')

def callback():
    import os
    print subprocess.call('python /users/cmbp/p4e/helloworld.py', 
shell=True)
    lbl = Tkinter.Label(master)
    lbl.pack()

master = Tkinter.Tk()
master.geometry('200x90')
master.title('Input Test')

Btn1 = Tkinter.Button(master, text="Input", command=callback)
Btn1.pack()

master.mainloop()

EDIT

I also started having some success with trying to import the called script as a module. The problem with this is I can only get one function to print out from the called script even though there are multiple functions that I want to try and call (I just want the entire called script to print out all the results of its functions).

Here is an example of a script that I want to call helloworld.py:

def cooz():
    return ('hello worldz!')

def tooz():
    return ("here is another line")

def main():
    return cooz()
    return tooz()

And here is an example of the tkinter gui script that is trying to import helloworld.py:

import Tkinter as tk
import helloworld

def printSomething():
    y = helloworld.main()
    label = tk.Label(root, text= str(y))
    label.pack()


root = tk.Tk()
root.geometry('500x200')
root.title('Input Test')

button = tk.Button(root, text="Print Me", command=printSomething)
button.pack()

root.mainloop()

This results in only the first function printing ('hello worldz!'). Any thoughts on why it only will return one line and not the entire helloworld.py script?

Philalethes
  • 105
  • 1
  • 2
  • 14
  • Interesting. Can you search SO for `freopen`? That's a C function but I think it would be applicable for you as well. – Jongware Dec 30 '17 at 23:06
  • I don't think this is a simple question at all. You need a complete wrapper that detects print statements and converts them to GUI widgets, and that's still not guaranteed to work with every function. – Nae Dec 30 '17 at 23:14
  • 1
    @Nae: capturing *all* output that prints to `stdout` is a standard operation in consoles, IDEs, and other software. So no need to detect singular "print statements". I wouldn't know where to start for Python, hence my suggestion. – Jongware Dec 30 '17 at 23:26
  • 2
    There's a script named `errorwindow.py` in my [answer](https://stackoverflow.com/a/18091356/355230) to another question which shows how to redirect output sent to `stdout` and `stderr` to a `tkinter` window. What it does is very similar to what you want to do (and it doesn't require—and isn't specific to—using the `easygui` module). – martineau Dec 30 '17 at 23:33
  • `subprocess` has other functions to get output - ie. [check_output()](https://docs.python.org/3/library/subprocess.html#subprocess.check_output) – furas Dec 31 '17 at 01:04
  • or maybe you should `import helloworld` and run `helloworld.some_function()`, and then you have to redirect `sys.stdout` before you run function. You can use [contextlib.redirect_stdout](https://docs.python.org/3/library/contextlib.html#contextlib.redirect_stdout). – furas Dec 31 '17 at 01:11
  • Be careful when adding new information to your question – do not stray away too far from the original! (Which IMO has been answered, and so you may want to *accept* one of the answers.) Your latest edit should have been a new question, except that it would be shot down immediately ("you can only use one `return` at a time"). – Jongware Dec 31 '17 at 22:09

2 Answers2

2

You can use subprocess.check_output() to get output and assign to Label

You can also import script and execute function from script.

import test
test.function()

But first you will have to redirect sys.stdout using class with write() and then it will catch all printed text.

You can redirect sys.stdout to variable (see StdoutRedirector) and then you can edit it (ie. strip \n at the end) or you can redirect directly to Label (see StdoutRedirectorLabel)

import Tkinter as tk

# -----

import subprocess

def callback1():
    cmd = 'python test.py'

    # it will execute script which runs only `function1`
    output = subprocess.check_output(cmd, shell=True)

    lbl['text'] = output.strip()

# -----

class StdoutRedirector(object):

    def __init__(self):
        # clear before get all values
        self.result = ''

    def write(self, text):
        # have to use += because one `print()` executes `sys.stdout` many times
        self.result += text

def callback2():

    import test

    # keep original `sys.stdout
    old_stdout = sys.stdout

    # redirect to class which has `self.result`
    sys.stdout = StdoutRedirector()

    # it will execute only `function2`
    test.function2()

    # assign result to label (after removing ending "\n")
    lbl['text'] = sys.stdout.result.strip()

    # set back original `sys.stdout
    sys.stdout = old_stdout

# -----

import sys

class StdoutRedirectorLabel(object):

    def __init__(self, widget):
        self.widget = widget
        # clear at start because it will use +=
        self.widget['text'] = ''

    def write(self, text):
        # have to use += because one `print()` executes `sys.stdout` many times
        self.widget['text'] += text

def callback3():

    import test

    # keep original `sys.stdout
    old_stdout = sys.stdout

    # redirect to class which will add text to `lbl`
    sys.stdout = StdoutRedirectorLabel(lbl)

    # it will execute only `function3` and assign result to Label (with ending "\n")
    test.function3()

    # set back original `sys.stdout
    sys.stdout = old_stdout

# --- main ---

master = tk.Tk()
master.geometry('200x200')

lbl = tk.Label(master, text='')
lbl.pack()

btn1 = tk.Button(master, text="subprocess", command=callback1)
btn1.pack()

btn2 = tk.Button(master, text="StdoutRedirector", command=callback2)
btn2.pack()

btn3 = tk.Button(master, text="StdoutRedirectorLabel", command=callback3)
btn3.pack()

master.mainloop()

test.py

def function1():
    print('function 1')

def function2():
    print('function 2')

def function3():
    print('function 3')

if __name__ == '__main__':
    function1() 
furas
  • 134,197
  • 12
  • 106
  • 148
1

In a method when a line with return ... is run, nothing else will be seen that comes after that line, as in your 2nd line of return ... is effectively useless as return cooz() is run unconditionally. You could simply replace your main with:

def main():
    return cooz(), tooz()

and accordingly your printSomething:

x, y = helloworld.main()

Returning all methods/functions from a script without explicitly passing method names:

Well, I stand corrected, based on this answer you can do it fairly simply. For calling all methods or functions this answer helped a lot.

Let's say there's a script named hello_world.py:

def hello_world():
    print("Hello World!")
    print("this is the 2nd line of this method")

def multiplication(*args):
    mult = 1
    for arg in args:
        mult *= arg

    return mult

def some_other_method():
    print("some other method")
    print(multiplication(2, 3, 5, 7))

is in the same directory as GUI script below:

import tkinter as tk    # required for the GUI
import subprocess       # required for redirecting stdout to GUI
import sys, inspect     # required for all methods and functions redirection
import hello_world      # the script file that is redirected

def redirect(module, method):
    '''Redirects stdout from the method or function in module as a string.'''
    proc = subprocess.Popen(["python", "-c",
        "import " + module.__name__ + ";" + module.__name__ + "." + method + "()"],
                                                                stdout=subprocess.PIPE)
    out = proc.communicate()[0]
    return out.decode('unicode_escape')

def redirect_module(module):
    '''Retruns all stdout from all methods or functions in module as a string.'''
    # to filter out non-method, and non-function attributes
    all_mtds_or_funcs = inspect.getmembers(sys.modules[module.__name__], 
                                                inspect.isfunction or inspect.ismethod)
    red_str_buffer = ""
    for method in all_mtds_or_funcs:
        red_str_buffer += redirect(module, method[0]) + "\n---\n"

    return red_str_buffer

def put_in_txt(module):
    '''Puts the redirected string in a text.'''
    txt.insert('1.0', redirect_module(module))

root = tk.Tk()
txt = tk.Text(root)
btn = tk.Button(root, text="Redirect")
btn['command'] = lambda module=hello_world : put_in_txt(module)

txt.pack()
btn.pack()

root.mainloop()

which returns console output of all methods and functions in hello_world.py as a string. Based on this suggestion it then puts that string in a Text field.

Community
  • 1
  • 1
Nae
  • 14,209
  • 7
  • 52
  • 79
  • Above codes are for python3, it may be slightly different in python 2. – Nae Dec 31 '17 at 01:09
  • 1
    Knock me over with a feather duster. Works like a charm: https://imgur.com/27GAoek (I added a scrolling text field, just for funsies). – Jongware Dec 31 '17 at 01:19
  • @usr2564301 Scrolling text suits better I think. – Nae Dec 31 '17 at 01:23
  • I can edit my changes into your answer if you want. (OTOH As I am a starter with Python, it's a bit of copypasta from elsewhere. So perhaps you already know how to do this :) – Jongware Dec 31 '17 at 01:24
  • @usr2564301 Improved the code allow more flexibility with redirection, which is now returned as a string. Which should make putting on the widget of choice easier. Thanks for the suggestion. – Nae Dec 31 '17 at 01:48
  • This seems to be working. As I am pretty new at this, I am trying to understand all of the components of it. I definitely need to play around with it to get it to display all the results from the various functions in the exact format that I would like. Thank you for your help! – Philalethes Dec 31 '17 at 03:20
  • I did also edit my post above to show a different direction I had been working on in the meantime by importing the other script as a module in the tkinter script. I almost go this method to work, but I am running into an issue with it only printing one of the functions even though there are multiple functions that I am trying to print results out for. – Philalethes Dec 31 '17 at 03:22
  • @Philalethes Added a solution to your particular issue there as well, see topmost part of this answer. – Nae Dec 31 '17 at 13:15