# Function-level (indent-less) "with" to do cleanup when function exits

**URL:** <https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958>\
**Category:** Ideas\
**Created:** [September 8, 2026, 7:44pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958 "2026-09-08T19:44:10Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![techvslife](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/techvslife/32/37377_2.png) [@techvslife](https://discuss.python.org/u/techvslife)\
**Post date:** [September 8, 2026, 7:44pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/1 "2026-09-08T19:44:10Z")

</div>

This is a suggestion for a minor Python syntax enhancement (to “with” statements), but it may not be of wide interest or there may be objections I haven’t considered. I often find myself wanting to have context-managed resources automatically cleanup upon exit from a function, but an indent of the entire function code seems ugly and pointless in this case. It’s also wasteful in the “indent” economy (–things are harder to follow the more indents there are). I don’t need to track or clean up the resources block level: I view them as similar to function-scoped variables in that I need them around for the life of the function but don’t want to think or track them at finer block levels. (Syntax of this function-level “with” could be “with” without a colon at the end.)

Ordinarily one would have to do this in Python:

`def process(): `  
` with managed_resource() as db, open("example.txt", "r") as file:`  
` # All lines have to be indented if they might rely on db and file`  
` return "Done"`

In contrast, C# allows its “with” equivalent, “using” statements, in a function-level form (in addition to its (traditional) block-level syntax)

`public void ProcessFile() {`  
` using var f = new StreamReader("file.txt"); 'using is the C# "with"`  
` 'statements that might make use of f, don't need C# braces (=indents)`  
` 'f is cleaned up on function exit`  
`}`

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [September 8, 2026, 8:53pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/2 "2026-09-08T20:53:50Z")

</div>

This might be less of a syntax issue and more of a garbage collection issue. In this context Python doesn’t really have such a thing as a function-scoped variable, the variable can always escape its scope and then will not close itself once the function exits. Some implementations such as PyPy were known to cleanup “function-scoped” variables at an arbitrary time rather than at the end of a function.

The `with` statement is used to ensure a variable calls its closing routine even if the variable was kept alive elsewhere. The indent is required to mark the boundary for the setup and cleanup routines.

Hiding the indent is technically possible as context management can be hidden in another space such as within a function decorator, though I wouldn’t recommend it. Being able to consolidate multiple with statements into a single block has already solved the worst cases.

---

<div class="post-metadata">

**Author:** ![petercordia](https://avatars.discourse-cdn.com/v4/letter/p/bb73d2/32.png) [@petercordia](https://discuss.python.org/u/petercordia)\
**Post date:** [September 8, 2026, 8:56pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/3 "2026-09-08T20:56:21Z")

</div>

I recognise the issue.

This has caused me to adopt a code pattern where often I will pass the open ‘file’ into the function instead of ‘filename’, and calling functions with an open database connection instead of opening them in the function.

If you don’t have to obey a linter, or if you’re really good at modifying the rules of your linter you might be able to allow yourself the special case that a ‘with’ on the first line of a function has an indent of 2 spaces instead of 4, and its body has 4 spaces instead of 8. But that isn’t workable for me, so I don’t know if it counts as a real solution.

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [September 8, 2026, 9:07pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/4 "2026-09-08T21:07:35Z")

</div>

Statements with a function scope are interesting, but I’m unsure how well they’d mesh with everything else in Python. These could mess with what’s expected of error handling in unknown ways, and having multiple ways to do the same thing always adds at least a little confusion.

```python
def process() -> None:
    using file = open("example.txt", "r") # file = file. __enter__ () now, file. __exit__ () at function end
    ... # use file

```

```python
def process() -> None:
    file = open("example.txt", "r")
    defer file.close() # called when function ends
    ... # use file

```

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [September 8, 2026, 9:36pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/5 "2026-09-08T21:36:29Z")

</div>

With the scope tied to the function, this does a good job at keeping the function flat:

```python
def process() -> None:
    if x:
        using file = open("foo.txt", "r")
    else:
        using file = open("bar.txt", "r")
    ... # use file
    # either file closed when function ends

```

But what happens in a loop? This syntax tells Python to keep files open until the end of the function. Muscle memory is all it takes to make this mistake:

```python
def process() -> None:
    for path in paths:
        using file = open(path, "r")
        ... # do something for only this file
    # all files remain open until the function ends

```

If `using` in a for-loop closed the previous file then that breaks this hypothetical statements function-scope rules. `using` could be tied to the block scope but that would be very unusual for Python.

---

<div class="post-metadata">

**Author:** ![bswck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bswck/32/34363_2.png) [@bswck](https://discuss.python.org/u/bswck)\
**Post date:** [September 8, 2026, 10:30pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/8 "2026-09-08T22:30:28Z")

</div>

> [@techvslife](#):
>
> `def process(): `  
> ` with managed_resource() as db, open("example.txt", "r") as file:`  
> ` # All lines have to be indented if they might rely on db and file`  
> ` return "Done"`

```py
from contextlib import ExitStack

def process():
    resources = ExitStack()
    db = resources.enter_context(managed_resource())
    file = resources.enter_context(open("example.txt", "r"))

    # Do the things with db and file...

    resources.close()
    return "Done"

```

I think this is quite clean and is supported by Python out of the box.  
Works everywhere and adds zero indentation to your code.

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [September 8, 2026, 10:41pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/9 "2026-09-08T22:41:31Z")

</div>

I was trying to find something like `ExitStack.close()` earlier. It does have its own issue that any raised exception will exit the function without immediately closing those resources.

---

<div class="post-metadata">

**Author:** ![techvslife](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/techvslife/32/37377_2.png) [@techvslife](https://discuss.python.org/u/techvslife)\
**Post date:** [September 8, 2026, 10:45pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/10 "2026-09-08T22:45:23Z")

</div>

Thank you for the comments and suggestions–they’re all points that carry weight with me, but I’m tempted to push back a little:

First, regarding bswck’s suggestion (I’ll do other posts for other points) :

> [@Function-level (indent-less) "with" to do cleanup when function exits](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/8):
>
> from contextlib import ExitStack def process(): resources = ExitStack() db = resources.enter\_context(managed\_resource()) file = resources.enter\_context(open("example.txt", "r")) # Do the things with db and file... resources.close() return "Done" I think this is quite clean and is supported by Python out of the box. Works everywhere and adds zero indentation to your code.

That does solve the indentation issue, but now you have four lines instead of one, and it’s divorced from the WITH keyword, which is the flag that one is calling the context manager. So it’s un-pythonic in requiring two ways to do essentially the same one thing, and one is longer and has a quite different syntax. But the most important point is that it also has a different behavior: your suggestion does NOT automatically clear the resources on exit in case of error or other early exit, which a major reason for using “with.” (edit: as [HexDecimal](https://discuss.python.org/u/hexdecimal) also notes).

---

<div class="post-metadata">

**Author:** ![techvslife](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/techvslife/32/37377_2.png) [@techvslife](https://discuss.python.org/u/techvslife)\
**Post date:** [September 8, 2026, 10:59pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/11 "2026-09-08T22:59:52Z")

</div>

I was worried that it may not mesh well with everything else in Python, and it’s not worth it if it creates any new problems. But let’s see what I can address. Regarding the for-loop problem:

> If `using` in a for-loop closed the previous file then that breaks this hypothetical statements function-scope rules.

My idea is that the function-level “with” statement would NOT have a colon at the end, so there would be a visual indicator. When that syntax is used (no ending colon), it would only be legal outside a for loop or other block. (Maybe that would introduce other problems!)

---

<div class="post-metadata">

**Author:** ![techvslife](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/techvslife/32/37377_2.png) [@techvslife](https://discuss.python.org/u/techvslife)\
**Post date:** [September 8, 2026, 11:08pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/12 "2026-09-08T23:08:53Z")

</div>

Just to underline, I was not proposing a substantial syntax change, so no new keyword (like the “defer” of Golang). I would say use the SAME syntax as the current python WITH statement, but without a closing colon when it is being used in the proposed function-level way. (so like your first suggestion). Would that cause an issue with error handling, especially if a function-level “with” could not be used (i.e. was not legal) inside a for-loop or block?

---

<div class="post-metadata">

**Author:** ![techvslife](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/techvslife/32/37377_2.png) [@techvslife](https://discuss.python.org/u/techvslife)\
**Post date:** [September 8, 2026, 11:13pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/13 "2026-09-08T23:13:51Z")

</div>

> [@petercordia](#):
>
> This has caused me to adopt a code pattern where often I will pass the open ‘file’ into the function instead of ‘filename’, and calling functions with an open database connection instead of opening them in the function.

Thanks, that’s a suggestion I thought of, and that would work. The issue, beyond involving more lines of code and more work, is that it violates the important principle that functions encapsulate what they need to fulfill their purpose and not depend on external code, whenever that doesn’t create other issues or drawbacks. There’s probably a snappy name for that principle, but I forget it.

EDIT: On further reflection (as mentioned in other posts), this actually will be the better pattern for a great many cases (though not all), and at any rate I think it’s the best workaround – thanks!

---

<div class="post-metadata">

**Author:** ![bswck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bswck/32/34363_2.png) [@bswck](https://discuss.python.org/u/bswck)\
**Post date:** [September 8, 2026, 11:19pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/14 "2026-09-08T23:19:19Z")

</div>

You can write a decorator that creates `ExitStack()` on call and then passes that to the targeted function, and manages to call `close()` in a `finally`.

The indentation remains outside the business logic function with some glue code overhead.

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [September 8, 2026, 11:19pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/15 "2026-09-08T23:19:49Z")

</div>

I think I understand, you mean syntax reusing `with` to keep the intent clear:

```python
def process() -> None:
    with file = open("example.txt", "r") # no colon, no indent
    ... # use file

```

---

<div class="post-metadata">

**Author:** ![bswck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bswck/32/34363_2.png) [@bswck](https://discuss.python.org/u/bswck)\
**Post date:** [September 8, 2026, 11:21pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/16 "2026-09-08T23:21:41Z")

</div>

Not trying to discourage the language change idea, but I’m being realistic as to what options there are now. There’s plenty of existing options in the language to achieve a similar outcome IMO. I admit, my ExitStack() proposal doesn’t really work because there’s no `finally` anywhere (that `with` encapsulates). You just have to pay the “indentation tax” _somewhere_, outside your business logic if that bothers you. However, I believe the indentation tax was quite intentional, to make it clear what fragments of your code are resource-occupying.

---

<div class="post-metadata">

**Author:** ![nedbat](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/nedbat/32/8744_2.png) [@nedbat](https://discuss.python.org/u/nedbat)\
**Post date:** [September 8, 2026, 11:28pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/17 "2026-09-08T23:28:06Z")

</div>

A function that takes an open file and does something with it will be easier to test that a function that takes a file name, opens it, and does something with it. It’s easier to test because you can pass non-file objects into the file-taking function, often any iterable of strings will be enough.

If you refactor your file-opening-and-doing function into two (a function that takes an open file, and a function that takes a file name, opens the file, and calls the first function), you’ll have more modularized code that is easier to test, and you’ll have solved your too-many-indents problem as well.

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [September 8, 2026, 11:28pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/18 "2026-09-08T23:28:44Z")

</div>

`ExitStack` passed via a decorator would work, though I forget the correct type-hinting for it. Any indentation is moved into the decorator function.

```python
import functools
from contextlib import ExitStack

def exit_stack_wrapper(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        with ExitStack() as stack:
            func(stack, *args, **kwargs)
    return wrapper

@exit_stack_wrapper
def process(resources: ExitStack) -> None:
    db = resources.enter_context(managed_resource())
    file = resources.enter_context(open("example.txt", "r"))
    ...
    return

```

This of course causes its own issues with parameter handling.

---

<div class="post-metadata">

**Author:** ![bswck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bswck/32/34363_2.png) [@bswck](https://discuss.python.org/u/bswck)\
**Post date:** [September 8, 2026, 11:31pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/19 "2026-09-08T23:31:01Z")

</div>

> [@HexDecimal](#):
>
> This of course causes its own issues with parameter handling.

`ContextVar`s.

This is a nightmare to test and explain, but it can make the code less indented for sure.

> **Example of what I mean**
>
> ```pycon
> >>> from contextlib import ExitStack, ContextDecorator
> ... from contextvars import ContextVar, Token
> ... from typing import Any
> ...
> ... current = ContextVar("current")
> ...
> ... class ContextUnwinder[C: Callable](ExitStack, ContextDecorator):
> ... tok: Token
> ... def __enter__ (self):
> ... self.tok = current.set(self)
> ... return super(). __enter__ ()
> ... def __exit__ (self, *args):
> ... try:
> ... return super(). __exit__ (*args)
> ... finally:
> ... current.reset(self.tok)
> ...
> ... def contextunwinder[C: Callable](f: C) -> C:
> ... return ContextUnwinder()(f)
> ...
> ... def using[R, CM: AbstractContextManager[R]](cm: CM) -> R:
> ... return current.get().enter_context(cm)
> ...
> ... def callback[**P, R](c: Callable[P, R], /, *args: P.args,**kwargs: P.kwargs) -> None:
> ... current.get().callback(c, *args, **kwargs)
> ...
> ... class SanityContextManager:
> ... def __init__ (self, cm):
> ... self.cm = cm
> ... def __enter__ (self):
> ... print(" __enter__ called")
> ... return self.cm. __enter__ ()
> ... def __exit__ (self, *args):
> ... print(" __exit__ called")
> ... return self.cm. __exit__ (*args)
> ...
> ... @contextunwinder
> ... def somefunction(f):
> ... using(SanityContextManager(f))
> ... callback(print, "I've returned!")
> ... print("I've read", c := f.read(16))
> ... return c
> ...
> ... somefunction(open("/dev/urandom", "rb"))
> ...
> __enter__ called
> I've read b'\x92\xfaxd5r\xae<\x80L\x99`Q\x80\x91F'
> I've returned!
> __exit__ called
> b'\x92\xfaxd5r\xae<\x80L\x99`Q\x80\x91F'
> 
> ```

---

<div class="post-metadata">

**Author:** ![techvslife](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/techvslife/32/37377_2.png) [@techvslife](https://discuss.python.org/u/techvslife)\
**Post date:** [September 8, 2026, 11:33pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/20 "2026-09-08T23:33:11Z")

</div>

> [@HexDecimal](#):
>
> Hiding the indent is technically possible as context management can be hidden in another space such as within a function decorator, though I wouldn’t recommend it. Being able to consolidate multiple with statements into a single block has already solved the worst cases.

Agreed, that solved the worst cases. However, I will add that it is pythonic that how code APPEARS, should reflect intention and what the code DOES. Hence I’m all in favor of Python’s meaningful indentation (e.g. “if then” indents). But in cases where the indentation is NOT meaningful, as here, then, for exactly the same reason, that isn’t Pythonic. That is, the indent after a “with” when the resources are not meant to be used inside a block but by the whole function, is a bit un-Pythonic. But this is very minor thing. The main thing is that indentations required for the whole body of function code are almost always going to be wasteful, cluttering, and useless, so if one could get rid of it without creating other drawbacks, I think it would be good to do (as C# has consciously sought to do with a series of changes in recent versions, essentially to get rid of some ceremony and allow flatter structure).

(p.s. I understand that function scoped variables aren’t necessarily cleaned up on function exit, but I just meant it as a conceptual analogy Of course, in the case of resources managed by context managers, one also does want to make sure they get cleaned up as soon as their intended scope ends (–just want to expand that scope to the function level, rather than force the creation of an artificial block).)

---

<div class="post-metadata">

**Author:** ![bswck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bswck/32/34363_2.png) [@bswck](https://discuss.python.org/u/bswck)\
**Post date:** [September 8, 2026, 11:33pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/21 "2026-09-08T23:33:54Z")

</div>

> [@nedbat](#):
>
> If you refactor your file-opening-and-doing function into two (a function that takes an open file, and a function that takes a file name, opens the file, and calls the first function), you’ll have more modularized code that is easier to test, and you’ll have solved your too-many-indents problem as well.

Dependency injection is one honking great idea, let’s do more of it.

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [September 8, 2026, 11:38pm UTC](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958/22 "2026-09-08T23:38:59Z")

</div>

As a real-life example I’ve seen `pytest` use similar-looking syntax for its fixtures. Various ways to fetch unique temporary folders (`tmp_path`), etc, simultaneously and without necessitating additional indentation for each test function.

[Next page](https://discuss.python.org/t/function-level-indent-less-with-to-do-cleanup-when-function-exits/108958.md?page=2)
