Search code examples
pythonlambdapython-2.xmultiprocessing-manager

python manager register issue or just my faulty code


I use a python manager to share data between processes. I wanted to break up my code into smaller pieces and created a function 'soFactory' that would take a dictionary of name/object pairs and register the object with the manager under the given name. In the simple example below I create two lists and register them with the manager. If I use soFactory the manager returns only one of the lists (last one registered) regardless which name I reference. If I unravel the logic of soFactory I get access to the correct object by referencing their registered names. I must be missing something obvious but not seeing it.

# ------------ prototype for SO question -------
from sys import stderr

def soFactory(dictofSo, manager):
    """shared object factory"""
    for n,t in dictofSo.items():
        print >>stderr, 'created item',n,t
        manager.register(n, callable=lambda: t)

def soRoutine(n, t, manager):
    manager.register(n, callable=lambda: t)

def test_soFactory(useFactory=True):
    """tests the soFactory function"""
    from multiprocessing import managers
    m = managers.BaseManager(address='/var/tmp/tqps-test', authkey='abc123')
    mySOlist = {'L1': [1],'L2':[2]}

    if useFactory:
        soFactory(mySOlist, m)
    else:
        for n, t in mySOlist.items():
            soRoutine(n, t, m)

    m.start()

    m.L1().append('only in L1!')
    print >>stderr, m.L1(), m.L2()

>>> test_soFactory()
created item L2 [2]
created item L1 [1]
[1, 'only in L1!'] [1, 'only in L1!']

>>> test_soFactory(useFactory=False)
[1, 'only in L1!'] [2]
>>> 

Solution

  • It is a common python variable scope pitfall, this happens due to the closure which lambda created remember the name of variables, but not the object (or the "pointer"). When the lambda is called, the value of t is looked up in the surrounding scope, at that time, the loop has ended, so t assigned its final value of L1, of course, this depends on the order of return value of dictofSo.items().

    You could confirm this by turn on debug log of multiprocessing:

    import logging
    import multiprocessing
    multiprocessing.util.log_to_stderr(logging.DEBUG)
    

    L1 and L2 have same id:

    [DEBUG/MainProcess] requesting creation of a shared 'L1' object
    [DEBUG/BaseManager-1] 'L1' callable returned object with id '104aef878'
    [DEBUG/MainProcess] INCREF '104aef878'
    ...
    [DEBUG/MainProcess] requesting creation of a shared 'L2' object
    [DEBUG/BaseManager-1] 'L2' callable returned object with id '104aef878'
    

    When you create the lambda in the function soRoutine(), because a function will create a new local scope, this time t could point to the value you expected. See also this post.