Traceback: AttributeError: экземпляр addinfourl не имеет атрибута '__exit__'

from urllib import urlopen
with urlopen('https://www.python.org') as story:
    story_words = []
    for line in story:
        line_words = line.split()
        for words in line_words:
            story_words.append(word)

Сообщение об ошибке:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: addinfourl instance has no attribute '__exit__'

Я не понимаю, что неправильно с приведенным выше кодом и как его решить?

Информация о системе: python 2.7 в виртуальном ящике ubuntu oracle.

Ответ 1

Эта ошибка вызвана этой строкой:

with urlopen('https://www.python.org') as story:

Вы не можете использовать какой-либо случайный объект в выражении with...as.

Есть два способа это исправить:

Решение 1: Используйте contextlib.closing:

from contextlib import closing

with closing(urlopen('https://www.python.org')) as story:
    ...

Решение 2: Не используйте оператор with...as; вместо этого присвойте значение переменной:

story = urlopen('https://www.python.org')
...

Почему это происходит?

Вы не можете использовать любой случайный объект в выражении with ... as.

Будут работать только те объекты, у которых есть два магических метода: __enter__ и __exit__. В совокупности эти методы называются "диспетчер контекста". Вводное руководство по этому вопросу можно найти ниже.

AttributeError был вызван, потому что для urlopen не реализован менеджер контекста (т.е. для него не определены методы __enter__ и __exit__).

Это оставляет вам два варианта:

  1. либо не используйте оператор with...as.
  2. или используйте contextlib.closing (спасибо @vaultah, который предоставил это решение в комментарии ниже). Он автоматически реализует диспетчер контекста для любого объекта, что позволяет вам использовать оператор with...as.

(Примечание: в Python 3 у urlopen есть менеджер контекста, и поэтому его можно использовать в операторе with...as.)


Учебник. Как реализовать менеджер контекста?

Чтобы заставить объект работать в операторе with...as, сначала необходимо реализовать менеджер контекста для этого объекта. Проще говоря, вам нужно определить методы __enter__ и __exit__ для этого объекта/класса.

Прочитайте эти документы по менеджерам контекста.

Пример:

>>> class Person(object):
        """To implement context manager, just create two methods 
           __enter__ and __exit__.
        """

        def __init__(self, name):
            self.name = name

        def __enter__(self):
            # The value returned by this method is 
            # assigned to the variable after ''as''
            return self

        def __exit__(self, exc_type, exc_value, exc_traceback ):
            # returns either True or False
            # Don't raise any exceptions in this method
            return True


>>> with Person("John Doe") as p:
        print p.name

>>> "John Doe" #success

Ответ 2

Вы можете попробовать следующее в Python 2.7:

from urllib import urlopen
story = urlopen('https://www.python.org')
story_words = []
for line in story:
    line_words = line.split()
    for words in line_words:
        story_words.append(words)