web-dev-qa-db-fra.com

blocks - envoie une entrée au pipeline de sous-processus python

Je teste les pipelines de sous-processus avec python. Je suis conscient que je peux faire ce que les programmes ci-dessous font directement en python, mais ce n'est pas le but. Je veux juste tester le pipeline pour que je sache l'utiliser.

Mon système est Linux Ubuntu 9.04 avec Python 2.6 par défaut.

J'ai commencé avec ceci exemple de documentation .

from subprocess import Popen, PIPE
p1 = Popen(["grep", "-v", "not"], stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]
print output

Cela fonctionne, mais comme p1 's stdin n'est pas redirigé, je dois taper des éléments dans le terminal pour alimenter le canal. Quand je tape ^D fermant stdin, je reçois la sortie que je veux.

Cependant, je souhaite envoyer des données au canal en utilisant une variable de chaîne python. J'ai d'abord essayé d'écrire sur stdin:

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
p1.stdin.write('test\n')
output = p2.communicate()[0] # blocks forever here

N'a pas fonctionné J'ai essayé d'utiliser p2.stdout.read() à la place de la dernière ligne, mais cela bloque également. J'ai ajouté p1.stdin.flush() et p1.stdin.close() mais cela n'a pas fonctionné non plus. Je me suis ensuite déplacé pour communiquer:

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
p1.communicate('test\n') # blocks forever here
output = p2.communicate()[0] 

Donc ce n'est toujours pas ça.

J'ai remarqué que l'exécution d'un seul processus (comme p1 ci-dessus, en supprimant p2) fonctionne parfaitement. Et passer un descripteur de fichier à p1 (stdin=open(...)) fonctionne également. Le problème est donc:

Est-il possible de transmettre des données à un pipeline de 2 sous-processus ou plus en python, sans blocage? Pourquoi pas?

Je sais que je pourrais faire fonctionner un Shell et le pipeline dans Shell, mais ce n'est pas ce que je veux.


UPDATE 1 : Suivant le conseil d'Aaron Digulla ci-dessous, j'essaie maintenant d'utiliser des threads pour que cela fonctionne.

J'ai d'abord essayé de lancer p1.communicate sur un thread.

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
t = threading.Thread(target=p1.communicate, args=('some data\n',))
t.start()
output = p2.communicate()[0] # blocks forever here

Ok, n'a pas fonctionné. Essayé d'autres combinaisons comme le changer en .write() et aussi p2.read(). Rien. Essayons maintenant l’approche opposée:

def get_output(subp):
    output = subp.communicate()[0] # blocks on thread
    print 'GOT:', output

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
t = threading.Thread(target=get_output, args=(p2,)) 
t.start()
p1.communicate('data\n') # blocks here.
t.join()

le code finit par bloquer quelque part. Soit dans le fil créé, soit dans le fil principal, soit les deux. Donc ça n'a pas marché. Si vous savez comment le faire fonctionner, il serait plus facile de fournir du code fonctionnel. J'essaye ici.


UPDATE 2

Paul Du Bois a répondu ci-dessous avec quelques informations, alors j'ai fait plus de tests. J'ai lu l'intégralité du module subprocess.py et j'ai compris comment cela fonctionne. J'ai donc essayé d'appliquer exactement cela au code.

Je suis sur Linux, mais depuis que je testais avec des threads, ma première approche consistait à répliquer le code de threading de Windows exact vu sur la méthode communicate() de subprocess.py, mais pour deux processus au lieu d'un. Voici la liste complète de ce que j'ai essayé:

import os
from subprocess import Popen, PIPE
import threading

def get_output(fobj, buffer):
    while True:
        chunk = fobj.read() # BLOCKS HERE
        if not chunk:
            break
        buffer.append(chunk)

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)

b = [] # create a buffer
t = threading.Thread(target=get_output, args=(p2.stdout, b))
t.start() # start reading thread

for x in xrange(100000):
    p1.stdin.write('hello world\n') # write data
    p1.stdin.flush()
p1.stdin.close() # close input...
t.join()

Bien. Ça n'a pas marché. Même après l'appel de p1.stdin.close(), p2.stdout.read() bloque toujours.

Ensuite, j'ai essayé le code posix sur subprocess.py:

import os
from subprocess import Popen, PIPE
import select

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)

numwrites = 100000
to_read = [p2.stdout]
to_write = [p1.stdin]
b = [] # create buffer

while to_read or to_write:
    read_now, write_now, xlist = select.select(to_read, to_write, [])
    if read_now:
        data = os.read(p2.stdout.fileno(), 1024)
        if not data:
            p2.stdout.close()
            to_read = []
        else:
            b.append(data)

    if write_now:
        if numwrites > 0:
            numwrites -= 1
            p1.stdin.write('hello world!\n'); p1.stdin.flush()
        else:
            p1.stdin.close()
            to_write = []

print b

Bloque également select.select(). En écartant prints, j'ai découvert ceci:

  • La lecture fonctionne. Le code se lit plusieurs fois pendant l'exécution.
  • L'écriture fonctionne aussi. Les données sont écrites dans p1.stdin.
  • À la fin de numwrites, p1.stdin.close() est appelé.
  • Lorsque select() commence à bloquer, seul to_read possède quelque chose, p2.stdout. to_write est déjà vide.
  • os.read() call renvoie toujours quelque chose, donc p2.stdout.close() n'est jamais appelé.

Conclusion des deux tests : Fermer la stdin du premier processus sur le pipeline (grep dans l'exemple) ne lui permet pas de vider sa sortie en mémoire tampon vers le suivant et de mourir.

Pas moyen de le faire fonctionner?

PS: Je ne veux pas utiliser de fichier temporaire, j'ai déjà testé avec des fichiers et je sais que cela fonctionne. Et je ne veux pas utiliser Windows.

33
nosklo

J'ai découvert comment faire.

Il ne s'agit pas de threads, ni de select ().

Lorsque j'exécute le premier processus (grep), il crée deux descripteurs de fichier de bas niveau, un pour chaque canal. Permet d'appeler ces a et b.

Lorsque j'exécute le deuxième processus, b est passé à cutsdtin. Mais il y a une valeur mortelle sur Popen - close_fds=False.

Cela a pour effet que cut hérite également de a. Donc, grep ne peut pas mourir même si je ferme a, car stdin est toujours ouvert sur le processus de cut (cut l'ignore).

Le code suivant fonctionne maintenant parfaitement.

from subprocess import Popen, PIPE

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE, close_fds=True)
p1.stdin.write('Hello World\n')
p1.stdin.close()
result = p2.stdout.read() 
assert result == "Hello Worl\n"

close_fds=True DEVRAIT ÊTRE DEFAULT sur les systèmes Unix. Sur les fenêtres, il ferme tout fds, ce qui évite les tuyaux.

MODIFIER:

PS: Pour les personnes ayant un problème similaire, lisant cette réponse: Comme l’a dit Pooryorick dans un commentaire, cela pourrait également bloquer si les données écrites dans p1.stdin sont plus grandes que les tampons. Dans ce cas, vous devez découper les données en fragments plus petits et utiliser select.select() pour savoir quand lire/écrire. Le code de la question devrait donner une indication sur la manière de mettre en œuvre cela.

EDIT2: Une autre solution, avec plus d’aide de pooryorick - au lieu d’utiliser close_fds=True et de fermerALLfds, vous pouvez fermer la fds qui appartient au premier processus, lors de l’exécution du second, et cela fonctionnera. La fermeture doit être effectuée dans l’enfant, la fonction preexec_fn de Popen est donc très pratique pour le faire. En exécutant p2, vous pouvez faire:

p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE, stderr=devnull, preexec_fn=p1.stdin.close)
21
nosklo

Travailler avec de gros fichiers

Deux principes doivent être appliqués de manière uniforme lorsque vous travaillez avec de gros fichiers en Python.

  1. Comme toute routine IO peut bloquer, nous devons conserver chaque étape du pipeline dans un thread différent ou processus. Nous utilisons des threads dans cet exemple, mais les sous-processus vous permettraient d'éviter le GIL.
  2. Nous devons utiliser lectures incrémentielles et écrire pour ne pas attendre EOF avant de commencer à progresser.

Une alternative consiste à utiliser des entrées/sorties non bloquantes, bien que cela soit fastidieux en Python standard. Voir gevent pour une bibliothèque de threads légère qui implémente l'API synchrone IO à l'aide de primitives non bloquantes.

Exemple de code

Nous allons construire un pipeline stupide qui est à peu près

{cat /usr/share/dict/words} | grep -v not              \
    | {upcase, filtered tee to stderr} | cut -c 1-10   \
    | {translate 'E' to '3'} | grep K | grep Z | {downcase}

où chaque étape entre accolades {} est implémentée en Python alors que les autres utilisent des programmes externes standard. TL; DR: Voir ce résumé .

Nous commençons par les importations attendues.

#!/usr/bin/env python
from subprocess import Popen, PIPE
import sys, threading

Stades Python du pipeline

Tout, sauf la dernière étape du pipeline implémentée par Python, doit être inséré dans un thread pour que IO ne bloque pas les autres. Celles-ci pourraient à la place s'exécuter dans des sous-processus Python si vous vouliez qu'ils s'exécutent en parallèle (évitez le GIL).

def writer(output):
    for line in open('/usr/share/dict/words'):
        output.write(line)
    output.close()
def filter(input, output):
    for line in input:
        if 'k' in line and 'z' in line: # Selective 'tee'
            sys.stderr.write('### ' + line)
        output.write(line.upper())
    output.close()
def leeter(input, output):
    for line in input:
        output.write(line.replace('E', '3'))
    output.close()

Chacun de ceux-ci doit être mis dans son propre thread, ce que nous ferons à l'aide de cette fonction pratique.

def spawn(func, **kwargs):
    t = threading.Thread(target=func, kwargs=kwargs)
    t.start()
    return t

Créer le pipeline

Créez les étapes externes à l'aide de Popen et les étapes Python à l'aide de spawn. L'argument bufsize=-1 indique d'utiliser la mise en mémoire tampon par défaut du système (généralement 4 kiB). Cela est généralement plus rapide que la mise en mémoire tampon par défaut (sans tampon) ou de ligne, mais vous souhaiterez une mise en mémoire tampon de ligne si vous souhaitez surveiller visuellement la sortie sans retard.

grepv   = Popen(['grep','-v','not'], stdin=PIPE, stdout=PIPE, bufsize=-1)
cut     = Popen(['cut','-c','1-10'], stdin=PIPE, stdout=PIPE, bufsize=-1)
grepk = Popen(['grep', 'K'], stdin=PIPE, stdout=PIPE, bufsize=-1)
grepz = Popen(['grep', 'Z'], stdin=grepk.stdout, stdout=PIPE, bufsize=-1)

twriter = spawn(writer, output=grepv.stdin)
tfilter = spawn(filter, input=grepv.stdout, output=cut.stdin)
tleeter = spawn(leeter, input=cut.stdout, output=grepk.stdin)

Conduire le pipeline

Assemblés comme ci-dessus, tous les tampons du pipeline vont se remplir, mais comme personne ne lit à la fin (grepz.stdout), ils se bloqueront tous. Nous pourrions lire le tout en un seul appel à grepz.stdout.read(), mais cela utiliserait beaucoup de mémoire pour les gros fichiers. Au lieu de cela, nous lisons progressivement.

for line in grepz.stdout:
    sys.stdout.write(line.lower())

Les threads et les processus nettoient une fois qu'ils atteignent EOF. Nous pouvons explicitement nettoyer en utilisant

for t in [twriter, tfilter, tleeter]: t.join()
for p in [grepv, cut, grepk, grepz]: p.wait()

Python-2.6 et versions antérieures

En interne, subprocess.Popen appelle fork, configure les descripteurs de fichier de canal et appelle exec. Le processus enfant de fork a des copies de tous les descripteurs de fichier dans le processus parent et les les deux} copies devront être fermées avant que le lecteur correspondant obtienne EOF. Cela peut être corrigé en fermant manuellement les canaux (soit par close_fds=True ou un argument preexec_fn approprié en subprocess.Popen), soit en définissant le drapeau FD_CLOEXEC pour que exec ferme automatiquement le descripteur de fichier. Cet indicateur est défini automatiquement dans Python-2.7 et ultérieur, voir numéro12786 . Nous pouvons obtenir le comportement de Python-2.7 dans les versions antérieures de Python en appelant

p._set_cloexec_flags(p.stdin)

avant de passer p.stdin en tant qu’argument à un subprocess.Popen ultérieur.

6
Jed

Il existe trois astuces principales pour que les tuyaux fonctionnent comme prévu

  1. Assurez-vous que chaque extrémité du tuyau est utilisée dans un processus/processus différent (certains exemples situés tout en haut souffrent de ce problème).

  2. fermer explicitement l'extrémité du tuyau inutilisée dans chaque processus

  3. gérer la mise en mémoire tampon soit en la désactivant (option Python -u), en utilisant pty, soit en remplissant simplement la mémoire tampon avec quelque chose qui n'affectera pas les données (peut-être '\ n', mais ce qui convient le mieux).

Les exemples du module "pipeline" de Python (je suis l'auteur) correspondent parfaitement à votre scénario et rendent les étapes de bas niveau assez claires.

http://pypi.python.org/pypi/pipeline/

Plus récemment, j'ai utilisé le module de sous-processus dans le cadre d'un modèle de contrôleur producteur-processeur-consommateur-contrôleur:

http://www.darkarchive.org/w/Pub/PythonInteract

Cet exemple traite de la stdin tamponnée sans recourir à un pty et illustre également les extrémités des tuyaux à fermer où. Je préfère les processus au filetage, mais le principe est le même. En outre, il illustre la synchronisation des files d'attente alimentant le producteur et collecte la sortie du consommateur, ainsi que la façon de les fermer proprement (recherchez les sentinelles insérées dans les files d'attente). Ce modèle permet de générer de nouvelles entrées sur la base des sorties récentes, permettant ainsi une découverte et un traitement récursifs.

3
Poor Yorick

La solution proposée par Nosklo se cassera rapidement si trop de données sont écrites sur le destinataire:


from subprocess import Popen, PIPE

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE, close_fds=True)
p1.stdin.write('Hello World\n' * 20000)
p1.stdin.close()
result = p2.stdout.read() 
assert result == "Hello Worl\n"

Si ce script ne se bloque pas sur votre ordinateur, augmentez simplement "20000" pour atteindre un niveau dépassant la taille des tampons de canal de votre système d'exploitation.

Cela est dû au fait que le système d'exploitation met l'entrée "grep" en mémoire tampon, mais une fois que cette mémoire tampon est pleine, l'appel p1.stdin.write sera bloqué jusqu'à ce que quelque chose soit lu à partir de p2.stdout. Dans les scénarios de jouets, vous pouvez utiliser l'écriture/la lecture d'un canal dans le même processus, mais en utilisation normale, il est nécessaire d'écrire à partir d'un thread/processus et de lire à partir d'un thread/processus séparé. Ceci est vrai pour subprocess.popen, os.pipe, os.popen *, etc.

Un autre inconvénient est que, parfois, vous souhaitez continuer à alimenter le tuyau avec des éléments générés à partir d'une sortie précédente du même tuyau. La solution consiste à rendre le dévidoir et le lecteur de tuyau asynchrones par rapport au programme man et à mettre en œuvre deux files d'attente: une entre le programme principal et le dévidoir et l'autre entre le programme principal et le lecteur de tuyau. PythonInteract est un exemple de cela.

Le sous-processus est un modèle pratique, mais comme il cache les détails des appels os.popen et os.fork, il peut parfois être plus difficile à gérer que les appels de niveau inférieur qu'il utilise. Pour cette raison, le sous-processus n'est pas un bon moyen de comprendre le fonctionnement réel des canaux inter-processus.

3
Poor Yorick

Vous devez le faire dans plusieurs threads. Sinon, vous vous retrouverez dans une situation où vous ne pouvez pas envoyer de données: l'enfant p1 ne lit pas votre entrée car p2 ne lit pas la sortie de p1 car vous ne lisez pas la sortie de p2.

Vous avez donc besoin d’un fil d’arrière-plan qui lit ce que p2 écrit. Cela permettra à p2 de continuer après l'écriture de données dans le tube, ce qui permettra de lire la ligne d'entrée suivante de p1, ce qui permettra à nouveau à p1 de traiter les données que vous lui envoyez.

Vous pouvez également envoyer les données à p1 avec un fil d’arrière-plan et lire le résultat de p2 dans le fil principal. Mais chaque côté doit être un fil.

2
Aaron Digulla

En réponse à l'affirmation de nosklo (voir les autres commentaires à cette question) selon laquelle cela ne peut être fait sans close_fds=True:

close_fds=True n'est nécessaire que si vous avez laissé d'autres descripteurs de fichier ouverts. Lorsque vous ouvrez plusieurs processus enfants, il est toujours bon de garder une trace des fichiers ouverts qui pourraient être hérités et de fermer explicitement ceux qui ne sont pas nécessaires:

from subprocess import Popen, PIPE

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p1.stdin.write('Hello World\n')
p1.stdin.close()
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
result = p2.stdout.read() 
assert result == "Hello Worl\n"

close_fds est paramétré par défaut sur False car le sous-processus préfère faire confiance au programme appelant pour savoir ce qu'il fait avec les descripteurs de fichiers ouverts, et il suffit de fournir à l'appelant une option facile pour tous les fermer s'il le souhaite.

Mais le vrai problème, c’est que les tampons de tuyau vont vous piquer contre tous les exemples de jouets. Comme je l'ai dit dans mes autres réponses à cette question, la règle de base est de ne pas laisser votre lecteur et votre auteur ouverts dans le même processus/fil. Quiconque souhaite utiliser le module de sous-processus pour une communication bidirectionnelle serait bien inspiré d'étudier os.pipe et os.fork, en premier lieu. En fait, ils ne sont pas si difficiles à utiliser si vous avez un bon exemple à regarder.

2
Poor Yorick

Je pense que vous examinez peut-être le mauvais problème. Comme Aaron le dit, si vous essayez d’être à la fois un producteur et un consommateur de pipeline, il est facile de tomber dans une impasse. C'est le problème que communication () résout.

communiquer () n'est pas tout à fait correct pour vous car stdin et stdout se trouvent sur des objets de sous-processus différents; mais si vous jetez un coup d'œil à l'implémentation de subprocess.py, vous verrez que cela correspond exactement à ce que Aaron a suggéré.

Une fois que vous voyez que les lectures et les écritures sont communiquées, vous verrez que lors de votre deuxième essai, communic () entre en concurrence avec p2 pour la sortie de p1:

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
# ...
p1.communicate('data\n')       # reads from p1.stdout, as does p2

Je cours sur win32, qui a certainement des caractéristiques d’E/S et de tampon différentes, mais cela fonctionne pour moi:

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
t = threading.Thread(target=get_output, args=(p2,)) 
t.start()
p1.stdin.write('hello world\n' * 100000)
p1.stdin.close()
t.join()

J'ai réglé la taille de l'entrée pour produire un blocage lorsque vous utilisez un p2.read () naïf et non threadé

Vous pouvez également essayer de mettre en mémoire tampon dans un fichier, par exemple

fd, _ = tempfile.mkstemp()
os.write(fd, 'hello world\r\n' * 100000)
os.lseek(fd, 0, os.SEEK_SET)
p1 = Popen(["grep", "-v", "not"], stdin=fd, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE)
print p2.stdout.read()

Cela fonctionne aussi pour moi sans impasse.

1
Paul Du Bois

Dans l'un des commentaires ci-dessus, j'ai mis au défi nosklo de publier du code pour étayer ses affirmations concernant select.select ou de réévaluer mes réponses qu'il avait votées auparavant. Il a répondu avec le code suivant:

from subprocess import Popen, PIPE
import select

p1 = Popen(["grep", "-v", "not"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cut", "-c", "1-10"], stdin=p1.stdout, stdout=PIPE, close_fds=True)

data_to_write = 100000 * 'hello world\n'
to_read = [p2.stdout]
to_write = [p1.stdin]
b = [] # create buffer
written = 0


while to_read or to_write:
    read_now, write_now, xlist = select.select(to_read, to_write, [])
    if read_now:
        data = p2.stdout.read(1024)
        if not data:
            p2.stdout.close()
            to_read = []
        else:
            b.append(data)

    if write_now:
        if written < len(data_to_write):
            part = data_to_write[written:written+1024]
            written += len(part)
            p1.stdin.write(part); p1.stdin.flush()
        else:
            p1.stdin.close()
            to_write = []

print b

Un problème avec ce script est qu’il devine ensuite la taille/nature des tampons du canal de communication système. Le script aurait moins d'échecs s'il pouvait supprimer des nombres magiques tels que 1024.

Le gros problème est que ce code de script ne fonctionne de manière cohérente qu'avec la bonne combinaison de saisie de données et de programmes externes. grep et cut fonctionnent tous les deux avec des lignes et leurs tampons internes se comportent donc un peu différemment. Si nous utilisons une commande plus générique telle que "cat" et écrivons des bits de données plus petits dans le tube, la condition de concurrence fatale apparaîtra plus souvent:

from subprocess import Popen, PIPE
import select
import time

p1 = Popen(["cat"], stdin=PIPE, stdout=PIPE)
p2 = Popen(["cat"], stdin=p1.stdout, stdout=PIPE, close_fds=True)

data_to_write = 'hello world\n'
to_read = [p2.stdout]
to_write = [p1.stdin]
b = [] # create buffer
written = 0


while to_read or to_write:
    time.sleep(1)
    read_now, write_now, xlist = select.select(to_read, to_write, [])
    if read_now:
        print 'I am reading now!'
        data = p2.stdout.read(1024)
        if not data:
            p1.stdout.close()
            to_read = []
        else:
            b.append(data)

    if write_now:
        print 'I am writing now!'
        if written < len(data_to_write):
            part = data_to_write[written:written+1024]
            written += len(part)
            p1.stdin.write(part); p1.stdin.flush()
        else:
            print 'closing file'
            p1.stdin.close()
            to_write = []

print b

Dans ce cas, deux résultats différents vont se manifester:

write, write, close file, read -> success
write, read -> hang

Encore une fois, je mets au défi nosklo d’appliquer soit le code postal illustrant l’utilisation de select.select pour gérer les entrées arbitraires et la mise en mémoire tampon des canaux à partir d’un seul thread, soit de modifier au maximum mes réponses.

En bout de ligne: n'essayez pas de manipuler les deux extrémités d'un tuyau à partir d'un seul fil. C'est juste pas la peine. Voir pipeline pour un exemple bas niveau de Nice expliquant comment le faire correctement.

1
Poor Yorick

Qu'en est-il de l'utilisation d'un fichier spooledTemporaryFile? Cela contourne (mais ne résout peut-être pas) le problème:

http://docs.python.org/library/tempfile.html#tempfile.SpooledTemporaryFile

Vous pouvez y écrire comme un fichier, mais c'est en fait un bloc de mémoire.

Ou suis-je totalement mal compris ...

0
Adam Nelson