0

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?

2
  • 1
    Note that, inside transform_words, the function surround_with_value is transform. So when we call transform(word), we're actually calling surround_with_value(word), because those two names refer to the same function (not quite, actually, as surround_with_value goes out of scope when surround_with ends, but this is the easiest way to think about it). Commented Jul 14, 2016 at 8:53
  • @jonrsharpe That makes sense, I posted an answer based on my understanding, can you check for its correctness? Commented Jul 14, 2016 at 9:00

3 Answers 3

1

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))
Sign up to request clarification or add additional context in comments.

Comments

0

I think I understood it,

When we call surround_with('*'), it returns the function surround_with_value() which is then returning the value '{}{}{}'.format('*', word, '*').

Now the very same function takes an argument (word here) which is then passed inside transform_words() and renamed as transform().

Inside it we finally pass the value for word.

1 Comment

That's pretty much it, yes!
0

Closure can be very confusing and this example might not be the best to show why surround_with_value() remembers surround with(surrounding) event if it's not in its scope.

I strongly advice you to read this excellent blog showing all concept you need to understand to understand decorators. Step1: Scope-->Step2: Closure-->Step3: Decorators

Take some time and read it from the beginning. http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.