by Leodanis Pozo Ramos Publication date Dec 10, 2025 Reading time estimate 20m intermediate python
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python Inner Functions
Python inner functions are those you define inside other functions to access nonlocal names and bundle logic with its surrounding state. In this tutorial, you’ll learn how to create inner helper functions, build closures that retain state across calls, and implement decorators that modify the behavior o…
by Leodanis Pozo Ramos Publication date Dec 10, 2025 Reading time estimate 20m intermediate python
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python Inner Functions
Python inner functions are those you define inside other functions to access nonlocal names and bundle logic with its surrounding state. In this tutorial, you’ll learn how to create inner helper functions, build closures that retain state across calls, and implement decorators that modify the behavior or existing callables without changing the original implementation.
By the end of this tutorial, you’ll understand that:
- Inner functions access nonlocal names from the enclosing scope, so you pass data in once and reuse it across calls.
- You can replace an inner helper function with a non-public function to enable code reuse.
- You can create a closure by returning the inner function without calling it, which preserves the captured environment.
- You can modify the captured state by declaring nonlocal variables that point to mutable objects.
- You craft decorators with nested functions that wrap a callable and extend its behavior transparently.
You will now move through focused examples that feature encapsulated helpers, stateful closures, and decorator patterns, allowing you to apply each technique with confidence in real Python projects.
** Take the Quiz:** Test your knowledge with our interactive “Python Inner Functions: What Are They Good For?” quiz. You’ll receive a score upon completion to help you track your learning progress:
Creating Functions Within Functions in Python
A function defined inside another function is known as an inner function or a nested function. Yes, in Python, you can define a function within another function. This type of function can access names defined in the enclosing scope.
Here’s an example of how to create an inner function in Python:
In this example, you define inner_func() inside outer_func() to print the Hello, World! message to the screen. To do that, you call inner_func() on the last line of outer_func(). This is the quickest way to write and use an inner function in Python.
Inner functions provide several interesting possibilities beyond what you see in the example above. The core feature of inner functions is their ability to access variables and objects from their enclosing function even after that function has returned. The enclosing function provides a namespace that is accessible to the inner function:
Note how you can pass a string as an argument to outer_func(), and inner_func() can access that argument through the name who. This name is defined in the local scope of outer_func(). The names defined in the local scope of an outer function are nonlocal names from the inner function’s point of view.
Here’s an example of a more realistic inner function:
In factorial(), you first validate the input data to ensure that the user provides an integer that is equal to or greater than zero. Then, you define a recursive inner function called inner_factorial(). This function performs the factorial calculation and returns the result. The final step is to call inner_factorial().
An advantage of using the pattern in the example above is that you perform all the argument validation in the outer function, so you can skip error checking in the inner function and focus on the computation at hand.
Using Inner Functions in Python
The use cases of Python inner functions are varied. You can use them to provide encapsulation, hiding your functions from external access. You can also write quick helper inner functions. Finally, you can use inner functions to create closures and decorators.
In this section, you’ll learn about the former two use cases of inner functions, and in later sections, you’ll learn how to create closures and decorators.
Providing Encapsulation
A common use case of inner functions arises when you need to protect or hide a given function from everything happening outside of it, so that the function is completely hidden from the global scope. This type of behavior is known as encapsulation.
Here’s an example that showcases the concept:
In this example, you can’t access inner_increment() directly because it’s encapsulated in increment(). If you try to do it, then you get a NameError exception. The increment() function totally hides inner_increment(), preventing access from the global scope.
Building Helper Inner Functions
Sometimes you have a function that repeats the same chunk of code in several places within its body. To avoid this repetition, you can use an inner function.
For example, say you want to write Python code to process a CSV file containing information about the Wi-Fi hotspots in New York City. You’ll find the CSV file in the companion materials for this tutorial.
To find the total number of hotspots in New York, as well as the company that provides most of them, you create the following module:
Here, process_hotspots() takes a CSV file as an argument. Then, it checks whether the file is a string-based path to a physical file or a file object. Next, it counts the number of Wi-Fi hotspots per provider using a collections.Counter object and finally, prints a message with the retrieved information.
If you call this function, then you get the following output:
Whether you call process_hotspots() with a string-based file path or with a file object, you get the same result.
If you examine the function’s implementation closely, you’ll notice that the if and elif code blocks are quite similar and look repetitive, especially the for loop.
Here’s how you could use an inner function to remove this repetition and make your code more readable:
In this new implementation, you create an inner function to pack the for loop and give it a descriptive name. Then, you call the helper inner function in the conditional as you did before.
The inner function reads the file content into a generator that yields dictionaries using csv.DictReader. Then, create a list of Wi-Fi providers and store them in hotspots for further processing. Go ahead and try out this code as you did with the previous version!
Using Inner vs Non-Public Helper Functions
Typically, you create helper inner functions, such as extract_hotspots(), when you want to provide encapsulation. You can also create inner functions if you think you’re not going to call them anywhere else apart from the containing function.
Although writing your helper functions as inner functions achieves the desired result, you’ll probably be better off extracting them as top-level functions. In this case, you could use a leading underscore (_) in the function’s name to indicate that it’s non-public to the current module or class. This practice will allow you to access your helper functions from other parts of the current module or class and reuse them as needed.
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.
Creating Closures With Inner Functions
In Python, functions are first-class citizens. This means that they’re on par with any other object, such as numbers, strings, lists, tuples, modules, and so on. You can dynamically create or destroy them, store them in data structures, pass them as arguments to other functions, use them as return values, and so forth.
You can also create higher-order functions in Python. Higher-order functions are those that operate on other functions by taking them as arguments, returning them, or both.
The examples of inner functions that you’ve seen so far are ordinary functions that happen to be nested inside other functions. Unless you need to hide your functions from the outside world, there’s no specific reason to nest them. You could define those functions as non-public top-level functions, and you’d be good to go.
In this section, you’ll learn about closure factory functions. Closures are inner functions returned as function objects. Their main feature is that they can access the variables and names defined in the enclosing scope, even though the enclosing function has finished executing.
When you return an inner function object, Python packs the function along with its containing environment. This package is a closure.
Under the hood, the function object retains a snapshot of all the variables and names defined in its containing scope. To define a closure, you need to take three steps:
- Create an inner function.
- Reference variables from the enclosing function.
- Return the inner function object without calling it.
With this knowledge, you’re ready to start creating closures in Python and take advantage of their primary feature: retaining state between function calls.
Retaining State in a Closure
A closure is able to retain the state between consecutive function calls. The closure isn’t the inner function itself but the inner function along with its enclosed environment. The closure captures the local variables and names in the containing function and retains them.
Consider the following example:
In this example, you create generate_power(), which is a closure factory function. This means that it creates and returns a new closure each time you call it. Inside this function, you define power(), which is an inner function that takes a single argument, base, and returns the result of the expression base**exponent. Finally, your return power as a function object, without calling it.
Where does power() get the value of exponent from? In this example, power() gets the value of exponent from the outer function. Here’s what Python does when you call the generate_power() function:
- Defines a new instance of
power(), which takes a single argumentbase. - Takes a snapshot of the surrounding scope, including the
exponentargument with its current value. - Returns the
powerfunction object along with its whole surrounding scope.
When you call the closure returned by generate_power(), you’ll see that the power() function remembers the value of exponent:
In these examples, square() remembers that exponent=2, and cube() remembers that exponent=3. Note that both closures remember their respective exponent between calls.
Now consider another example:
The inner function checks whether a user has the correct permissions to access a given page. The check_permission object is a closure that remembers the input page between calls.
You’ll often create closures that don’t modify the state of their enclosing scope, or closures with a static enclosing state, as you saw in the above examples. However, you can also create closures that modify their enclosing scope by taking advantage of mutable objects.
Suppose you need to calculate the mean of a dataset. The data comes in a stream of successive values of the parameter under analysis, and you need your function to retain the previous values between calls. In this situation, you can code a closure factory function like the following:
The samples_mean closure retains the state of samples between successive calls. Even though you define samples in mean(), it’s still available in the closure, and you can modify it. In this example, samples works as persistent storage for consecutive data values.
Modifying the Closure State
Normally, the variables in a closure are completely hidden from the outside world. However, you can provide getter and setter inner functions for them:
In this example, make_point() returns a closure that represents a point object. This object has getter and setter functions associated with it. You can use those functions to read and modify the variables x and y, which are defined in the enclosing scope and shipped with the closure.
Even though this function creates closures that look like a class, you need to be aware that this technique doesn’t provide major features, such as inheritance, properties, descriptors, and class and static methods. If you want to dive deeper into this technique, then check out Simple Tool for Simulating Classes Using Closures and Nested Scopes (Python Recipe).
Building Decorators With Nested Functions in Python
Python decorators are another popular use case of inner functions, particularly of closures. Decorators are higher-order functions that take a callable (function, method, class) as an argument and return another callable with a modified behavior.
You can use decorator functions to dynamically add functionality to an existing callable and extend its behavior transparently, without affecting or modifying the original implementation.
Once you have a decorator function in place, you can apply it to any callable. To do so, you need to use the at symbol (@) in front of the decorator name and then place it on its own line immediately before the decorated callable:
This syntax enables @decorator to automatically take the decorated_func function object as an argument and process it within its body. This is syntactic sugar for the following assignment:
Here’s an example of how to build a decorator to add new functionality to an existing function:
In this example, you use @add_messages to decorate greet(). This way, you add new functionality to the decorated function. Now, when you call greet(), instead of just printing Hello, World!, it prints two new messages.
Some of the most common use cases for Python decorators include the following:
For example, you can write a decorator like the following for debugging your functions:
In this example, you define debug() as a decorator that takes a function as an argument and prints its signature with the current value of each argument and the corresponding return value. You can use this decorator to debug your functions. Once you get the desired result, you can remove the decorator call @debug, and your function will be ready for use.
As a final example, you’ll reimplement generate_power() to work as a decorator:
This version of generate_power() produces the same results as the original implementation. In this example, you use both a closure to remember exponent and a decorator that returns a modified version of the input function object, func.
The decorator needs to take an argument (exponent), so you need to have two levels of nested functions. The first level corresponds to power(), which takes the decorated function as an argument. The second level is inner_power(), which packs the argument exponent in args, makes the final power calculation, and returns the result.
Conclusion
If you define a function inside another function, then you’re creating an inner function, also known as a nested function. In Python, inner functions have direct access to the variables and names that you define in the enclosing function. This provides a mechanism for you to create helper functions, closures, and decorators.
In this tutorial, you learned how to:
- Provide encapsulation by nesting functions in other functions
- Write helper functions to reuse pieces of code
- Implement closure factory functions that retain state between calls
- Build decorator functions to provide new functionalities
With this knowledge, you’re now better prepared to start using inner functions to retain state, create closures, and build decorators in Python.
Frequently Asked Questions
Now that you have some experience with inner functions in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
You define an inner function inside another function to bundle logic with surrounding state and keep it encapsulated. You use it when you want a small helper tied to the outer function or when you want to build a closure or a decorator. You also use it to hide implementation details from other modules.
You return the inner function without calling it, which creates a closure that remembers names from the enclosing scope. The closure captures the values that the inner function references, so you reuse them across later calls. You then call the returned function to operate with that preserved state.
You use nonlocal when the inner function must rebind a name defined in the enclosing function rather than only read it. Without nonlocal, Python creates a new local name inside the inner function. With nonlocal, you update the captured state across calls.
You keep it inner when you only call it inside the outer function and you want strict encapsulation. You extract it as a non-public top-level helper with a leading underscore to enable reuse and improve readability. You choose based on how many places need the helper and how much you want to expose.
You write a function that takes a callable and returns an inner wrapper that calls the original plus extra behavior. You apply it with @decorator so Python replaces the original callable with the wrapper. You can nest another level when the decorator itself needs parameters.
** Take the Quiz:** Test your knowledge with our interactive “Python Inner Functions: What Are They Good For?” quiz. You’ll receive a score upon completion to help you track your learning progress:
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python Inner Functions