In general, when the inner function is small and you want to make it clear that it's only useful to the enclosing function. Alternatively, when you need to return a function. The latter scenario is trivial since generally the inner function relies on variables in the enclosing functions's scope, so declaring it anywhere else isn't an option. You might be able to use a lambda in that case, but anything longer than one expression would need a full function declaration.

Without the implementations it's hard to say which one you should choose in this case. I would hasten to add that you don't have to put everything in a class either.

Answer from Doval on Stack Exchange
🌐
Real Python
realpython.com › inner-functions-what-are-they-good-for
Python Inner Functions: What Are They Good For? – Real Python
2 weeks ago - Extracting inner functions into top-level non-public functions can make your code less repetitive and more readable. This practice can produce functions that consequently apply the single-responsibility principle. In Python, functions are first-class citizens.
🌐
Dive into Python
diveintopython.org › home › learn python programming › functions in python › nested functions
Nested Functions in Python - Powerful Tool for Organized Code
May 3, 2024 - Best practices for using nested functions include avoiding excessive nesting, ensuring each function has a clear and specific purpose, and using appropriate variable scopes. To use nested functions in Python, define the outer function and then ...
Discussions

Nested Function Best Practices (Python)
This is perfectly good code. I wouldn't ding it in a professional code review. Another option would be to use an underscore to hint that "display" is private: def _Display(node, level=0): if not node: return display(node.left_child, level + 1) print('\t' * level, node) display(node.right_child, level + 1) def print_inorder(self): self._Display(self.node) But your way is also just fine. More on reddit.com
🌐 r/learnprogramming
6
1
April 24, 2019
code quality - When to use python function nesting? - Software Engineering Stack Exchange
I am writing a program that creates backup of directories. Which of the following approach is better for code organization? Create separate class for Backup and Zip logic - class BackupUtil: ... More on softwareengineering.stackexchange.com
🌐 softwareengineering.stackexchange.com
March 18, 2014
python - Is nested function a good approach when required by only one function? - Stack Overflow
Although you may tell that it is ... general practice. 2017-07-26T19:50:03.623Z+00:00 ... Save this answer. ... Show activity on this post. So in the end it is largely a question about how smart the python implementation is or is not, particularly in the case of the inner function not being ... More on stackoverflow.com
🌐 stackoverflow.com
refactoring - Is it good to use inner function in a python function to make the logic clear? - Stack Overflow
Below is the basic logic for function foo: def foo(item_lst): val_in_foo_scope = 1 for item in item_lst: # some logic to deal with item # val_in_foo_scope used ... More on stackoverflow.com
🌐 stackoverflow.com
🌐
Colinsblog
colinsblog.net › 2023-08-05-nesting-functions
Better Code Organization by Nesting Functions - Colin's Notes
August 5, 2023 - This extends to the inner functions: They are not placed in the code block but as part of the outer function’s definition. So, you don’t have to reason about order of function or variable declaration or assignment. You’re only concerned with assignment or re-assignment and function call order. In this way it behaves superficially like Python.
🌐
GeeksforGeeks
geeksforgeeks.org › python › python-inner-functions
Python Inner Functions - GeeksforGeeks
May 22, 2026 - Inner functions follow Python’s LEGB rule (Local --> Enclosing --> Global --> Built-in).
🌐
Reddit
reddit.com › r/learnprogramming › nested function best practices (python)
r/learnprogramming on Reddit: Nested Function Best Practices (Python)
April 24, 2019 -

I have been writing a class which resembles a binary tree and have been looking to clean up my code a bit and found my way to nested functions. I understand the basic logic behind them and what not, my question is more on how they are used within custom objects.

For example, if i am trying to print the tree in-order and i do something like:

def print_inorder(self):
    def display(node, level):
        if not node:
            return 
        display(node.left_child, level + 1)
        print('\t' * level, node)
        display(node.right_child, level + 1)
   display(self.root, 0)

Would this be correct best practices? The thing that is somewhat confusing to me is switching from using 'self.' for functions relating to the object, to an inner function that doesn't need a 'self.' even though an object is technically calling it. is this already best practice or should I still be using the 'self.'? or is there a better convention to use all together? Thanks.

🌐
Towards Data Science
towardsdatascience.com › home › programming › should you use nested functions to encapsulate logic?
Should you use nested functions to encapsulate logic? | Towards Data Science
November 17, 2020 - When coding we want to hide immaterial detail. E.g. we mark functions as private as much as we can (well in python we can't really, so we use a leading underscore convention _like_this).
Find elsewhere
🌐
Analytics Vidhya
analyticsvidhya.com › home › how nested functions are used in python?
How nested functions are used in Python? - Analytics Vidhya
March 4, 2025 - Although writing internal helper functions gives the desired result, it is usually best to expose them as top-level functions. In this case, you can use the underscore prefix in the function name to indicate that it is private to the current ...
🌐
PW Skills
pwskills.com › blog › python › python-inner-function
Python Inner Function
January 29, 2026 - You have to be "intentional" by using the nonlocal keyword. Yes, you can nest functions as many times as you want. However, for the sake of your sanity (and your coworkers'), it is a "general best practice" to stop at one or two levels.
Top answer
1 of 12
137
>>> def sum(x, y):
...     def do_it():
...             return x + y
...     return do_it
... 
>>> a = sum(1, 3)
>>> a
<function do_it at 0xb772b304>
>>> a()
4

Is this what you were looking for? It's called a closure.

2 of 12
57

You don't really gain much by doing this, in fact it slows method_a down because it'll define and recompile the other function every time it's called. Given that, it would probably be better to just prefix the function name with underscore to indicate it's a private method -- i.e. _method_b.

I suppose you might want to do this if the nested function's definition varied each time for some reason, but that may indicate a flaw in your design. That said, there is a valid reason to do this to allow the nested function to use arguments that were passed to the outer function but not explicitly passed on to them, which sometimes occurs when writing function decorators, for example. It's what is being shown in the accepted answer although a decorator is not being defined or used.

Update:

Here's proof that nesting them is slower (using Python 3.6.1), although admittedly not by much in this trivial case:

setup = """
class Test(object):
    def separate(self, arg):
        some_data = self._method_b(arg)

    def _method_b(self, arg):
        return arg+1

    def nested(self, arg):

        def method_b2(self, arg):
            return arg+1

        some_data = method_b2(self, arg)

obj = Test()
"""
from timeit import Timer
print(min(Timer(stmt='obj.separate(42)', setup=setup).repeat()))  # -> 0.24479823284461724
print(min(Timer(stmt='obj.nested(42)', setup=setup).repeat()))    # -> 0.26553459700452575

Note I added some self arguments to your sample functions to make them more like real methods (although method_b2 still isn't technically a method of the Test class). Also the nested function is actually called in that version, unlike yours.

🌐
CodeRivers
coderivers.org › blog › python-inner-function
Python Inner Functions: Unveiling the Hidden Power - CodeRivers
March 8, 2025 - Python inner functions are a powerful and versatile feature that can greatly enhance your programming skills. They offer benefits such as data encapsulation, the ability to create dynamic functions, and the implementation of decorators. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can effectively incorporate inner functions into your Python code.
🌐
Bomberbot
bomberbot.com › python › mastering-python-inner-functions-a-deep-dive-for-seasoned-developers
Mastering Python Inner Functions: A Deep Dive for Seasoned Developers - Bomberbot
Closures are a fundamental concept in functional programming, and inner functions in Python provide an excellent mechanism for implementing them. A closure occurs when an inner function retains access to variables in its outer scope, even after the outer function has finished executing. This behavior allows for powerful programming patterns, such as maintaining state between function calls without resorting to global variables. Consider this more practical example of a closure:
🌐
Medium
medium.com › @tanishf24comp › nested-functions-and-their-use-cases-43d799f0de04
Nested Functions and Their Use Cases | by Tanish Fitkariwala | Medium
October 23, 2025 - Over-nesting: Defining functions four or five levels deep leads to highly complex scoping chains that are difficult to debug and understand. Unintentional Modification: Forgetting the nonlocal keyword in Python causes the inner function to create a new local variable instead of modifying the enclosing one, leading to subtle bugs.
🌐
Python Geeks
pythongeeks.org › python geeks › learn python › python inner functions – what are they good for?
Python Inner Functions - What Are They Good For? - Python Geeks
December 31, 2025 - Here, we have defined an outer function calculate_price() that returns an inner function calculate_total_price(). The inner function uses another inner function get_tax(), to calculate the tax on the given price. Python Inner Functions are a useful tool for developers to organize their code and improve its readability.
🌐
PYnative
pynative.com › home › python › inner functions in python: a comprehensive guide
Inner Functions in Python: A Comprehensive Guide – PYnative
January 26, 2025 - Inner functions are a powerful tool in Python that enable encapsulation, reusability, and closures. By understanding and using them effectively, you can write more organized and maintainable code.
🌐
Reddit
reddit.com › r/python › thoughts on nested / inner functions in python for better encapsulation and clarity?
r/Python on Reddit: Thoughts on nested / inner functions in Python for better encapsulation and clarity?
January 8, 2023 -

Been loving Python for 7+ years not and still going strong. I recently found myself writing more and more inner functions to encapsulate logic easier and make otherwise rather polluting/dead functions stick out less.

I like it, because it allows me to write way cleaner and less bloated code – clustering helper-functions to only where they need to be. Also decreases cognitive load considerably by not having to keep track on where a helper function is being used.

But it has gotten to a point where I'm genuinely concerned, because I have also started defining lambdas in inner-functions too! I know lambda shouldn't be used and PEP checker complains too, but it's so handy when combined with list comprehensions…

What are your thoughts on this?
Do you use nested functions yourself or do you consider it bad practice? Where else do you put helpers?


An example would be the following code:

def send_mail(
    *,
    subject: str,
    body_plain: str,
    send_to: Union[List[str], str, List[User], User],
    send_cc: Optional[Union[List[str], str, List[User], User]] = None,
    send_bcc: Optional[Union[List[str], str, List[User], User]] = None,
    reply_to: Optional[Union[List[str], str, List[User], User]] = None
    ...
) -> None:

    def _process_recipients(*, recipients: Optional[Union[List[str], str, List[User], User]]) -> List[str]:
        """ Process various inputs for `send_to`, `send_cc` and `send_bcc` to a normalized
        output that can be used by the emailing instance aka a list of emails """
        _user_to_email = lambda x: x.email if isinstance(x, User) else x  # transform user objects to mail
        if not recipients: return []  # recipients are empty
        if isinstance(recipients, str) or isinstance(recipients, User):
            recipients = [recipients]
        return [_user_to_email(recipient) for recipient in recipients]

    ...

    send_to = _process_recipients(recipients=send_to)
    send_cc = _process_recipients(recipients=send_cc)
    send_bcc = _process_recipients(recipients=send_bcc)

    ...
Top answer
1 of 29
168
You seem very receptive to feedback and understanding how code “feels” to people, so I’ll be direct. It is hard to get across how much I hate this code. Look at those type hints. So many options! I get that you’re trying to make things flexible, but all of this variety of types makes things so much harder to follow. People end up passing around strings, users, lists of strings, lists of users and nulls all over the place instead of having one clear place for casting. If I want to reuse process_recipients I can’t. If I want to test it, I can’t. You could rename it to something like cast_to_emails_list and have this one reusable “takes anything” function. Your inner functions are more polluted because they have access to more outer scope variables. You’re more likely to be bitten by typos and other mistakes. The keyword only arguments are fine on send_mail since that’s so ripe for mistakes. It’s silly on process_recepients. The likelihood that someone will mix up the order of positional arguments on a function with one argument is… low. Other ways to achieve your goals: Give your functions more descriptive names, as in this name helps me understand what the function does if I didn’t know already. Use a doc string if needed. Give functions a leading underscore to mark them as a private. But consider if you need to do this. You’re not really making someone’s life easier by giving them less tools that are useful. It’s just if you don’t want it to be something supported outside of the scope of that module. __all__ is similar. Have one input type for arguments unless the role of the function is casting/deserialization (single responsibility). It’s fine to have some “helpers” module for stuff that isn’t part of the “main story”. A hint that you’re doing it right is you don’t end up with circular imports there. Check out Grokking Simplicity. I think you’ll like it. You have good instincts, just need to see some ways other people have solved the same problems.
2 of 29
52
There are two questions I think you should ask yourself. Firstly, if these inner functions aren't closing over local variables, what's the benefit compared to putting them outside the function? Outside the function, they're easier to test and to reuse, and perhaps most importantly, to ignore. So I feel like I'd want to get something back in return for that, and it's not clear to me what that is. Secondly, it's worth talking to other people on your team to see what they make of this. I know I've been guilty of writing clever code that seems perfectly intuitive to me but that my teammates stare blankly at. Getting someone else to read it will tell you whether it's readable.
🌐
CodeRivers
coderivers.org › blog › function-inside-a-function-python
Function Inside a Function in Python: A Comprehensive Guide - CodeRivers
August 8, 2026 - Even though outer_function() has finished executing, the returned closure still remembers the value of outer_variable. When a closure needs to modify a variable from its enclosing scope, you must use the nonlocal keyword. Without it, Python creates a new local variable inside the inner function instead of updating the outer one.
🌐
Python.org
discuss.python.org › python help
The cost of nested definitions - Python Help - Discussions on Python.org
August 7, 2023 - My problem is advanced and out of the scope of the question. So I’ll keep it simple. Imagine the following: This is a set of helper functions, they only serve to make other functions more readable. def functionA(*args, **kwargs): ... def functionB(*args, **kwargs): ... def functionC(*args, **kwargs): ... But then I have my main functions: def mainA(*args, **kwargs): ... a = functionA(*args, **kwargs) ... def mainB(*args, **kwargs): ....