Tasdik Rahman Tasdik Rahman - 4 months ago 11
Python Question

Scope of variable when passed to another function in python decorators

So I was reading this wonderful piece which tries to explain decorators in python.

My question is specific to this code snippet.

def surround_with(surrounding):
"""Return a function that takes a single argument and."""
def surround_with_value(word):
return '{}{}{}'.format(surrounding, word, surrounding)
return surround_with_value

def transform_words(content, targets, transform):
"""Return a string based on *content* but with each occurrence
of words in *targets* replaced with
the result of applying *transform* to it."""
result = ''
for word in content.split():
if word in targets:
result += ' {}'.format(transform(word))
else:
result += ' {}'.format(word)
return result

markdown_string = 'My name is Jeff Knupp and I like Python but I do not own a Python'
markdown_string_italicized = transform_words(markdown_string, ['Python', 'Jeff'],
surround_with('*'))
print(markdown_string_italicized)


What I don't understand is how did the function
surround_with()
get the variable
word
(when passed on by
transform(word)
inside
transform_words()
) in it's scope? I mean we have only declared a holding variable (as a function argument) for what the surrounding value should be and nothing else. Then how was
word
available to it?

What am I missing here?

Answer

The surround_with() function returns another function object with a closure:

def surround_with(surrounding):
    """Return a function that takes a single argument and."""
    def surround_with_value(word):
        return '{}{}{}'.format(surrounding, word, surrounding)
    return surround_with_value

So surround_with_value is returned; it is this function that prepends and appends the value of surrounding to whatever is passed in:

>>> def surround_with(surrounding):
...     """Return a function that takes a single argument and."""
...     def surround_with_value(word):
...         return '{}{}{}'.format(surrounding, word, surrounding)
...     return surround_with_value
...
>>> function = surround_with(' foo ')
>>> function
<function surround_with_value at 0x108ac16e0>
>>> function('bar')
' foo bar foo '

The surround_with_value() function was returned and a reference to it was stored in the name function. That function object references surrounding as a closure:

>>> function.__closure__
(<cell at 0x108a8a590: str object at 0x1074c4060>,)
>>> function.__closure__[0].cell_contents
' foo '

and each time you call it that closure is dereferenced and the contents are used.

So surround_with() produces a function object, and such function (as the result of surround_with('*')), is passed to transform_words() as the 3rd argument:

transform_words(markdown_string, ['Python', 'Jeff'],
        surround_with('*'))

so it is assigned to the variable transform:

def transform_words(content, targets, transform):

Thus, each time you call transform, you really are calling the nested surround_with_value() function with '*' as the surrounding closure, and word is being passed in:

result += ' {}'.format(transform(word))
Comments