3

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.

4
  • In most cases you can just copy & paste the function and Pycharm will fix the indentation. I'm more than happy with that. Also, inner functions are mostly used inside decorators, functions that return a function (function factories?) or places where you would use a lambda in a language where lambda is not crippled to one-liners. I guess these are not used often enough to justify a special feature. Commented Dec 4, 2017 at 13:25
  • Thanks for the info. I am not concerned about the indentation, but rather about encapsulation and the proper interface between the extracted function and its scope. Am I misunderstanding the term "inner function"? I think of it as a nested subfunction, i.e., a function inside a function. Note the different signature of b) vs. c). Commented Dec 4, 2017 at 13:29
  • I meant: Note the different signature of bar(a) in b) vs. bar() c). Commented Dec 4, 2017 at 13:35
  • I think in Python the most used term is "nested functions". Closures are not as pervasive in Python as they are in languages like Javascript (thanks God). Other than closures, having a function that depends on the enclosing namespace is often a code smell in any language so most python programmers would keep the signature as bar(a) even if it is a nested function. Commented Dec 4, 2017 at 13:42

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your answer. However, I must point out the different use case here: I am not about closures, I am about nested functions. While you might call any nested function a closure, they are never used in my example, since the function is never labeled apart from its definition, and is only called from within the defining scope.
I see, my point is exactly that: 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. That justifies the lack of such special case refactoring feature for me, but you can hear it straight from the horse's mouth if you ask Jetbrains directly - their official support channels are superb.
Ah, now I get your point, thanks for the clarification! I am just looking at the link. I will definitely read a lot there from now on.

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.