sshussain270
sshussain270

Reputation: 1865

Redefining print function not working within a function

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.

Upvotes: 1

Views: 1019

Answers (3)

BPL
BPL

Reputation: 9863

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!

Upvotes: 1

Padraic Cunningham
Padraic Cunningham

Reputation: 180481

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)

Upvotes: 2

Prune
Prune

Reputation: 77850

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))

Upvotes: 2

Related Questions