Welcome, guest | Sign In | My Account | Store | Cart

Python allows the declaration of nested functions. These are typically hard to unit test because using just the normal ways of calling they cannot be called from outside their surrounding function. So they cannot be considered a clearly separated unit and thus cannot be unit tested.

This is a drawback of using them, so many developers (especially the ones deep into test driven development who strive to have a high unit test coverage) tend to avoid them in favor for standalone functions which can be called from the unit tests without any hassle.

But not all solutions with nested functions can be written as elegant with standalone functions. Nested functions are powerful insofar that they can access the local variables of the surrounding function without any need to pass them into the nested function, thus the code can in many cases stay neat and tidy while using a standalone function instead might raise the need to pass the complete context in form of a bunch of parameters. Also, using nested functions makes their local usage clear to any reader and keeps the name space tight.

But at least in the standard CPython (i. e. not necessarily in Jython, etc.) the implementation of functions (and methods) allows to find the nested function's code, wrap it properly to give it its needed context and then call it from the outside. I wrote a small module which helps doing exactly this.

Python, 16 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import types

def freeVar(val):
  def nested():
    return val
  return nested.__closure__[0]

codeAttribute = '__code__' if sys.version_info[0] == 3 else 'func_code'

def nested(outer, innerName, **freeVars):
  if isinstance(outer, (types.FunctionType, types.MethodType)):
    outer = outer.__getattribute__(codeAttribute)
  for const in outer.co_consts:
    if isinstance(const, types.CodeType) and const.co_name == innerName:
      return types.FunctionType(const, globals(), None, None, tuple(
          freeVar(freeVars[name]) for name in const.co_freevars))

This module above (named nested.py) can be used to test code like this:

#!/usr/bin/env python
# version of Python can be 2 or 3, works in both cases

def f(v1):
  v2 = 1
  def g(v3=2):
    return v1 + v2 + v3 + 4
  def h():
    return 16
  return g() + h() + 32

class C(object):
  def foo(self):
    def k(x):
      return [ self, x ]
    return k(3)

def m():
  vm = 1
  def n(an=2):
    vn = 4
    def o(ao=8):
      vo = 16
      return vm + an + vn + ao + vo
    return o()
  return n()

The testing code then could look like this:

import unittest
from nested import nested

class TestNested(unittest.TestCase):
  def runTest(self):
    nestedG = nested(f, 'g', v1=8, v2=1)
    self.assertEqual(nestedG(2), 15)
    nestedH = nested(f, 'h')
    self.assertEqual(nestedH(), 16)
    nestedK = nested(C.foo, 'k', self='mock')
    self.assertEqual(nestedK(5), [ 'mock', 5 ])
    nestedN = nested(m, 'n', vm=1)
    nestedO = nested(nestedN, 'o', vm=1, an=2, vn=4)
    self.assertEqual(nestedO(8), 31)

def main(argv):
  unittest.main()

if __name__ == '__main__':
  import sys
  sys.exit(main(sys.argv))

2 comments

Alfe (author) 7 years ago  # | flag

Thank you for pointing out this not-so-obvious case. I added an elaborate answer at the SO question you linked to. In short: Give a value for the identifier of the recursive function:

def f(x):
  def fac(n):
    return fac(n-1) * n if n > 1 else 1
  print "Faculty of", x, "is", fac(x)

nestedFac = nested(f, 'fac', fac=lambda n: nestedFac(n))