Vous avez dit que vous vouliez lancer un script indépendant ( my_script.py
) chaque fois qu'une demande nous parvient. Ceci utilisera les capacités de spacy.en
sans les frais généraux de chargement spacy.en
. Avec cette approche, le système d'exploitation créera toujours un nouveau processus lorsque vous lancerez votre script. Il n'y a donc qu'un seul moyen d'éviter de charger spacy.en
à chaque fois : avoir un processus séparé qui est déjà en cours d'exécution, avec spacy.en
chargé, et que votre script communique avec ce processus. Le code ci-dessous montre une façon de faire cela. Cependant, comme d'autres l'ont dit, vous aurez probablement intérêt à modifier l'architecture de votre serveur de sorte que spacy.en
est chargé dans votre serveur web (par exemple, si vous utilisez un serveur web basé sur Python).
La forme la plus courante de communication inter-processus est via les sockets TCP/IP. Le code ci-dessous met en œuvre un petit serveur qui garde spacy.en
chargé et traite les demandes du client. Il dispose également d'un client qui transmet les demandes à ce serveur et reçoit les résultats en retour. C'est à vous de décider ce qu'il faut mettre dans ces transmissions.
Il existe également un troisième script. Puisque le client et le serveur ont tous deux besoin de fonctions d'envoi et de réception, ces fonctions sont dans un script partagé appelé comm.py
. (Notez que le client et le serveur chargent chacun une copie distincte du fichier comm.py
; ils ne communiquent pas par le biais d'un module unique chargé dans la mémoire partagée).
Je suppose que les deux scripts sont exécutés sur la même machine. Si non, vous devrez mettre une copie de comm.py
sur les deux machines et changez comm.server_host
au nom de la machine ou à l'adresse IP du serveur.
Exécuter nlp_server.py
en tant que processus d'arrière-plan (ou simplement dans une autre fenêtre de terminal pour les tests). Il attend les demandes, les traite et renvoie les résultats :
import comm
import socket
from spacy.en import English
nlp = English()
def process_connection(sock):
print "processing transmission from client..."
# receive data from the client
data = comm.receive_data(sock)
# do something with the data
result = {"data received": data}
# send the result back to the client
comm.send_data(result, sock)
# close the socket with this particular client
sock.close()
print "finished processing transmission from client..."
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# open socket even if it was used recently (e.g., server restart)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind((comm.server_host, comm.server_port))
# queue up to 5 connections
server_sock.listen(5)
print "listening on port {}...".format(comm.server_port)
try:
while True:
# accept connections from clients
(client_sock, address) = server_sock.accept()
# process this connection
# (this could be launched in a separate thread or process)
process_connection(client_sock)
except KeyboardInterrupt:
print "Server process terminated."
finally:
server_sock.close()
Chargement my_script.py
comme un script à exécution rapide pour demander un résultat au serveur nlp (par exemple, python my_script.py here are some arguments
):
import socket, sys
import comm
# data can be whatever you want (even just sys.argv)
data = sys.argv
print "sending to server:"
print data
# send data to the server and receive a result
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# disable Nagle algorithm (probably only needed over a network)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
sock.connect((comm.server_host, comm.server_port))
comm.send_data(data, sock)
result = comm.receive_data(sock)
sock.close()
# do something with the result...
print "result from server:"
print result
comm.py
contient du code qui est utilisé à la fois par le client et le serveur :
import sys, struct
import cPickle as pickle
# pick a port that is not used by any other process
server_port = 17001
server_host = '127.0.0.1' # localhost
message_size = 8192
# code to use with struct.pack to convert transmission size (int)
# to a byte string
header_pack_code = '>I'
# number of bytes used to represent size of each transmission
# (corresponds to header_pack_code)
header_size = 4
def send_data(data_object, sock):
# serialize the data so it can be sent through a socket
data_string = pickle.dumps(data_object, -1)
data_len = len(data_string)
# send a header showing the length, packed into 4 bytes
sock.sendall(struct.pack(header_pack_code, data_len))
# send the data
sock.sendall(data_string)
def receive_data(sock):
""" Receive a transmission via a socket, and convert it back into a binary object. """
# This runs as a loop because the message may be broken into arbitrary-size chunks.
# This assumes each transmission starts with a 4-byte binary header showing the size of the transmission.
# See https://docs.python.org/3/howto/sockets.html
# and http://code.activestate.com/recipes/408859-socketrecv-three-ways-to-turn-it-into-recvall/
header_data = ''
header_done = False
# set dummy values to start the loop
received_len = 0
transmission_size = sys.maxint
while received_len < transmission_size:
sock_data = sock.recv(message_size)
if not header_done:
# still receiving header info
header_data += sock_data
if len(header_data) >= header_size:
header_done = True
# split the already-received data between header and body
messages = [header_data[header_size:]]
received_len = len(messages[0])
header_data = header_data[:header_size]
# find actual size of transmission
transmission_size = struct.unpack(header_pack_code, header_data)[0]
else:
# already receiving data
received_len += len(sock_data)
messages.append(sock_data)
# combine messages into a single string
data_string = ''.join(messages)
# convert to an object
data_object = pickle.loads(data_string)
return data_object
Remarque : vous devez vous assurer que le résultat envoyé par le serveur n'utilise que des structures de données natives (dicts, listes, chaînes de caractères, etc.). Si le résultat inclut un objet défini dans spacy.en
alors le client importera automatiquement spacy.en
lorsqu'il dépaquette le résultat, afin de fournir les méthodes de l'objet.
Cette configuration est très similaire au protocole HTTP (le serveur attend des connexions, le client se connecte, le client envoie une requête, le serveur envoie une réponse, les deux parties se déconnectent). Vous feriez donc mieux d'utiliser un serveur et un client HTTP standard au lieu de ce code personnalisé. Il s'agirait d'une "API RESTful", un terme très répandu de nos jours (à juste titre). L'utilisation de paquets HTTP standard vous épargnerait la gestion de votre propre code client/serveur, et vous pourriez même être en mesure d'appeler votre serveur de traitement des données directement à partir de votre serveur Web existant au lieu de lancer le programme my_script.py
. Cependant, vous devrez traduire votre demande en quelque chose de compatible avec HTTP, par exemple, une demande GET ou POST, ou peut-être simplement une URL spécialement formatée.
Une autre option serait d'utiliser un paquet standard de communication interprocessus comme PyZMQ, redis, mpi4py ou peut-être zmq_object_exchanger. Voir cette question pour quelques idées : IPC Python efficace
Ou bien vous pouvez sauvegarder une copie de la spacy.en
sur le disque en utilisant l'option dill
paquet ( https://pypi.python.org/pypi/dill ) et le restaurer ensuite au début de my_script.py
. Cela peut être plus rapide que de l'importer/reconstruire à chaque fois et plus simple que d'utiliser la communication interprocessus.