with, менеджер контекста, python: что происходит простыми словами?

Начинающий кодировщик Python здесь имеет опыт работы с Java. Я до сих пор озадачен этим:

with open(...) as f:
    do_something(f)

даже после поиска в Google и прочтения некоторых ответов здесь (я просто не мог понять их).

Насколько я понимаю, существует такая вещь, как диспетчер контекста, который представляет собой своего рода оболочку, содержащую ссылку на создаваемый файл. Касательно

as f:

«как» выше похоже на «как» ниже

import numpy as np

Это просто псевдоним. «f» относится не к файлу, а к диспетчеру контекста. Менеджер контекста, используя шаблон декоратора, реализует все методы, которые делает открытый файл, так что я могу обращаться с ним как с файловым объектом (и получить доступ к файловому объекту, вызвав соответствующие методы, которые будут вызываться для файла). внутри контекстного менеджера). И, конечно же, файл закрывается, когда блок завершается (в этом весь смысл).

Возникает вопрос: возвращает ли вообще open() файл (или ссылку на файл) или менеджер контекста? Возвращает ли он менеджеры контекста в целом, и это то, что мы использовали все время, не зная об этом? Или он возвращает типы файлов, за исключением этого специального контекста, когда возвращает что-то другое, например менеджер контекста.

Это где-то рядом правильно? Кто-нибудь хочет уточнить?


person Cartesian Theater    schedule 15.08.2016    source источник
comment
Файлы являются менеджерами контекста.   -  person user2357112 supports Monica    schedule 15.08.2016
comment
with после открытия просто безопасно закрывает файл после записи.   -  person n1c9    schedule 15.08.2016
comment
Вы читали python.org/dev/peps/pep-0343?   -  person Wayne Werner    schedule 15.08.2016
comment
@WayneWerner Я посмотрел на это, но я слишком новичок, чтобы понять это.   -  person Cartesian Theater    schedule 15.08.2016
comment
Сорри за такой нубский вопрос. Я предполагал что-то, что вызвало путаницу: я не знал, что File может быть ContextManager просто путем реализации двух методов, без наследования от суперкласса или явной реализации интерфейса. Опять же, я новичок в этом языке.   -  person Cartesian Theater    schedule 15.08.2016
comment
@Detroitteatime: основной язык Python не имеет явных интерфейсов, как Java. Самое близкое, что приходит на ум, это абстрактные базовые классы, и для многих распространенных 'протоколы' в Python, действительно есть определенные ABC< /а>. Нет такой азбуки для менеджеров контекста. Большинство проверок интерфейса Python выполняются с помощью утиной печати; если методы есть, он должен реализовывать этот интерфейс.   -  person Martijn Pieters    schedule 15.08.2016


Ответы (3)


Файловые объекты сами являются менеджерами контекста, поскольку они имеют __enter__ и __exit__. with уведомляет объект file о входе в контекст и выходе из него (вызывая __enter__ и __exit__ соответственно), и именно так файловый объект «узнает» о закрытии файла. Здесь не задействован объект-оболочка; файловые объекты предоставляют эти два метода (в терминах Java можно сказать, что файловые объекты реализуют интерфейс менеджера контекста).

Обратите внимание, что as не псевдоним, как import module as altname; вместо этого возвращаемое значение contextmanager.__enter__() назначается цели. Метод fileobject.__enter__() возвращает self (то есть сам файловый объект), чтобы упростить использование синтаксиса:

with open(...) as fileobj:

Если бы fileobject.__enter__() этого не делал, а либо возвращал None, либо другой объект, вы не могли бы встроить вызов open(); чтобы сохранить ссылку на возвращенный файловый объект, вам нужно сначала присвоить результат open() переменной, прежде чем использовать его в качестве менеджера контекста:

fileobj = open(...)
with fileobj as something_enter_returned:
    fileobj.write()

or

fileobj = open(...)
with fileobj:  # no as, ignore whatever fileobj.__enter__() produced 
    fileobj.write()

Обратите внимание, что ничто не мешает вам использовать последний шаблон в вашем собственном коде; вам не нужно использовать здесь часть as target, если у вас уже есть другая ссылка на файловый объект, или вам просто не нужен дальнейший доступ к файловому объекту.

Однако другие менеджеры контекста могут возвращать что-то другое. Некоторые соединители базы данных возвращают курсор базы данных:

conn = database.connect(....)
with conn as cursor:
    cursor.execute(...)

и выход из контекста приводит к фиксации или откату транзакции (в зависимости от того, было ли исключение).

person Martijn Pieters    schedule 15.08.2016

Это самый простой контекстный менеджер, который вы можете создать:

class UselessContextManager(object):
    def __enter__(self):
        pass

    def __exit__(self, type, value, traceback):
        pass

with UselessContextManager() as nothing:
    print(nothing is None)

Если вы хотите получить представление о том, как на самом деле выглядит поток процесса, попробуйте следующее:

class PrintingContextManager(object):                                          
    def __init__(self, *args, **kwargs):                                       
        print('Initializing with args: {} and kwargs: {}'.format(args, kwargs))

    def __enter__(self):                                                       
        print('I am entering the context')                                     
        print('I am returning 42')                                             
        return 42                                                              

    def __exit__(self, type, value, traceback):                                
        print('And now I am exiting')                                          


print('Creating manager')                                                      
manager = PrintingContextManager()                                             
print('Entering with block')                                                   
with manager as fnord:                                                         
    print('Fnord is {}'.format(fnord))                                         
    print('End of context')                                                    
print('Out of context')                                                        

Выход:

Creating manager
Initializing with args: () and kwargs: {}
Entering with block
I am entering the context
I am returning 42
Fnord is 42
End of context
And now I am exiting
Out of context

Вы должны попробовать изменить код, чтобы распечатать type, value, traceback, а затем вызвать исключение внутри блока with.

Как видите, синтаксис with — это почти просто сокращение от:

thing = ContextManager()
try:
    stuff = thing.__enter__()
except Exception as e:
    stuff.__exit__(type(e), e.args[0], e.__traceback__)

Хотя на самом деле это немного по-другому

Вы можете видеть, что файлы всегда являются менеджерами контекста:

>>> f = open('/tmp/spanish_inquisition.txt', 'w')
>>> f.__enter__
<function TextIOWrapper.__enter__>
>>> f.__exit__
<function TextIOWrapper.__exit__>

Я не знал, что File может быть ContextManager просто путем реализации двух методов, без наследования от суперкласса или явной реализации интерфейса. Опять же, я новичок в этом языке.

В Python это явно реализует интерфейс. В Java вы должны указать, какого интерфейса вы хотите придерживаться. В Python вы просто делаете это. Нужен файлоподобный объект? Добавьте метод .read(). Возможно, .seek(), .open() и .close() в зависимости от того, что они ожидают. Но в Питоне...

it = DecoyDuck()
if it.walks_like_a_duck() and it.talks_like_a_duck() and it.quacks_like_a_duck():
    print('It must be a duck')
person Wayne Werner    schedule 15.08.2016

Контекстные менеджеры — довольно простые звери... Это просто классы, которые определяют два отдельных метода (__enter__ и __exit__). Все, что возвращается из __enter__, связывается в предложении as x оператора with при выполнении оператора with.

Вот действительно глупый пример:

>>> class CM(object):
...   def __enter__(self):
...     print('In __enter__')
...     return 'Hello world'
...   def __exit__(self, *args):
...     print('In __exit__')
... 
>>> with CM() as x:
...   print(x)
... 
In __enter__
Hello world
In __exit__

Вы часто будете видеть, как менеджеры контекста просто возвращают self из метода __enter__, но я написал приведенный выше пример, чтобы продемонстрировать, что вам необходимо этого делать. Также обратите внимание, что вам не нужно создавать диспетчер контекста в операторе with, вы можете создать его заранее:

cm = CM()
with cm as x:
    ...

Причина использования менеджеров контекста заключается в том, что при использовании в сочетании с оператором with python гарантирует, что будет вызван __exit__ (даже если внутри набора with произойдет исключение)1.

Объекты file реализуются с помощью API диспетчера контекста (у них есть четко определенные методы __enter__ и __exit__), поэтому объекты file являются диспетчерами контекста. При использовании с оператором with python гарантирует, что при выходе из пакета with файл будет закрыт.

1за исключением катастрофических сбоев системы -- например. если ваш компьютер взорвется...

person mgilson    schedule 15.08.2016
comment
Таким образом, любой класс, реализующий методы enter и exit, является менеджером контекста. Я запутался, потому что думаю о наследовании или реализации интерфейса, но не вижу ничего подобного для File. - person Cartesian Theater; 15.08.2016
comment
@Detroitteatime - я не уверен, что понимаю. На python2.5 -> python2.7, если вы наберете help(file), вы увидите там __enter__ и __exit__. Возможно, путаница в том, что open — это фабричный метод, который возвращает файловый объект? - person mgilson; 15.08.2016
comment
@Detroitteatime: в любом случае вы можете думать о менеджерах контекста как об интерфейсе; файловые объекты реализуют этот интерфейс. - person Martijn Pieters; 15.08.2016
comment
@MartijnPieters Я запутался, потому что ожидал увидеть что-то вроде этого docs.oracle.com/javase/7/docs/api/java/io/File.html где-то в документах, где File реализует интерфейс с именем ContextManager или расширяет класс с именем ContextManager. Теперь я знаю, что это не требуется для менеджеров файлов и контекста Python, и это все проясняет. Спасибо, я чувствую, что понял это сейчас. - person Cartesian Theater; 15.08.2016
comment
@Detroitteatime Я объясняю это в конце своего ответа, но это не требуется для ничего в Python — просто реализация определенных методов и атрибутов, которые что-то использует. - person Wayne Werner; 15.08.2016