Вопрос

I've been playing around with making my own context managers in Python. I'm seeing some strange behavior most likely due to my implementation.

I see the __exit__ code called before a statement in the 'with' context. For example, here is the code snippet:

with ProgressBar(10) as p:
  p.update(1)

and it's exception:

Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
AttributeError: 'NoneType' object has no attribute 'update'

I put debug in all the __enter__, __exit__, and update methods of my context manager. It looks like __exit__ is called before update(). This makes no sense so I must be missing something simple.

Here is my simple context manager class:

class ProgressBar(object):
    """Progress bar that normalizes progress to [0 - 100] scale"""

    def __init__(self, max_value):
        """Create progress bar with max_value"""

        self._current_value = 0.0
        self._completed_value = 100.0
        self._max_value = float(max_value)
        print 'init', max_value

    def __enter__(self):
        """Start of context manager, 'with' statement"""

        print 'enter'
        self._current_value = 0.0

    def __exit__(self, exc_type, exc_value, traceback):
        """Start of context manager, 'with' statement"""

        print 'exit'
        self._current_value = self._completed_value

        # Not handling any exceptions, so they'll be raised automatically
        # To ignore exceptions return True or inspect arguments to handle

        return False

    def update(self, value):
        """Update progress value"""

        print 'update'
        if value >= self._max_value:
            self._current_value = 100
        else:
            self._current_value = (value / self._max_value) * self._completed_value

        print '\r%s' % (self._current_value),
Это было полезно?

Решение

From the documentation:

object.__enter__(self)

Enter the runtime context related to this object. The with statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.

You're not returning anything from __enter__ (and thus you're returning None, as always). If you return self, you'll get

init 10
enter
update
10.0 exit

Другие советы

The with statement binds the return value of a context manager __enter__ method to the variable. In your case that means p is bound to None (your __enter__ doesn't have a return statement at all, so None is the default), an AttributeError is raised and the __exit__ method is called.

The solution is to return self from the __enter__ method:

def __enter__(self):
    """Start of context manager, 'with' statement"""

    print 'enter'
    self._current_value = 0.0
    return self
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top