Decorator factory parameter is a static value and not a variable's value

Viewed 66

So I have

# my decorator factory
def execute_in(directory):  # <-- I want this to be a variable's value which can change
    def decorator(function):
        def wrapper(*args, **kwargs):
            os.chdir(directory)
            print(directory)  # currently is printing None which is my problem
            value = function(*args, **kwargs)
            os.chdir(home_dir)
            return value
        return wrapper
    return decorator

and

# a function that runs after assigning General.archive_dir a value
@execute_in(General.archive_dir)
def get_data():
  print(General.archive_dir)  # will print the correct directory name
  with open('data.csv', 'r') as f:
      rows = [row for row in csv.reader(f, delimiter=',')]
  return rows

My problem is that the decorator factory is using the value of the variable General.archive_dir instantiated at program start when its value is None. I want it to use the value of General.archive_dir at the time the decorated function is called. How can I do this?

I apologize if this question is unclear. If you can, please let me know how I can clarify it if needed.

2 Answers

One solution is calling @execute_in with a lambda.

directory inside wrapper would become a function that, when called, returns the current value.

archive_dir = None

# decorator factory
def execute_in(directory_path_getter):
    def decorator(function):
        def wrapper(*args, **kwargs):
            print('from wrapper:', directory_path_getter())  # Notice the function call
            value = function(*args, **kwargs)
            return value
        return wrapper
    return decorator

@execute_in(lambda: archive_dir)
def get_data():
    ...

archive_dir = 'some directory'

print(get_data())

Prints:

from wrapper: some directory
from get_data: some directory
['some data']

If a decorator isn't strictly required, a context manager can also fulfill the task of temporarily changing directories.

import os
from contextlib import contextmanager

@contextmanager
def execute_in(directory):
    orig_dir = os.getcwd()
    os.chdir(directory)

    try:
        yield
    finally:
        os.chdir(orig_dir)

Using a context manager would allow for changing directories many times in one method, and can be nested.

settings = {
    'archive_dir': './dir'
}

def get_data():
    print(os.getcwd())

    with execute_in(settings['archive_dir']):
        print('    ' + os.getcwd())

        with execute_in('bin'):
            print('        ' + os.getcwd())

        print('    ' + os.getcwd())

    print(os.getcwd())

And when we run it

>>> get_data()
/home/they4kman/.PyCharm2019.2/config/scratches
    /home/they4kman/.PyCharm2019.2/config/scratches/dir
        /home/they4kman/.PyCharm2019.2/config/scratches/dir/bin
    /home/they4kman/.PyCharm2019.2/config/scratches/dir
/home/they4kman/.PyCharm2019.2/config/scratches
Related