Python overwriting variables in nested functions
In Python 3.x, you can use the nonlocal
keyword:
def outer():
string = ""
def inner():
nonlocal string
string = "String was changed by a nested function!"
inner()
return string
In Python 2.x, you could use a list with a single element and overwrite that single element:
def outer():
string = [""]
def inner():
string[0] = "String was changed by a nested function!"
inner()
return string[0]
How do I change nesting function's variable in the nested function
In Python 3.x, you can use the nonlocal
declaration (in nested
) to tell Python you mean to assign to the count
variable in nesting
.
In Python 2.x, you simply can't assign to count
in nesting
from nested
. However, you can work around it by not assigning to the variable itself, but using a mutable container:
def nesting():
count = [0]
def nested():
count[0] += 1
for i in range(10):
nested()
print count[0]
Although for non-trivial cases, the usual Python approach is to wrap the data and functionality in a class, rather than using closures.
Does an equivalent of override exist for nested functions?
Here's one way of doing it, creating a new foo that "does the right thing" by hacking the function internals. ( As mentioned by @DSM ). Unfortunately we cant just jump into the foo
function and mess with its internals, as they're mostly marked read only, so what we have to do is modify a copy we construct by hand.
# Here's the original function
def foo():
def bar():
print(" In bar orig")
def baz():
print(" Calling bar from baz")
bar()
print("Foo calling bar:")
bar()
print("Foo calling baz:")
baz()
# Here's using it
foo()
# Now lets override the bar function
import types
# This is our replacement function
def my_bar():
print(" Woo hoo I'm the bar override")
# This creates a new code object used by our new foo function
# based on the old foo functions code object.
foocode = types.CodeType(
foo.func_code.co_argcount,
foo.func_code.co_nlocals,
foo.func_code.co_stacksize,
foo.func_code.co_flags,
foo.func_code.co_code,
# This tuple is a new version of foo.func_code.co_consts
# NOTE: Don't get this wrong or you will crash python.
(
foo.func_code.co_consts[0],
my_bar.func_code,
foo.func_code.co_consts[2],
foo.func_code.co_consts[3],
foo.func_code.co_consts[4]
),
foo.func_code.co_names,
foo.func_code.co_varnames,
foo.func_code.co_filename,
foo.func_code.co_name,
foo.func_code.co_firstlineno,
foo.func_code.co_lnotab,
foo.func_code.co_freevars,
foo.func_code.co_cellvars )
# This is the new function we're replacing foo with
# using our new code.
foo = types.FunctionType( foocode , {})
# Now use it
foo()
I'm pretty sure its not going to catch all cases. But it works for the example (for me on an old python 2.5.1 )
Ugly bits that could do with some tidy up are:
- The huge argument list being passed to CodeType
- The ugly tuple constructed from
co_consts
overriding only one member. All the info is in co_consts to determine which to replace - so a smarter function could do this. I dug into the internals by hand usingprint( foo.func_code.co_consts )
.
You can find some information about the CodeType
and FunctionType
by using the interpreter
command help( types.CodeType )
.
UPDATE:
I thought this was too ugly so I built a helper function to make it prettier. With the helper you can write:
# Use our function to get a new version of foo with "bar" replaced by mybar
foo = monkey_patch_fn( foo, "bar", my_bar )
# Check it works
foo()
Here's the implementation of monkey_patch_fn
:
# Returns a copy of original_fn with its internal function
# called name replaced with new_fn.
def monkey_patch_fn( original_fn, name, new_fn ):
#Little helper function to pick out the correct constant
def fix_consts(x):
if x==None: return None
try:
if x.co_name == name:
return new_fn.func_code
except AttributeError, e:
pass
return x
original_code = original_fn.func_code
new_consts = tuple( map( fix_consts, original_code.co_consts ) )
code_type_args = [
"co_argcount", "co_nlocals", "co_stacksize", "co_flags", "co_code",
"co_consts", "co_names", "co_varnames", "co_filename", "co_name",
"co_firstlineno", "co_lnotab", "co_freevars", "co_cellvars" ]
new_code = types.CodeType(
*[ ( getattr(original_code,x) if x!="co_consts" else new_consts )
for x in code_type_args ] )
return types.FunctionType( new_code, {} )
Overriding an inner function of a method in python
Either factoring _do_atomic_job()
into a proper method, or maybe factoring it
into its own class seem like the best approach to take. Overriding an inner
function can't work, because you won't have access to the local variable of the
containing method.
You say that _do_atomic_job()
takes a lot of parameters returns lots of values. Maybe you group some of these parameters into reasonable objects:
_do_atomic_job(start_x, start_y, end_x, end_y) # Separate coordinates
_do_atomic_job(start, end) # Better: start/end points
_do_atomic_job(rect) # Even better: rectangle
If you can't do that, and _do_atomic_job()
is reasonably self-contained,
you could create helper classes AtomicJobParams
and AtomicJobResult
.
An example using namedtuples
instead of classes:
AtomicJobParams = namedtuple('AtomicJobParams', ['a', 'b', 'c', 'd'])
jobparams = AtomicJobParams(a, b, c, d)
_do_atomic_job(jobparams) # Returns AtomicJobResult
Finally, if the atomic job is self-contained, you can even factor it into its
own class AtomicJob
.
class AtomicJob:
def __init__(self, a, b, c, d):
self.a = a
self.b = b
self.c = c
self.d = d
self._do_atomic_job()
def _do_atomic_job(self):
...
self.result_1 = 42
self.result_2 = 23
self.result_3 = 443
Overall, this seems more like a code factorization problem. Aim for rather lean
classes that delegate work to helpers where appropriate. Follow the single responsibility principle. If values belong together, bundle them up in a value class.
As David Miller (a prominent Linux kernel developer) recently said:
If you write interfaces with more than 4 or 5 function arguments, it's
possible that you and I cannot be friends.
Related Topics
Stopping a Thread After a Certain Amount of Time
Target Wsgi Script Cannot Be Loaded as Python Module
Django Rest Framework Serializing Many to Many Field
Why Is Using Thread Locals in Django Bad
How to Convert a Python Datetime.Datetime to Excel Serial Date Number
Opencv - Apply Mask to a Color Image
How to Initialize Weights in Pytorch
Execute a Function After Flask Returns Response
Most Pythonic Way to Interleave Two Strings
No Module Named 'Pandas._Libs.Tslibs.Timedeltas' in Pyinstaller
How to Apply Itertools.Product to Elements of a List of Lists
Python:2D Contour Plot from 3 Lists:X, Y and Rho
How to Force a List to a Fixed Size
Type Object 'Datetime.Datetime' Has No Attribute 'Datetime'
Django Signals VS. Overriding Save Method
Python 2 CSV Writer Produces Wrong Line Terminator on Windows