Difference between Context Managers and Decorators in Python

Question:

What is the main difference between the two? I have been studying Python and came across them. A decorator is essentially a function that wraps another function and you can do anything before and after a particular function executes.

def my_decorator(some_function):
    def wrapper(*args, **kwargs):
        print("Do something before the function is called")
        some_function(*args, **kwargs)
        print("Do something after the function is called")

    return wrapper

@my_decorator
def addition(a, b):
    result = a+b
    print("Addition of {} and {} is {}".format(a,b,result))

But after studying Context Manager, I couldn’t help but notice that it too has a enter and exit where you could do most similar operations.

from contextlib import contextmanager

@contextmanager
def open_file(path, mode):
    the_file = open(path, mode)
    yield the_file
    the_file.close()

files = []

for x in range(100000):
    with open_file('foo.txt', 'w') as infile:
        files.append(infile)

for f in files:
    if not f.closed:
        print('not closed')

Everything before yield is taken as part of the “enter” and everything after a part of “exit”.

Although both Context Managers and Decorators are syntactically different, their behaviors can be looked upon as similar. So what is the difference? What are the different scenarios when one should use either of them?

Asked By: Mithil Bhoras

||

Answers:

they are completely different concepts.

context managers are objects to be used with the python with keyword. It runs code when entering the block and exiting the block.

decorators are modifications to a function or class definition. It runs code that replaces the function as it is being defined.

@D
def Y(...):
    ...

is just another way of writing

def Y(...):
    ....
Y = D(Y)
Answered By: nosklo

They are completely separate concepts and should not be seen in the same light.

A decorator lets you augment or replace a function or a class when it is defined. This is far broader than just executing things before or after a function call. Sure, your specific decorator lets you do something just before and after a function call, provided no exception is raised, or you explicitly handle exceptions. But you could also use a decorator to add an attribute to the function object, or to update some kind of registry. Or to return something entirely different and ignore the original function. Or to produce a wrapper that manipulates the arguments passed in, or the return value of the original function. A context manager can’t do any of those things.

A context manager on the other hand lets you abstract away try: ... finally: constructs, in that no matter how the block exits, you get to execute some more code at the end of the block. Even if the block raises an exception, or uses return to exit a function, the context manager __exit__ method is still going to be called, regardless. A context manager can even suppress any exceptions raised in the block.

The two concepts are otherwise not related at all. Use decorators when you require to do something to or with functions or classes when they are defined. Use context managers when you want to clean up or take other actions after a block ends.

Answered By: Martijn Pieters

Good thinking, indeed the concepts have many similiarities, though there are important differences, so it is safer to think of them as totally different concepts

Answered By: Serge

Any context manager created with contextlib.contextmanager is also a decorator, as described here: https://docs.python.org/3/library/contextlib.html#using-a-context-manager-as-a-function-decorator

Context managers can be used to wrap code with setup and teardown steps. Decorators are a more general construct with allow us to modify functions many ways, including by wrapping them with setup/teardown logic. So it seems pretty natural to ask: why can’t we use a context manager as a decorator?

We can, and in fact contextlib has already done it for you. If we write a context manager like so:

from contextlib import contextmanager

@contextmanager
def my_context():
    print("setup")
    yield
    print("teardown")

We can use it as a context manager in a with block or we can use it as a decorator:

def foo():
    with my_context():
        print("foo ran")

@my_context()
def bar():
    print("bar ran")
>>> foo()
setup
foo ran
teardown
>>> bar()
setup
bar ran
teardown

Which should you use?

Use a with block when your enclosed code needs access to the object returned by the context manager, e.g. file handling:

with open("my_file.txt") as file:
    file.read()  # needs access to the file object

Use as a decorator when an entire function needs to be wrapped in a context and doesn’t need any context variables:

@contextmanager
def suppress_all_exceptions():  
    try: yield
    except: pass

@suppress_all_exceptions()
def div_by_zero():
    print("hi")
    x = 1 / 0  # exception suppressed

Note: the same functionality can also be achieved by subclassing contextlib.ContextDecorator:

class MyContext(contextlib.ContextDecorator):
    def __enter__(): ...
    def __exit__(*errs): ... 
Answered By: ajanss