Pergunta

A combinação de co-rotinas e aquisição de recursos parece que poderia ter algumas consequências não intencionais (ou unintuitive).

A questão básica é se ou não algo como isso funciona:

def coroutine():
    with open(path, 'r') as fh:
        for line in fh:
            yield line

O que ele faz. (Você pode testá-lo!)

A preocupação mais profunda é que with é suposto ser algo uma alternativa para finally, onde você garantir que um recurso é liberado no final do bloco. Coroutines pode suspender e execução currículo de em o bloco with, então como é o conflito resolvido?

Por exemplo, se você abrir um arquivo com leitura / gravação, tanto dentro como fora de uma co-rotina, enquanto a co-rotina ainda não retornou:

def coroutine():
    with open('test.txt', 'rw+') as fh:
        for line in fh:
            yield line

a = coroutine()
assert a.next() # Open the filehandle inside the coroutine first.
with open('test.txt', 'rw+') as fh: # Then open it outside.
    for line in fh:
        print 'Outside coroutine: %r' % repr(line)
assert a.next() # Can we still use it?

Atualização

Eu estava indo para a contenção identificador de arquivo bloqueado para gravação no exemplo anterior, mas desde que a maioria dos sistemas operacionais alocar filehandles por processo não haverá disputa lá. (. Kudos para @Miles por apontar o exemplo não faz muito sentido) Aqui está o meu exemplo revisto, o que mostra uma condição de impasse real:

import threading

lock = threading.Lock()

def coroutine():
    with lock:
        yield 'spam'
        yield 'eggs'

generator = coroutine()
assert generator.next()
with lock: # Deadlock!
    print 'Outside the coroutine got the lock'
assert generator.next()
Foi útil?

Solução

Eu realmente não entendo o que conflito que você está perguntando sobre, nem o problema com o exemplo:. É bom ter dois coexistindo, alças independentes para o mesmo arquivo

Uma coisa que eu não sabia que eu aprendi em resposta à sua pergunta se que há um novo método close () em geradores:

close() levanta uma nova exceção GeneratorExit dentro do gerador para terminar a iteração. Ao receber essa exceção, o código deve ou aumento GeneratorExit ou StopIteration do gerador.

close() é chamado quando um gerador é coleta de lixo, então isso significa que o código do gerador recebe uma última chance de correr antes que o gerador é destruído. Este último meio chance de que declarações try...finally em geradores podem agora ser garantido para trabalhar; a cláusula finally agora sempre tem a chance de correr. Este parece ser um pouco menor de trivia língua, mas usando geradores e try...finally é realmente necessário para implementar a declaração with descrito por PEP 343.

http://docs.python.org/ whatsnew / 2.5.html # pep-342-new-gerador, possui

Assim que lida com a situação em que uma declaração with é usado em um gerador, mas ele produz no meio, mas nunca retorna-o método __exit__ do gerente de contexto será chamado quando o gerador é de lixo coletado.


Editar :

Com relação à questão identificador de arquivo: Eu às vezes se esquecem que existem plataformas que não são POSIX-like. :)

Quanto fechaduras ir, eu acho Rafal Dowgird bate a cabeça no prego quando ele diz "Você apenas tem que estar ciente de que o gerador é como qualquer outro objeto que contém recursos." Eu não acho que a declaração with é realmente relevante aqui, uma vez que esta função sofre dos mesmos problemas de impasse:

def coroutine():
    lock.acquire()
    yield 'spam'
    yield 'eggs'
    lock.release()

generator = coroutine()
generator.next()
lock.acquire() # whoops!

Outras dicas

Eu não acho que há um verdadeiro conflito. Você apenas tem que estar ciente de que o gerador é como qualquer outro objeto que contém recursos, por isso é de responsabilidade do criador para se certificar de que está devidamente finalizado (e conflitos / impasse com os recursos mantidos pelo objeto evitar). A única (menor) problema que eu vejo aqui é que os geradores não implementar o protocolo de gerenciamento de contexto (pelo menos a partir de Python 2.5), então você não pode simplesmente:

with coroutine() as cr:
  doSomething(cr)

mas em vez disso tem que:

cr = coroutine()
try:
  doSomething(cr)
finally:
  cr.close()

O coletor de lixo faz o close() de qualquer maneira, mas é má prática de confiar em que, para liberar recursos.

Porque yield pode executar código arbitrário, eu seria muito cauteloso em manter um bloqueio sobre uma declaração de rendimento. Você pode obter um efeito semelhante em muitas outras maneiras, no entanto, incluindo chamar um método ou funções que podem ser ter sido substituído ou modificado.

Geradores, no entanto, são sempre (quase sempre) "fechada", ou com uma chamada close() explícito, ou apenas por estar coleta de lixo. Fechando um gerador gera uma excepção GeneratorExit no interior do gerador e, portanto, é executado finalmente cláusulas, com a limpeza de instrução, etc. É possível detectar a excepção, mas tem de jogar ou sair da função (isto é, accionar uma excepção StopIteration), em vez de rendimento. É uma prática provavelmente pobre para contar com o coletor de lixo para fechar o gerador em casos como você escreveu, porque isso poderia acontecer mais tarde do que você pode querer, e se alguém chama sys._exit (), então a sua limpeza não poderia acontecer em tudo .

Isso seria como eu esperava que as coisas funcionem. Sim, o bloco não vai liberar seus recursos até que ela seja concluída, então, nesse sentido o recurso escapou-lo de nidificação lexical. No entanto, mas isso não é diferente de fazer uma chamada de função que tentou usar o mesmo recurso dentro de um com bloco - nada ajuda no caso em que o bloco tem não terminado ainda, para qualquer que seja razão. Não é realmente nada específico para geradores.

Uma coisa que pode valer a pena se preocupar com, porém, é o comportamento se o gerador é não retomado. Eu teria esperado que o bloco with a agir como um bloco finally e chamar a parte __exit__ no termo, mas que não parece ser o caso.

Para uma TLDR, olhar para ele dessa forma:

with Context():
    yield 1
    pass  # explicitly do nothing *after* yield
# exit context after explicitly doing nothing

As extremidades Context após pass é feito (ou seja, nada), executa pass após yield é feito (ou seja, continua a execução). Assim, as extremidades with após controle é reiniciada às yield.

TLDR:. Vestígios de contexto with A detidas quando o controle yield lançamentos


Na verdade, existem apenas duas regras que são relevantes aqui:

  1. Quando é que with lançar seu recurso?

    Fá-lo uma vez e directamente após seu bloco é feito. A primeira significa que não libera durante a yield, como isso poderia acontecer várias vezes. O meio mais tarde ele faz versão após yield foi concluída.

  2. Quando for concluída yield?

    Pense yield como uma chamada inversa: o controle é passado para um chamador, não para baixo a um chamado de um. Da mesma forma, completa yield quando o controle é passado de volta para ele, assim como quando um controle chamada retorna.

Note que tanto with e yield estão funcionando como esperado aqui! O ponto de um with lock é proteger um recurso e permanece protegido durante um yield. Você sempre pode liberar explicitamente essa proteção:

def safe_generator():
  while True:
    with lock():
      # keep lock for critical operation
      result = protected_operation()
    # release lock before releasing control
    yield result
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top