Search code examples
pythonfunctionpycharmautomated-refactoring

Have PyCharm extract code in a Python function to a nested function


I have had PyCharm 2017.3 extract some code inside a top-level function to another top-level function, and it does a good job.

However, sometimes I would like to not put the extracted function on top level, but rather it should become a function nested inside the existing function. The rationale is re-using code that is only used inside a function, but several times there. I think that this "sub-function" should ideally not be accessible outside of the original function.

How can I do this? I have not seen any options in the refactoring dialog.

Example

a) Original code:

def foo():
    a = ''
    if a == '':
        b = 'empty'
    else:
        b = 'not empty'
    return b

b) What extracting does:

def foo():
    a = ''
    b = bar(a)
    return b


def bar(a):
    if a == '':
        b = 'empty'
    else:
        b = 'not empty'
    return b

c) What I would like to have:

def foo():
    def bar():
        if a == '':
            b = 'empty'
        else:
            b = 'not empty'
        return b

    a = ''
    b = bar(a)
    return b

I am aware that bar's b will shadow foo's b unless it is renamed in the process. I also thought about completely accepting the shadowing by not returning or requesting b and just modifying it inside bar.

Please also hint me if what I want is not a good thing for any reason.


Solution

  • It is considered good practice to keep function boundaries isolated: get data as parameters and spit data as return values with as little side-effects as possible. That said, there are a few special cases where you break this rule; many of them when using closures. Closures are not as idiomatic in Python as they are in Javascript - personally I think it is good but many people disagree.

    There is one place were closures are absolutely idiomatic in Python: decorators. For other cases where you would use a closure in order to avoid use of global variables and provide some form of data hiding there are other alternatives in Python. Although some people advocates using closure instead of a class when it has just one method, a plain function combined with functools.partial can be even better.

    This is my guess about why there is no such feature in Pycharm: we almost never do it in Python, instead we tend to keep the function signature as foo(x) even when we can get x from the enclosing scope. Hell, in Python our methods receive self explicitly where most languages have an implicit this. If you write code this way then Pycharm already does everything that is needed when refactoring: it fixes the indentation when you cut & paste.

    If you catch yourself doing this kind of refactoring a lot I guess you are coming from a language where closures are more idiomatic like Javascript or Lisp.

    So my point is: this "nested to global" or "global to nested" function refactoring feature does not exist in Pycharm because nested functions relying on the enclosing scopes are not idiomatic in Python unless for closures - and even closures are not that idiomatic outside of decorators.

    If you care enough go ahead and fill a feature request at their issue tracker or upvote some related tickets like #PY-12802 and #PY-2701 - as you can see those have not attracted a lot of attention possibly because of the reasons above.