0

After stumbling upon this question I've been playing around with exec() to better understand how it works. I'm trying to get this abomination of a script to increment a global variable, read itself, and call exec with the new global value until the recursion limit is met. The best I've been able to come up with is having one file declare the global and then call the next file (an exact duplicate minus the variable declaration) which will then recursively call itself. Here's the code for the first one:

# recurse.py

def func():
    global x
    x += 1
    with open('recurse2.py', 'r') as f:    
        try:
            exec(f.read(), {'x': x})
        except RecursionError:
            print('maximum recursion depth reached at', x)

x = 0
func()

And here's the file it executes, that will execute itself:

# recurse2.py

def func():
    global x
    x += 1
    with open('recurse2.py', 'r') as f:    
        try:
            exec(f.read(), {'x': x})
        except RecursionError:
            print('maximum recursion depth reached at', x)

func()

Is it possible to achieve the same effect with only one file?

Alec
  • 1,399
  • 4
  • 15
  • 27

2 Answers2

2

You could do this:

# recurse.py

def func():
    global x
    x += 1
    with open('recurse.py', 'r') as f:    
        try:
            exec(f.read(), {'x': x})
        except RuntimeError:
            print('maximum recursion depth reached at', x)

try:
    x
except NameError:
    x = 0
func()

The problem with your initial example isn't really specific to exec. It's just that the program itself sets x to zero before calling func. So passing in a starting value of x has no effect: the code you are exec-ing sets a new value of x anyway. In this new version, a try/except block tests whether the name already exists before initializing it to zero.

(I used RuntimeError here because I don't have Python 3.5, where RecursionError was introduced, but it should work the same with RecursionError.)

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • On my system, it doesn't fail due to recursion depth, it fails with: OSError: [Errno 24] Too many open files: 'recurse.py' (or IOError in Python 2) -- I realize this is different for every system but the IOError is preventable. – cdlane Jul 03 '16 at 04:32
2

A variation on @BrenBarn's excellent solution that fails with a RuntimeError (@ ~ 500 recursions) instead of an IOError (@ ~ 256 recursions) due to too many files being open:

# recurse.py

def func():
    global x
    x += 1
    with open('recurse.py') as source:
        string = source.read()
    try:
        exec(string, {'x': x})
    except RuntimeError:
        print('maximum recursion depth reached at', x)

try:
    x
except NameError:
    x = 0

func()

However, you could also expand the recursion stack (sys.setrecursionlimit()) and/or open file limit (ulimit -n) as needed.

cdlane
  • 40,441
  • 5
  • 32
  • 81
  • Ah, I hadn't noticed the potential IOError issue as I'd bumped up my open file limit in the past. Good point! – Alec Jul 03 '16 at 04:36