Várias variáveis ​​em uma declaração 'with'?

391

É possível declarar mais de uma variável usando uma withinstrução em Python?

Algo como:

from __future__ import with_statement

with open("out.txt","wt"), open("in.txt") as file_out, file_in:
    for line in file_in:
        file_out.write(line)

... ou está limpando dois recursos ao mesmo tempo, o problema?

baiacu
fonte
Talvez assim: com [expr1, expr2] como f: e use f [0] ef [1].
21139 jbasko
Teria sido bom, porque não há necessidade de importação algo .... mas ele não funciona AttributeError: 'list' objeto não tem nenhum atributo ' saída '
baiacu
Se python só tinha encerramentos, você não precisa o com a afirmação
BT
Você não precisa usar uma declaração with, certo? Você pode simplesmente definir file_out e file_in como None e, em seguida, faça uma tentativa / exceto / finalmente, onde você os abre e processa na tentativa e, finalmente, fechá-los se eles não forem None. Não é necessário nenhum recuo duplo para isso.
M Katz
11
Muitas dessas respostas não tratam da necessidade de mais de duas com declarações. Teoricamente, pode haver aplicativos que precisam abrir dezenas de contextos, o aninhamento se desfaz muito rapidamente, caso sejam impostas limitações de comprimento de linha.
precisa saber é o seguinte

Respostas:

667

É possível no Python 3 desde a v3.1 e Python 2.7 . A nova withsintaxe suporta vários gerenciadores de contexto:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

Diferentemente do contextlib.nested, isso garante que ae bterão seus __exit__()chamados mesmo C()que o __enter__()método suscite uma exceção.

Você também pode usar variáveis ​​anteriores em definições posteriores (h / t Ahmad abaixo):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)
Rafał Dowgird
fonte
11
é possível definir campos iguais a algo dentro da instrução como na with open('./file') as arg.x = file:?
Charlie Parker
13
Além disso, é possível: com A () como um, B (a), como b, C (a, b), c:
Ahmad Yoosofan
classe teste2: x = 1; t2 = test2 () com aberto ('f2.txt') como t2.x: para l1 em t2.x.readlines (): print (l1); # Charlie Parker # testado em python 3.6
Ahmad Yoosofan
11
observe, asé opcional.
Sławomir Lenart
para esclarecer o que @ SławomirLenart está dizendo: asé necessário se você precisar do objeto aou b, mas o todo as aou as bnão for necessário
Ciprian Tomoiagă
56

contextlib.nested suporta isso:

import contextlib

with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):

   ...

Atualização:
Para citar a documentação, referente a contextlib.nested:

Descontinuado desde a versão 2.7 : A declaração with agora suporta essa funcionalidade diretamente (sem as peculiaridades propensas a erros confusas).

Veja a resposta de Rafał Dowgird para mais informações.

Alex Martelli
fonte
34
Lamento dizer isso, mas acho que o nestedgerenciador de contexto é um erro e nunca deve ser usado. Neste exemplo, se a abertura do segundo arquivo gerar uma exceção, o primeiro arquivo não será fechado, destruindo totalmente o objetivo de usar gerenciadores de contexto.
Rafał Dowgird
Por que você diz isso? A documentação diz que o uso de nested é equivalente a aninhados 'com de
James Hopkin
@ Rafal: uma olhada no manual parece indicar que o python aninha adequadamente as instruções with. O verdadeiro problema é se o segundo arquivo lança uma exceção ao fechar.
Unknown
10
@ James: Não, o código equivalente nos documentos em docs.python.org/library/contextlib.html#contextlib.nested difere dos withblocos aninhados padrão . Os gerentes são criados em ordem antes de inserir os blocos with: m1, m2, m3 = A (), B (), C () Se B () ou C () falharem com exceção, sua única esperança de finalizar corretamente A ( ) é o coletor de lixo.
Rafał Dowgird 22/05/09
8
Descontinuado desde a versão 2.7 . Nota: A declaração with agora suporta essa funcionalidade diretamente (sem as peculiaridades propensas a erros confusos).
Miku
36

Observe que, se você dividir as variáveis ​​em linhas, deverá usar barras invertidas para quebrar as novas linhas.

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

Parênteses não funcionam, pois o Python cria uma tupla.

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

Como as tuplas não possuem um __enter__atributo, você recebe um erro (não descritivo e não identifica o tipo de classe):

AttributeError: __enter__

Se você tentar usar asentre parênteses, o Python detectará o erro no momento da análise:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)

SyntaxError: sintaxe inválida

https://bugs.python.org/issue12782 parece estar relacionado a esse problema.

nyanpasu64
fonte
16

Eu acho que você quer fazer isso:

from __future__ import with_statement

with open("out.txt","wt") as file_out:
    with open("in.txt") as file_in:
        for line in file_in:
            file_out.write(line)
Andrew Hare
fonte
5
Isso é como eu atualmente fazê-lo, mas, em seguida, o assentamento é duas vezes tão profundo como eu quero (média) que seja ...
baiacu
Eu acho que essa é a abordagem mais limpa - qualquer outra abordagem será mais difícil de ler. A resposta de Alex Martelli parece estar mais próxima do que você deseja, mas é muito menos legível. Por que aninhar tal preocupação?
Andrew Hare
7
É certo que não é grande coisa, mas, por "importar isso" (também conhecido como "Zen do Python"), "flat é melhor que aninhado" - é por isso que adicionamos contextlib.nested à biblioteca padrão. BTW, 3.1 pode ter uma nova sintaxe "com A () como a, B () como b:" (o patch está inserido, nenhum pronunciamento BDFL sobre o assunto até agora) para um suporte mais direto (tão claramente a solução da biblioteca não é ' considerado perfeito ... mas evitar o aninhamento indesejado é definitivamente um objetivo amplamente compartilhado entre os principais desenvolvedores de Python).
Alex Martelli
2
@ Alex: Muito verdade, mas também devemos considerar que "Legibilidade conta".
Andrew Hare
4
@ Andrew: Eu acho que um nível de indentação expressa melhor a lógica pretendida do programa, que é "atomicamente" criar duas variáveis ​​e limpá-las mais tarde, juntas (eu percebo que isso não é realmente o que acontece). Acho que a questão exceção é um disjuntor de negócio embora
baiacu
12

Desde o Python 3.3, você pode usar a classe ExitStackdo contextlibmódulo.

Ele pode gerenciar um número dinâmico de objetos sensíveis ao contexto, o que significa que será especialmente útil se você não souber quantos arquivos manipulará.

O caso de uso canônico mencionado na documentação está gerenciando um número dinâmico de arquivos.

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception

Aqui está um exemplo genérico:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(stack._exit_callbacks)
    nums = [stack.enter_context(x) for x in xs]
    print(stack._exit_callbacks)
print(stack._exit_callbacks)
print(nums)

Resultado:

deque([])
enter X1
enter X2
enter X3
deque([<function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86158>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f861e0>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86268>])
exit X3
exit X2
exit X1
deque([])
[1, 2, 3]
timgeb
fonte
0

No Python 3.1+, você pode especificar várias expressões de contexto, e elas serão processadas como se várias withinstruções estivessem aninhadas:

with A() as a, B() as b:
    suite

é equivalente a

with A() as a:
    with B() as b:
        suite

Isso também significa que você pode usar o alias da primeira expressão na segunda (útil ao trabalhar com conexões / cursores db):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
Eugene Yarmash
fonte