How do I write a decorator that restores the cwd?
Asked Answered
A

5

44

How do I write a decorator that restores the current working directory to what it was before the decorated function was called? In other words, if I use the decorator on a function that does an os.chdir(), the cwd will not be changed after the function is called.

Attired answered 3/10, 2008 at 22:4 Comment(5)
I had written the code and then it turned out (after refactoring) that I didn't need it. I figured stackoverflow is a good place to archive it, and perhaps others can benefit.Attired
I think the "Jeopardy" comment is just supposed to mean the title should still be "how do I write a decorator for X?", not "here's useful decorator for X". I agree that a note in the question would save people wasting time on duplicate solutions while you're typing. But they might better yours...Chiropractic
... for instance codeape's point about exception-handling is an improvement on the questioner's proposed solution in this case, so it wasn't time wasted.Chiropractic
Well I'm glad I asked the question, because codeape improved upon my answer, and ΤΖΩΤΖΙΟΥ came up with a context manager which didn't occur to me.Attired
Context manager is a better choice than decorator. Modern answer: stdlib contextlib.chdir.Dub
G
36

The path.py module (which you really should use if dealing with paths in python scripts) has a context manager:

subdir = d / 'subdir' #subdir is a path object, in the path.py module
with subdir:
  # here current dir is subdir

#not anymore

(credits goes to this blog post from Roberto Alsina)

Gastrulation answered 24/12, 2012 at 9:38 Comment(4)
If path.py is now built-in, perhaps you should answer #3900261.Attired
unfortunately it isn't, but thanks I wasn't aware of the questionGastrulation
I guess I misinterpreted your answer. Did you mean that the context manager is built in to path.py? (I thought you meant path.py is now built in to Python.)Attired
Because Daryl Spitzer assumed that path.py is the same as pathlib, you may also assume that their context managers behave in same way, but they aren't: pathlib.Path doesn't touch CWD and instead acts as a context manager from open() ("closes" potentially open file descriptor).Bacteriolysis
P
57

The answer for a decorator has been given; it works at the function definition stage as requested.

With Python 2.5+, you also have an option to do that at the function call stage using a context manager:

from __future__ import with_statement # needed for 2.5 ≤ Python < 2.6
import contextlib, os

@contextlib.contextmanager
def remember_cwd():
    curdir= os.getcwd()
    try: yield
    finally: os.chdir(curdir)

which can be used if needed at the function call time as:

print "getcwd before:", os.getcwd()
with remember_cwd():
    walk_around_the_filesystem()
print "getcwd after:", os.getcwd()

It's a nice option to have.

EDIT: I added error handling as suggested by codeape. Since my answer has been voted up, it's fair to offer a complete answer, all other issues aside.

Phenobarbitone answered 3/10, 2008 at 22:19 Comment(3)
And it can be used to write the aforementioned decorator :)Pharyngitis
Does the error handling need an explicit try/finally? I thought the point of context managers was that MANAGER.__exit__ was always called. But then, I have never tried the decorator from contextlib, so I don't know what the issues are.Duct
No, it doesn't need an explicit try/finally, but you most probably want a try/except clause to handle failures.Phenobarbitone
G
36

The path.py module (which you really should use if dealing with paths in python scripts) has a context manager:

subdir = d / 'subdir' #subdir is a path object, in the path.py module
with subdir:
  # here current dir is subdir

#not anymore

(credits goes to this blog post from Roberto Alsina)

Gastrulation answered 24/12, 2012 at 9:38 Comment(4)
If path.py is now built-in, perhaps you should answer #3900261.Attired
unfortunately it isn't, but thanks I wasn't aware of the questionGastrulation
I guess I misinterpreted your answer. Did you mean that the context manager is built in to path.py? (I thought you meant path.py is now built in to Python.)Attired
Because Daryl Spitzer assumed that path.py is the same as pathlib, you may also assume that their context managers behave in same way, but they aren't: pathlib.Path doesn't touch CWD and instead acts as a context manager from open() ("closes" potentially open file descriptor).Bacteriolysis
S
28

The given answers fail to take into account that the wrapped function may raise an exception. In that case, the directory will never be restored. The code below adds exception handling to the previous answers.

as a decorator:

def preserve_cwd(function):
    @functools.wraps(function)
    def decorator(*args, **kwargs):
        cwd = os.getcwd()
        try:
            return function(*args, **kwargs)
        finally:
            os.chdir(cwd)
    return decorator

and as a context manager:

@contextlib.contextmanager
def remember_cwd():
    curdir = os.getcwd()
    try:
        yield
    finally:
        os.chdir(curdir)
Sandy answered 4/10, 2008 at 11:29 Comment(0)
I
24

You dont need to write it for you. With python 3.11, the developers have written it for you. Check out their code at github.com.

import contextlib
with contextlib.chdir('/path/to/cwd/to'):
    pass
Inflect answered 8/5, 2022 at 17:24 Comment(0)
A
3
def preserve_cwd(function):
   def decorator(*args, **kwargs):
      cwd = os.getcwd()
      result = function(*args, **kwargs)
      os.chdir(cwd)
      return result
   return decorator

Here's how it's used:

@preserve_cwd
def test():
  print 'was:',os.getcwd()
  os.chdir('/')
  print 'now:',os.getcwd()

>>> print os.getcwd()
/Users/dspitzer
>>> test()
was: /Users/dspitzer
now: /
>>> print os.getcwd()
/Users/dspitzer
Attired answered 3/10, 2008 at 22:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.