Para que é projetada a declaração “with” do python?

419

Encontrei a withinstrução Python pela primeira vez hoje. Estou usando o Python de ânimo leve há vários meses e nem sabia de sua existência! Dado seu status um tanto obscuro, pensei que valeria a pena perguntar:

  1. Para que a withinstrução Python foi projetada para ser usada?
  2. para que você usa isso?
  3. Existem algumas dicas que eu preciso estar ciente ou anti-padrões comuns associados ao seu uso? Algum caso em que é melhor usar do try..finallyque with?
  4. Por que não é usado mais amplamente?
  5. Quais classes de biblioteca padrão são compatíveis com ele?
fmark
fonte
5
Apenas para constar, aqui estáwith na documentação do Python 3.
Alexey19 /
vindo de um plano de fundo Java, me ajuda a lembrá-lo como a "tentativa com recursos" correspondente em Java, mesmo que isso possa não estar totalmente correto.
vefthym 13/05/19

Respostas:

399
  1. Acredito que isso já tenha sido respondido por outros usuários antes de mim, portanto, apenas o adiciono por questões de integridade: a withinstrução simplifica o tratamento de exceções, encapsulando tarefas comuns de preparação e limpeza nos chamados gerenciadores de contexto . Mais detalhes podem ser encontrados no PEP 343 . Por exemplo, a openinstrução é um gerenciador de contexto em si, que permite abrir um arquivo, mantê-lo aberto enquanto a execução estiver no contexto da withinstrução em que você o usou e fechá-lo assim que sair do contexto, não importa se você o deixou devido a uma exceção ou durante o fluxo de controle regular. A withinstrução pode, portanto, ser usada de maneiras semelhantes ao padrão RAII em C ++: algum recurso é adquirido pelowithe liberada quando você sai do withcontexto.

  2. Alguns exemplos são: abrir arquivos usando with open(filename) as fp:, adquirindo bloqueios usando with lock:(onde locké uma instância de threading.Lock). Você também pode construir seus próprios gerenciadores de contexto usando o contextmanagerdecorador de contextlib. Por exemplo, costumo usar isso quando preciso alterar temporariamente o diretório atual e depois retornar para onde estava:

    from contextlib import contextmanager
    import os
    
    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)
    
    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory

    Aqui está outro exemplo que redireciona temporariamente sys.stdin, sys.stdoute sys.stderrpara algum outro identificador de arquivo e restaura-los mais tarde:

    from contextlib import contextmanager
    import sys
    
    @contextmanager
    def redirected(**kwds):
        stream_names = ["stdin", "stdout", "stderr"]
        old_streams = {}
        try:
            for sname in stream_names:
                stream = kwds.get(sname, None)
                if stream is not None and stream != getattr(sys, sname):
                    old_streams[sname] = getattr(sys, sname)
                    setattr(sys, sname, stream)
            yield
        finally:
            for sname, stream in old_streams.iteritems():
                setattr(sys, sname, stream)
    
    with redirected(stdout=open("/tmp/log.txt", "w")):
         # these print statements will go to /tmp/log.txt
         print "Test entry 1"
         print "Test entry 2"
    # back to the normal stdout
    print "Back to normal stdout again"

    E, finalmente, outro exemplo que cria uma pasta temporária e a limpa ao sair do contexto:

    from tempfile import mkdtemp
    from shutil import rmtree
    
    @contextmanager
    def temporary_dir(*args, **kwds):
        name = mkdtemp(*args, **kwds)
        try:
            yield name
        finally:
            shutil.rmtree(name)
    
    with temporary_dir() as dirname:
        # do whatever you want
Tamás
fonte
20
Obrigado por adicionar a comparação ao RAII. Como programador de C ++, me disse tudo o que eu precisava saber.
Fred Thomsen
Ok, então deixe-me esclarecer isso. Você está dizendo que a withinstrução foi criada para preencher uma variável com dados até que as instruções contidas nela estejam completas e liberar a variável?
Musixauce3000
Porque eu usei para abrir um script py. with open('myScript.py', 'r') as f: pass. Eu esperava ser capaz de chamar a variável fpara ver o conteúdo do texto do documento, como este é o que apareceria se o documento foram atribuídos a fvia regular opendeclaração: f = open('myScript.py').read(). Mas em vez disso eu tenho o seguinte: <_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>. O que isso significa?
Musixuce3000 #
3
@ Musixauce3000 - o uso withnão remove a necessidade readdo arquivo real. As withchamadas open- não sabe o que você precisa fazer com ela - você pode querer procurar, por exemplo.
Tony Suffolk 66
@ Musixauce3000 A withdeclaração pode preencher uma variável com dados ou fazer alguma outra alteração no ambiente até que as instruções contidas nela estejam completas e, em seguida, faça qualquer tipo de limpeza necessária. Os tipos de limpeza que podem ser feitos são como fechar um arquivo aberto ou, como o @Tamas fez neste exemplo, alterar os diretórios de volta para onde você estava antes, etc. Como o Python tem coleta de lixo, liberar uma variável não é importante. caso de uso. withé geralmente usado para outros tipos de limpeza.
22419 Bob Steinke
89

Eu sugeriria duas palestras interessantes:

  • PEP 343 A declaração "with"
  • Effbot Entendendo a declaração "with" do Python

1. A withinstrução é usada para agrupar a execução de um bloco com métodos definidos por um gerenciador de contexto. Isso permite que try...except...finallypadrões de uso comuns sejam encapsulados para reutilização conveniente.

2. Você pode fazer algo como:

with open("foo.txt") as foo_file:
    data = foo_file.read()

OU

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()

OR (Python 3.1)

with open('data') as input_file, open('result', 'w') as output_file:
   for line in input_file:
     output_file.write(parse(line))

OU

lock = threading.Lock()
with lock:
    # Critical section of code

3. Não vejo nenhum antipadrão aqui.
Citando o Mergulho no Python :

try..finally é bom. com é melhor.

4. Acho que está relacionado ao hábito dos programadores de usar try..catch..finallydeclarações de outros idiomas.

systempuntoout
fonte
4
Ele realmente se destaca quando você está lidando com objetos de sincronização de segmentação. Relativamente raro em Python, mas quando você precisa deles, você realmente precisa with.
detly
1
O diveintopython.org está inoperante (permanentemente?). Espelhado na diveintopython.net
aconchega
Exemplo de uma resposta boa, arquivo aberto é um excelente exemplo que mostra por trás das cenas de abertura, io, fechando as operações de arquivo estão escondidos limpa com um nome de referência costume
irritado 84
40

A withinstrução Python é o suporte à Resource Acquisition Is Initializationlinguagem interna do idioma comumente usado em C ++. Destina-se a permitir a aquisição e liberação seguras de recursos do sistema operacional.

A withinstrução cria recursos dentro de um escopo / bloco. Você escreve seu código usando os recursos dentro do bloco. Quando o bloco sai, os recursos são liberados corretamente, independentemente do resultado do código no bloco (ou seja, se o bloco sai normalmente ou devido a uma exceção).

Muitos recursos da biblioteca Python que obedecem ao protocolo exigido pela withinstrução e, portanto, podem ser usados ​​com ela imediatamente. No entanto, qualquer pessoa pode criar recursos que possam ser usados ​​em uma declaração with, implementando o protocolo bem documentado: PEP 0343

Use-o sempre que você adquirir recursos em seu aplicativo que devam ser explicitamente abandonados, como arquivos, conexões de rede, bloqueios e similares.

Tendayi Mawushe
fonte
27

Novamente, para completar, adicionarei meu caso de uso mais útil para withinstruções.

Faço muita computação científica e, para algumas atividades, preciso da Decimalbiblioteca para cálculos de precisão arbitrários. Alguma parte do meu código eu preciso de alta precisão e para a maioria das outras partes eu preciso de menos precisão.

Defino minha precisão padrão como um número baixo e, em seguida, uso withpara obter uma resposta mais precisa para algumas seções:

from decimal import localcontext

with localcontext() as ctx:
    ctx.prec = 42   # Perform a high precision calculation
    s = calculate_something()
s = +s  # Round the final result back to the default precision

Eu uso muito isso com o Teste Hipergeométrico, que requer a divisão de grandes números resultantes de fatores fatoriais. Ao fazer cálculos de escala genômica, você deve ter cuidado com os erros de arredondamento e transbordamento.

JudoWill
fonte
26

Um exemplo de um antipadrão pode ser o uso de withum loop interno, quando seria mais eficiente ter o withexterior do loop

por exemplo

for row in lines:
    with open("outfile","a") as f:
        f.write(row)

vs

with open("outfile","a") as f:
    for row in lines:
        f.write(row)

A primeira maneira é abrir e fechar o arquivo para cada um deles, o rowque pode causar problemas de desempenho em comparação com a segunda maneira com abrir e fechar o arquivo apenas uma vez.

John La Rooy
fonte
10

Veja PEP 343 - A declaração 'with' , há uma seção de exemplo no final.

... nova declaração "with" para a linguagem Python, para possibilitar o uso padrão de declarações try / finally.

stefanB
fonte
5

1, 2 e 3, sendo razoavelmente bem cobertos:

4: é relativamente novo, disponível apenas em python2.6 + (ou python2.5 usando from __future__ import with_statement)

cobbal
fonte
4

A instrução with funciona com os chamados gerenciadores de contexto:

http://docs.python.org/release/2.5.2/lib/typecontextmanager.html

A idéia é simplificar o tratamento de exceções, fazendo a limpeza necessária depois de sair do bloco 'with'. Alguns dos python internos já funcionam como gerenciadores de contexto.

zefciu
fonte
3

Outro exemplo de suporte pronto para uso, e um que pode ser um pouco desconcertante no início quando você está acostumado com o open()comportamento interno, são connectionobjetos de módulos populares de banco de dados, como:

Os connectionobjetos são gerenciadores de contexto e, como tal, podem ser usados ​​imediatamente em a with-statement, no entanto, ao usar a nota acima, observe que:

Quando o with-blocktérmino, com uma exceção ou sem, a conexão não é fechada . No caso de a with-blockexceção terminar, a transação é revertida, caso contrário, a transação é confirmada.

Isso significa que o programador deve cuidar de fechar a conexão, mas permite adquirir uma conexão e usá-la em múltiplos with-statements, conforme mostrado nos documentos do psycopg2 :

conn = psycopg2.connect(DSN)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL1)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL2)

conn.close()

No exemplo acima, você observará que os cursorobjetos psycopg2também são gerenciadores de contexto. A partir da documentação relevante sobre o comportamento:

Quando um cursorsai, with-blockele é fechado, liberando qualquer recurso eventualmente associado a ele. O estado da transação não é afetado.

bgse
fonte
3

No python, geralmente a instrução " with " é usada para abrir um arquivo, processar os dados presentes no arquivo e também para fechar o arquivo sem chamar um método close (). A declaração "with" simplifica o tratamento de exceções, fornecendo atividades de limpeza.

Forma geral de com:

with open(“file name”, mode”) as file-var:
    processing statements

note: não é necessário fechar o arquivo chamando close () no arquivo-var.close ()

Tushar.PUCSD
fonte