Simplicity
Simplicity

Reputation: 48986

Error with context manager definition

I have the following Python script:

from contextlib import contextmanager

@contextmanager
def my_content_manager():
    self.entrance = True

    try:
        yield
    except Exception as ex:
        self.entrance = False

with my_content_manager() as cm:
    print (cm.entrance)

print (cm.entrance)

When I tried to run the script, I got the following:

Traceback (most recent call last):
  File "test.py", line 12, in <module>
    with my_content_manager() as cm:
  File "C:\Users\abc\AppData\Local\Programs\Python\Python36\lib\contextlib.py", line 82, in __enter__
    return next(self.gen)
  File "test.py", line 5, in my_content_manager
    self.entrance = True
NameError: name 'self' is not defined

Why is that? How can I solve this error?

Thanks.

Upvotes: 2

Views: 6775

Answers (2)

rkersh
rkersh

Reputation: 4465

In general, using a class is probably a better way to go (as the other answer suggests). However, I remembered that function objects can have attributes. I came across this related question, which lead to the following trick (which does seem like an abuse of the feature):

from contextlib import contextmanager

@contextmanager
def my_content_manager():
    my_content_manager.entrance = True
    try:
        yield my_content_manager.entrance
    finally:
        my_content_manager.entrance = False
my_content_manager.entrance = False

with my_content_manager() as cm:
    print(my_content_manager.entrance)
    # Or, print(cm)

print(my_content_manager.entrance)

There are probably pitfalls to this technique. I'm simply posting it as a curiosity.

Upvotes: 3

Alex Hall
Alex Hall

Reputation: 36063

The error NameError: name 'self' is not defined is pretty self-explanatory, no pun intended. self is just a name used by convention in methods of classes. You haven't defined self anywhere so Python doesn't know what to do. Moreover you've defined a function, not a method in a class, so this isn't close to working. Use a class to define your context manager like so:

class MyContextManager(object):
    def __init__(self):
        self.entrance = True

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_val:
            self.entrance = False
        # signal that the exception was handled and the program should continue
        return True


with MyContextManager() as cm:
    print (cm.entrance)
    raise Exception()

print (cm.entrance)

EDIT: if, as requested in a comment, you only want to print a value and not store it somewhere:

@contextmanager
def my_content_manager():
    try:
        yield
    except Exception as ex:
        print(False)
    else:
        print(True)

Upvotes: 2

Related Questions