Missing local variable in a function factory in Python

136 Views Asked by At

I am using Python 3.5. I would like to create a factory where local variables of the outer function can be overwritten by user kwargs.

When doing so, I found out that some variable are not defined in the inner scope without apparent reasons.

Here is a MWE of the function factory:

def TicksFormatterFactory( \
    title=None, target='xaxis', limits=100 \
   ,major_locator=None, major_format=None, major_gridstyle=None, major_rotation=90, major_fontsize=7 \
   ,minor_locator=None, minor_format=None, minor_gridstyle=None, minor_rotation=None, minor_fontsize=None \
   ,title_kw=dict() \
):
    pprint.pprint(locals())   # (1)
    varKeys = locals().keys() # (2)
    def inner(axe, **kwargs):

        pprint.pprint(locals())   # (5)

        # Allow User to overwrite settings using kwargs:
        settings = dict()
        for k in varKeys:
            settings[k] = kwargs.get(k, locals().get(k)) # (3)

        pprint.pprint(settings) # (4)

    return inner

A simple call to the factory and its inner function:

test = TicksFormatterFactory(minor_fontsize=4)
fig, axe = plt.subplots()
test(axe, title='Hello world')

Leads to:

# (1)
{'limits': 100,
 'major_fontsize': 7,
 'major_format': None,
 'major_gridstyle': None,
 'major_locator': None,
 'major_rotation': 90,
 'minor_fontsize': 4,
 'minor_format': None,
 'minor_gridstyle': None,
 'minor_locator': None,
 'minor_rotation': None,
 'target': 'xaxis',
 'title': None,
 'title_kw': {}}

# (5)
{'axe': <matplotlib.axes._subplots.AxesSubplot object at 0x0000027793B02BE0>,
 'kwargs': {'title': 'Hello world'},
 'major_fontsize': 7,
 'major_format': None,
 'major_gridstyle': None,
 'major_locator': None,
 'major_rotation': 90,
 'minor_fontsize': 4,
 'minor_format': None,
 'minor_gridstyle': None,
 'minor_locator': None,
 'minor_rotation': None,
 'target': 'xaxis',
 'varKeys': dict_keys(['major_gridstyle', 'major_format', 'minor_format', 'target', 'minor_gridstyle', 'major_fontsize', 'minor_rotation', 'minor_locator', 'major_rotation', 'major_locator', 'title_kw', 'title', 'limits', 'minor_fontsize'])}

# (4)
{'limits': None,
 'major_fontsize': 7,
 'major_format': None,
 'major_gridstyle': None,
 'major_locator': None,
 'major_rotation': 90,
 'minor_fontsize': 4,
 'minor_format': None,
 'minor_gridstyle': None,
 'minor_locator': None,
 'minor_rotation': None,
 'target': 'xaxis',
 'title': 'Hello world',
 'title_kw': None}

What I have seen deeper:

Variable pointed by 'limits' key is not defined in inner scope:

locals()[k] # (3')

Fails for 'limits' key (in fact the third factory parameter, whatever it is called is not passed to inner). This is why I have changed from # (3') to # (3) and I must store keys from outer scope with # (2) in order to keep track of keys and make the MWE working.

This is totally puzzling me!

My question is: Why some of my locals variable does not reach the inner scope of my factory?

1

There are 1 best solutions below

0
On

I tried your MWE and it gave me different results (Python 3.5.2):

>>> test = TicksFormatterFactory(minor_fontsize=4)
{'limits': 100,
 'major_fontsize': 7,
 'major_format': None,
 'major_gridstyle': None,
 'major_locator': None,
 'major_rotation': 90,
 'minor_fontsize': 4,
 'minor_format': None,
 'minor_gridstyle': None,
 'minor_locator': None,
 'minor_rotation': None,
 'target': 'xaxis',
 'title': None,
 'title_kw': {}}
>>> test(1, title='Hello')
{'axe': 1,
 'kwargs': {'title': 'Hello'},
 'varKeys': dict_keys(['title_kw', 'minor_fontsize', 'minor_rotation', 'minor_gridstyle', 'minor_format', 'minor_locator', 'major_fontsize', 'major_rotation', 'major_gridstyle', 'major_format', 'major_locator', 'limits', 'target', 'title'])}
{'limits': None,
 'major_fontsize': None,
 'major_format': None,
 'major_gridstyle': None,
 'major_locator': None,
 'major_rotation': None,
 'minor_fontsize': None,
 'minor_format': None,
 'minor_gridstyle': None,
 'minor_locator': None,
 'minor_rotation': None,
 'target': None,
 'title': 'Hello',
 'title_kw': None}

Which is what I expected. Calling locals() inside inner shouldn't return the outer function's parameters, simply because they're not local to inner unless you reference them.

You need to access TicksFormatterFactory's locals this way:

def TicksFormatterFactory( \
    title=None, target='xaxis', limits=100 \
   ,major_locator=None, major_format=None, major_gridstyle=None, major_rotation=90, major_fontsize=7 \
   ,minor_locator=None, minor_format=None, minor_gridstyle=None, minor_rotation=None, minor_fontsize=None \
   ,title_kw=dict() \
):
    pprint.pprint(locals())   # (1)
    localVars = locals() # (2)
    def inner(axe, **kwargs):

        pprint.pprint(locals())   # (5)

        # Allow User to overwrite settings using kwargs:
        settings = dict()
        for k in localVars:
            settings[k] = kwargs.get(k, localVars.get(k)) # (3)

        pprint.pprint(settings) # (4)

    return inner

Which yields:

>>> test = TicksFormatterFactory(minor_fontsize=4)
{'limits': 100,
 'major_fontsize': 7,
 'major_format': None,
 'major_gridstyle': None,
 'major_locator': None,
 'major_rotation': 90,
 'minor_fontsize': 4,
 'minor_format': None,
 'minor_gridstyle': None,
 'minor_locator': None,
 'minor_rotation': None,
 'target': 'xaxis',
 'title': None,
 'title_kw': {}}
>>> test(1, title='Hello')
{'axe': 1,
 'kwargs': {'title': 'Hello'},
 'localVars': {'limits': 100,
               'major_fontsize': 7,
               'major_format': None,
               'major_gridstyle': None,
               'major_locator': None,
               'major_rotation': 90,
               'minor_fontsize': 4,
               'minor_format': None,
               'minor_gridstyle': None,
               'minor_locator': None,
               'minor_rotation': None,
               'target': 'xaxis',
               'title': None,
               'title_kw': {}}}
{'limits': 100,
 'major_fontsize': 7,
 'major_format': None,
 'major_gridstyle': None,
 'major_locator': None,
 'major_rotation': 90,
 'minor_fontsize': 4,
 'minor_format': None,
 'minor_gridstyle': None,
 'minor_locator': None,
 'minor_rotation': None,
 'target': 'xaxis',
 'title': 'Hello',
 'title_kw': {}}