1

I am writing a python script in python 3.x in which I need to redefine the print function. When I do it in my interpreter, it works fine. But when I create a function using the same code, it gives out error.

Here is my code:

list = ["print('Wow!')\n", "print('Great!')\n", "print('Epic!')\n"]
old_print = print

def print(s): 
   global catstr
   catstr += s

catstr = ""
for item in list: 
    s = item
    exec(s)
print = old_print

catstr
>> 'Wow!Great!Epic!'

As you can see I have got my desired result: 'Wow!Great!Epic!'

Now I make a function using the same code:

def execute(list):
    old_print = print
    def print(s):
        global catstr
        catstr += s
    catstr = ""
    for item in list: 
        s = item
        exec(s)
    print = old_print
    return catstr

Now when I run this function using the following code:

list = ["print('Wow!')\n", "print('Great!')\n", "print('Epic!')\n"]

execute(list)

I get the following error:

old_print = print 
UnboundLocalError: local variable 'print' referenced before assignment

Does anyone know why this is not working within a function?
Any suggestions on how to fix it will be highly appreciated.

martineau
  • 119,623
  • 25
  • 170
  • 301
sshussain270
  • 1,785
  • 4
  • 25
  • 49
  • It is the first line as you define print in the function and as a function, what are you using old_print etc.. at all? – Padraic Cunningham Sep 14 '16 at 00:08
  • 1
    What's the purpose of your question? Or is this just out of curiosity? – BPL Sep 14 '16 at 00:13
  • @BPL It's directly related as a follow-up question they asked yesterday [here](http://stackoverflow.com/questions/39460882/how-to-get-execution-of-python-print-statements-as-a-string) – idjaw Sep 14 '16 at 00:13

3 Answers3

2

The interpreter doesn't recognize print as the built-in function unless you specifically tell it so. Instead of declaring it global, just remove it (thanks to Padraic Cunningham): the local print will take on your desired definition, and the global is never affected.

You also have a forward-reference problem with catstr. The below code elicits the desired output.

catstr = ""
def execute(list):

    def print(s):
        global catstr
        catstr += s

    for item in list: 
        s = item
        exec(s)
    print = old_print
    return catstr

list = ["print('Wow!')\n", "print('Great!')\n", "print('Epic!')\n"]

print (execute(list))
Prune
  • 76,765
  • 14
  • 60
  • 81
2

All you need is nonlocal and to forget all the other variables you have created bar catstr:

def execute(lst):
    def print(s):
        nonlocal catstr
        catstr += s
    catstr = ""
    for item in lst:
        s = item
        exec(s)
    return catstr

That gives you:

In [1]: paste
def execute(lst):
    def print(s):
        nonlocal catstr
        catstr += s
    catstr = ""
    for item in lst:
        s = item
        exec(s)
    return catstr

## -- End pasted text --

In [2]: list = ["print('Wow!')\n", "print('Great!')\n", "print('Epic!')\n"]

In [3]: execute(lst)
Out[3]: 'Wow!Great!Epic!'

Anything that happens in the function is local to the function so you don't need to worry about resetting anything. If you did happen to want to set a reference to print you could use old_print = __builtins__.print.

If you want to have your function print without needing a print call use __builtins__.print to do the printing:

def execute(lst):
    catstr = ""
    def print(s):
        nonlocal catstr
        catstr += s
    for s in lst:
        exec(s)
    __builtins__.print(catstr)
Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
1

Your issue has been already addressed by Prune and Padraic Cunningham answers, here's another alternative way to achieve (i guess) what you want:

import io
from contextlib import redirect_stdout

g_list = ["print('Wow!')\n", "print('Great!')\n", "print('Epic!')\n"]


def execute(lst):
    with io.StringIO() as buf, redirect_stdout(buf):
        [exec(item) for item in lst]
        return buf.getvalue()


def execute_modified(lst):
    result = []
    for item in lst:
        with io.StringIO() as buf, redirect_stdout(buf):
            exec(item)
            result.append(buf.getvalue()[:-1])

    return "".join(result)


print(execute(g_list))
print('-' * 80)
print(execute_modified(g_list))

Output:

Wow!
Great!
Epic!

--------------------------------------------------------------------------------
Wow!Great!Epic!
BPL
  • 9,632
  • 9
  • 59
  • 117
  • That puts all the data on different lines, the *desired result* is `'Wow!Great!Epic!'` – Padraic Cunningham Sep 14 '16 at 00:38
  • @PadraicCunningham You got me there :P . I've edited my answer... Although I'm not very happy with the solution I've come up with, suggestions welcomed – BPL Sep 14 '16 at 00:59