TAGS :Viewed: 12 - Published at: a few seconds ago

[ python module variable not mutable as a function_default ]

I need to unit-test a python module by changing a default in the head of the application module called process.py. The declared default is a fixed int. I could change it to use something else from os.environ, but I was hoping to simply assign the global, but clearly I am missing some comprehension around 'def'.

process.default_timeout_secs = 2

# --name: process

default_timeout_secs = 120

def call_process(cmd, timeout=default_timeout_secs):
    print 'run:', cmd, 'timeout:', timeout
    ...



# --name: test_process
from nose.tools import *
import process

@ raises(TimeoutExpired)
def test_process_run_modified_default():
    process.default_timeout_secs = 5
    run(sleep 10)

I understand from other posts, that the process.call_process.func_defaults value for default_timeout_secs is not the one in the top of the module when the module is imported. How do I change the default used in the function?

process.default_timeout_secs = 5 process.call_process.func_globals['default_timeout'] 5 process.call_process.func_defaults (120)

Out[21]: 5
>>> process.call_process(cmd)

Out[22]: call_process: cmd= sleep 2 timeout= 120       <----- 120? 
         Executing cmd sleep 2 verbose=True

The output should show exception TimoutExpired.

Answer 1


Function defaults are evaluated at definition time, not call time (see "Least Astonishment" in Python: The Mutable Default Argument).

The only way to access and modify the function defaults are through its __defaults__ property (func_defaults in older versions):

>>> def f(a=5):
...     return a
>>> f.__defaults__
(5,)
>>> f.__defaults__ = (99,)
>>> f()
99

Note that __defaults__ is a tuple so you cannot assign its members individually, but you can assign it as a whole.

Answer 2


d = list(process.call_process.func_defaults)

In [10]: d
Out[10]: [True, True, 120]

In [11]: d[-1] = 5

In [12]: d
Out[12]: [True, True, 5]

In [13]: t = tuple(d)

In [14]: t
Out[14]: (True, True, 5)

In [15]: process.call_process.func_defaults = t
process.call_process('sleep 8')

call_process(cmd, verbose, shell, timeout, **kwargs)
     94         except:
     95             print(str(c.getName()) + 'running cmd "'+ cmd+ '" could not be terminated')
---> 96         raise TimeoutExpired('calling '+cmd)
     97     else:
     98         return c.response

TimeoutExpired: calling sleep 8

In [17]: call_process result: 0 :--:-None-:

Answer 3


Concerning your original question about changing a default for testing purpose, you might want to use a dictionary which is a mutable object. See my answer there for details.