2010-03-09 3 views
31

Sto cercando di creare un semplice client IRC in Python (come una specie di progetto mentre imparo la lingua).Ingresso console non bloccante Python

Ho un ciclo che utilizzo per ricevere e analizzare ciò che il server IRC mi invia, ma se uso raw_input per inserire elementi, interrompe il ciclo in modo permanente fino a quando non inserisco qualcosa (ovviamente).

Come posso inserire qualcosa senza l'arresto del loop?

Grazie in anticipo.

(non credo che ho bisogno di pubblicare il codice, voglio solo qualcosa di ingresso senza l'arresto mentre 1 ciclo.)

EDIT: Sono su Windows.

+2

Ho aggiornato la mia risposta con un esempio specifico di Windows. – Mizipzor

+0

Quale modulo di rete stai usando? Contorto, prese, asyncore? – DevPlayer

risposta

34

Per Windows, console, utilizzare il modulo msvcrt:

import msvcrt 

num = 0 
done = False 
while not done: 
    print(num) 
    num += 1 

    if msvcrt.kbhit(): 
     print "you pressed",msvcrt.getch(),"so now i will quit" 
     done = True 

Per Linux, questo article descrive la seguente soluzione, si richiede il modulo termios:

import sys 
import select 
import tty 
import termios 

def isData(): 
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []) 

old_settings = termios.tcgetattr(sys.stdin) 
try: 
    tty.setcbreak(sys.stdin.fileno()) 

    i = 0 
    while 1: 
     print(i) 
     i += 1 

     if isData(): 
      c = sys.stdin.read(1) 
      if c == '\x1b':   # x1b is ESC 
       break 

finally: 
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) 

Per cross platform, o nel caso tu voglia una GUI, puoi usare Pygame:

import pygame 
from pygame.locals import * 

def display(str): 
    text = font.render(str, True, (255, 255, 255), (159, 182, 205)) 
    textRect = text.get_rect() 
    textRect.centerx = screen.get_rect().centerx 
    textRect.centery = screen.get_rect().centery 

    screen.blit(text, textRect) 
    pygame.display.update() 

pygame.init() 
screen = pygame.display.set_mode((640,480)) 
pygame.display.set_caption('Python numbers') 
screen.fill((159, 182, 205)) 

font = pygame.font.Font(None, 17) 

num = 0 
done = False 
while not done: 
    display(str(num)) 
    num += 1 

    pygame.event.pump() 
    keys = pygame.key.get_pressed() 
    if keys[K_ESCAPE]: 
     done = True 
+0

Ho già pygame, quindi proverò. Grazie. Ancora, qualcun altro ha una soluzione migliore? Voglio tenerlo una console. – ImTooStupidForThis

+0

Grazie per la roba msvcrt. Sei fantastico. – ImTooStupidForThis

+0

Felice di essere d'aiuto, benvenuto su Stackoverflow. :) – Mizipzor

8

Su Linux, ecco un refactoring del codice di mizipzor che rende questo un po 'più semplice, nel caso in cui si debba usare questo codice in più punti.

import sys 
import select 
import tty 
import termios 

class NonBlockingConsole(object): 

    def __enter__(self): 
     self.old_settings = termios.tcgetattr(sys.stdin) 
     tty.setcbreak(sys.stdin.fileno()) 
     return self 

    def __exit__(self, type, value, traceback): 
     termios.tcsetattr(sys.stdin, termios.TCSADRAIN, self.old_settings) 


    def get_data(self): 
     if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []): 
      return sys.stdin.read(1) 
     return False 

Ecco come usare questo: Questo codice stamperà un contatore che continua a crescere finché non si preme ESC.

with NonBlockingConsole() as nbc: 
    i = 0 
    while 1: 
     print i 
     i += 1 
     if nbc.get_data() == '\x1b': # x1b is ESC 
      break 
+0

Uso di GNU/Linux: è ancora necessario premere Invio dopo aver inserito un carattere, ma poi funziona. Almeno non è bloccante e restituisce principalmente caratteri normali (nessun codice chiave, a parte chiavi speciali come escape o backspace, ovviamente). Grazie! – Luc

9

Ecco una soluzione che gira sotto Linux e Windows utilizzando un thread separato:

import sys 
import threading 
import time 
import Queue 

def add_input(input_queue): 
    while True: 
     input_queue.put(sys.stdin.read(1)) 

def foobar(): 
    input_queue = Queue.Queue() 

    input_thread = threading.Thread(target=add_input, args=(input_queue,)) 
    input_thread.daemon = True 
    input_thread.start() 

    last_update = time.time() 
    while True: 

     if time.time()-last_update>0.5: 
      sys.stdout.write(".") 
      last_update = time.time() 

     if not input_queue.empty(): 
      print "\ninput:", input_queue.get() 

foobar() 
+0

Sembra essere l'unica soluzione qui che funziona sia su windows cmd-console che su eclipse! –

15

Questo è il più impressionante solution che abbia mai visto. Incollato qui nel caso di collegamento va giù:

#!/usr/bin/env python 
''' 
A Python class implementing KBHIT, the standard keyboard-interrupt poller. 
Works transparently on Windows and Posix (Linux, Mac OS X). Doesn't work 
with IDLE. 

This program is free software: you can redistribute it and/or modify 
it under the terms of the GNU Lesser General Public License as 
published by the Free Software Foundation, either version 3 of the 
License, or (at your option) any later version. 

This program is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
GNU General Public License for more details. 

''' 

import os 

# Windows 
if os.name == 'nt': 
    import msvcrt 

# Posix (Linux, OS X) 
else: 
    import sys 
    import termios 
    import atexit 
    from select import select 


class KBHit: 

    def __init__(self): 
     '''Creates a KBHit object that you can call to do various keyboard things. 
     ''' 

     if os.name == 'nt': 
      pass 

     else: 

      # Save the terminal settings 
      self.fd = sys.stdin.fileno() 
      self.new_term = termios.tcgetattr(self.fd) 
      self.old_term = termios.tcgetattr(self.fd) 

      # New terminal setting unbuffered 
      self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO) 
      termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term) 

      # Support normal-terminal reset at exit 
      atexit.register(self.set_normal_term) 


    def set_normal_term(self): 
     ''' Resets to normal terminal. On Windows this is a no-op. 
     ''' 

     if os.name == 'nt': 
      pass 

     else: 
      termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term) 


    def getch(self): 
     ''' Returns a keyboard character after kbhit() has been called. 
      Should not be called in the same program as getarrow(). 
     ''' 

     s = '' 

     if os.name == 'nt': 
      return msvcrt.getch().decode('utf-8') 

     else: 
      return sys.stdin.read(1) 


    def getarrow(self): 
     ''' Returns an arrow-key code after kbhit() has been called. Codes are 
     0 : up 
     1 : right 
     2 : down 
     3 : left 
     Should not be called in the same program as getch(). 
     ''' 

     if os.name == 'nt': 
      msvcrt.getch() # skip 0xE0 
      c = msvcrt.getch() 
      vals = [72, 77, 80, 75] 

     else: 
      c = sys.stdin.read(3)[2] 
      vals = [65, 67, 66, 68] 

     return vals.index(ord(c.decode('utf-8'))) 


    def kbhit(self): 
     ''' Returns True if keyboard character was hit, False otherwise. 
     ''' 
     if os.name == 'nt': 
      return msvcrt.kbhit() 

     else: 
      dr,dw,de = select([sys.stdin], [], [], 0) 
      return dr != [] 


# Test  
if __name__ == "__main__": 

    kb = KBHit() 

    print('Hit any key, or ESC to exit') 

    while True: 

     if kb.kbhit(): 
      c = kb.getch() 
      if ord(c) == 27: # ESC 
       break 
      print(c) 

    kb.set_normal_term() 

Realizzato da Simon D. Levy, parte di un compilation of software Ha scritto e rilasciato sotto Gnu Lesser General Public License.

2

Penso che la libreria di curses possa aiutare.

import curses 
import datetime 

stdscr = curses.initscr() 
curses.noecho() 
stdscr.nodelay(1) # set getch() non-blocking 

stdscr.addstr(0,0,"Press \"p\" to show count, \"q\" to exit...") 
line = 1 
try: 
    while 1: 
     c = stdscr.getch() 
     if c == ord('p'): 
      stdscr.addstr(line,0,"Some text here") 
      line += 1 
     elif c == ord('q'): break 

     """ 
     Do more things 
     """ 

finally: 
    curses.endwin() 
+0

curses non è portatile. – kfsone