2011-08-24 13 views
9

Sé que puedo dejar la impresión de escribir una nueva línea mediante la adición de una comaPosible obtener la entrada del usuario sin insertar una nueva línea?

print "Hello, world!", 

Pero, ¿cómo hago para dejar raw_input de escribir una nueva línea?

print "Hello, ", 
name = raw_input() 
print ", how do you do?" 

Resultado:

Hola, Tomas
, ¿cómo se hace?

resultado que quiero:

Hola, Tomás, ¿cómo se hace?

+0

Para el registro, si la nueva línea en 'raw_input' se debe a que el usuario hace clic en la tecla de retorno, entonces eso es lo que esperaba. Todavía necesito una forma de evitarlo :-) – Hubro

+2

¿No sería el primer resultado "Hola, Tomás
Tomás, ¿cómo estás?" ;) – Jacob

+0

Escribí antes de pensar – Hubro

Respuesta

6

¿Pero cómo dejo que raw_input escriba una nueva línea?

En resumen: No se puede.

raw_input() siempre hará eco del texto ingresado por el usuario, incluida la nueva línea final. Eso significa que cualquier cosa que el usuario escriba se imprimirá en la salida estándar.

Si desea evitar esto, tendrá que utilizar una biblioteca de control de terminal como el módulo curses. Sin embargo, esto no es portátil; por ejemplo, curses no está disponible en sistemas Windows.

+0

'raw_input()' elimina la nueva línea final de acuerdo con su propia docstring. – Wieland

+1

@Wieland H .: Pero todavía ** lo hará eco **, p. escríbalo en salida estándar. Esto es todo lo que dije. El valor de retorno tendrá la nueva línea eliminada, pero eso es irrelevante para esta pregunta. Lea antes de votar. –

0

¿getpass.getpass() hace lo que quiere?

+0

Parecía una buena sugerencia. Desafortunadamente, 'getpass' también imprime una nueva línea. –

7

Esto evita que, en cierta medida, pero no asigna nada a la variable name:

print("Hello, {0}, how do you do?".format(raw_input("Enter name here: "))) 

Se le pedirá al usuario un nombre antes de imprimir el mensaje completo sin embargo.

6

Veo que nadie ha dado una solución de trabajo, así que decidí probarlo. Como dijo Ferdinand Beyer, es imposible obtener raw_input() para no imprimir una nueva línea después de la entrada del usuario. Sin embargo, es posible volver a la línea anterior. Lo hice en una sola línea. Es posible utilizar:

print '\033[{}C\033[1A'.format(len(x) + y), 

donde x es un número entero de la longitud de la entrada del usuario dado y y un número entero de la longitud de cadena raw_input() 's. Aunque podría no funcionar en todas las terminales (como lo leí cuando aprendí sobre este método), funciona bien en la mía. Estoy usando Kubuntu 14.04.
La cadena '\033[4C' se usa para saltar 4 índices a la derecha, por lo que sería equivalente a ' ' * 4. De la misma manera, la cadena '\033[1A' se usa para saltar 1 línea. Al usar las letras A, B, C o D en el último índice de la cadena, puede subir, bajar, derecha e izquierda, respectivamente.

Tenga en cuenta que al hacer una alineación, se eliminará el carácter impreso existente en ese punto, si lo hay.

+0

¡Esto es brillante, gracias! – Matt

1

Al igual que Nick K., dijo, tendrá que mover el cursor de texto a antes de que se repita la nueva línea. El problema es que no se puede obtener fácilmente la longitud de la línea anterior para desplazarse hacia la derecha, por temor a almacenar cada cadena impresa, solicitada e ingresada en su propia variable.

A continuación se muestra una clase (para Python 3) que soluciona esto almacenando automáticamente la última línea del terminal (siempre que utilice sus métodos). El beneficio de esto en comparación con el uso de una biblioteca de control de terminal es que funcionará en la terminal estándar tanto para la última versión de Windows como para los sistemas operativos * NIX. También imprimirá el mensaje 'Hola' antes de obtener entrada.

Si está en Windows pero no es v1511 de Windows 10, entonces necesitará instalar el módulo colorama o de lo contrario esto no funcionará, ya que trajeron soporte de movimiento de cursor ANSI en esa versión.

# For the sys.stdout file-like object 
import sys 
import platform 

if platform.system() == 'Windows': 
    try: 
     import colorama 
    except ImportError: 
     import ctypes 
     kernel32 = ctypes.windll.kernel32 
     # Enable ANSI support on Windows 10 v1511 
     kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) 
    else: 
     colorama.init() 
else: 
    # Fix Linux arrow key support in Python scripts 
    import readline 


class TempHistory: 
    """Record one line from the terminal. 

    It is necessary to keep track of the last line on the terminal so we 
    can move the text cursor rightward and upward back into the position 
    before the newline from the `input` function was echoed. 

    Note: I use the term 'echo' to refer to when text is 
    shown on the terminal but might not be written to `sys.stdout`. 

    """ 

    def __init__(self): 
     """Initialise `line` and save the `print` and `input` functions. 

     `line` is initially set to '\n' so that the `record` method 
     doesn't raise an error about the string index being out of range. 

     """ 
     self.line = '\n' 
     self.builtin_print = print 
     self.builtin_input = input 

    def _record(self, text): 
     """Append to `line` or overwrite it if it has ended.""" 
     if text == '': 
      # You can't record nothing 
      return 
     # Take into account `text` being multiple lines 
     lines = text.split('\n') 
     if text[-1] == '\n': 
      last_line = lines[-2] + '\n' 
      # If `text` ended with a newline, then `text.split('\n')[-1]` 
      # would have merely returned the newline, and not the text 
      # preceding it 
     else: 
      last_line = lines[-1] 
     # Take into account return characters which overwrite the line 
     last_line = last_line.split('\r')[-1] 
     # `line` is considered ended if it ends with a newline character 
     if self.line[-1] == '\n': 
      self.line = last_line 
     else: 
      self.line += last_line 

    def _undo_newline(self): 
     """Move text cursor back to its position before echoing newline. 

     ANSI escape sequence: `\x1b[{count}{command}` 
     `\x1b` is the escape code, and commands `A`, `B`, `C` and `D` are 
     for moving the text cursor up, down, forward and backward {count} 
     times respectively. 

     Thus, after having echoed a newline, the final statement tells 
     the terminal to move the text cursor forward to be inline with 
     the end of the previous line, and then move up into said line 
     (making it the current line again). 

     """ 
     line_length = len(self.line) 
     # Take into account (multiple) backspaces which would 
     # otherwise artificially increase `line_length` 
     for i, char in enumerate(self.line[1:]): 
      if char == '\b' and self.line[i-1] != '\b': 
       line_length -= 2 
     self.print('\x1b[{}C\x1b[1A'.format(line_length), 
        end='', flush=True, record=False) 

    def print(self, *args, sep=' ', end='\n', file=sys.stdout, flush=False, 
       record=True): 
     """Print to `file` and record the printed text. 

     Other than recording the printed text, it behaves exactly like 
     the built-in `print` function. 

     """ 
     self.builtin_print(*args, sep=sep, end=end, file=file, flush=flush) 
     if record: 
      text = sep.join([str(arg) for arg in args]) + end 
      self._record(text) 

    def input(self, prompt='', newline=True, record=True): 
     """Return one line of user input and record the echoed text. 

     Other than storing the echoed text and optionally stripping the 
     echoed newline, it behaves exactly like the built-in `input` 
     function. 

     """ 
     if prompt == '': 
      # Prevent arrow key overwriting previously printed text by 
      # ensuring the built-in `input` function's `prompt` argument 
      # isn't empty 
      prompt = ' \b' 
     response = self.builtin_input(prompt) 
     if record: 
      self._record(prompt) 
      self._record(response) 
     if not newline: 
      self._undo_newline() 
     return response 


record = TempHistory() 
# For convenience 
print = record.print 
input = record.input 

print('Hello, ', end='', flush=True) 
name = input(newline=False) 
print(', how do you do?) 
0

Una alternativa al retroceso de la nueva línea es la definición de su propia función que emula la incorporada en input función, haciendo eco y añadiendo cada golpe de teclado a una variable response excepto Introduzca (que devolverá la respuesta), mientras que también manejo Retroceso, del,Inicio, Fin , teclas de flecha, la historia de la línea, KeyboardInterrupt, EOFError, SIGTSTP y pegar desde el portapapeles. Es muy simple.

Tenga en cuenta que en Windows, necesitará instalar pyreadline si desea utilizar el historial de línea con las teclas de flecha como en la función input habitual, aunque está incompleto, por lo que la funcionalidad aún no es correcta. Además, si no tiene la versión v1511 o superior de Windows 10, necesitará instalar el módulo colorama (si está en Linux o macOS, no es necesario hacer nada).

Además, debido a msvcrt.getwch usando '\ xe0' para indicar caracteres especiales, no podrá escribir 'à'. Usted debería poder pegarlo sin embargo.

A continuación se muestra el código que hace que esto funcione en actualizado Windows 10 systems (al menos v1511), distribuciones Linux basadas en Debian y tal vez macOS y otros sistemas operativos * NIX. También debería funcionar independientemente de si tiene pyreadline instalado en Windows, aunque le faltará alguna funcionalidad.

En windows_specific.py:

"""Windows-specific functions and variables for input_no_newline.""" 
import ctypes 
from msvcrt import getwch # pylint: disable=import-error, unused-import 
from shared_stuff import ANSI 

try: 
    import colorama # pylint: disable=import-error 
except ImportError: 
    kernel32 = ctypes.windll.kernel32 
    # Enable ANSI support to move the text cursor 
    kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) 
else: 
    colorama.init() 


def get_clipboard_data(): 
    """Return string previously copied from Windows clipboard. 

    Adapted from <http://stackoverflow.com/a/23285159/6379747>. 

    """ 
    CF_TEXT = 1 
    user32 = ctypes.windll.user32 
    user32.OpenClipboard(0) 
    try: 
     if user32.IsClipboardFormatAvailable(CF_TEXT): 
      data = user32.GetClipboardData(CF_TEXT) 
      data_locked = kernel32.GlobalLock(data) 
      text = ctypes.c_char_p(data_locked) 
      kernel32.GlobalUnlock(data_locked) 
    finally: 
     user32.CloseClipboard() 
    return text.value 


def sigtstp(): 
    """Raise EOFError from Ctrl-Z since SIGTSTP doesn't exist on Windows.""" 
    raise EOFError 


input_code = { 
    **ANSI, 
    'CSI': [['\xe0', '\x00'], ''], 
    'up': 'H', 
    'down': 'P', 
    'right': 'M', 
    'left': 'K', 
    'end': 'O', 
    'home': 'G', 
    'backspace': '\b', 
    'del': 'S', 
} 

En unix_specific.py:

"""Functions and variables for Debian-based Linux distros and macOS.""" 
import sys 
import os 
import tty 
import signal 
import termios 
from shared_stuff import ANSI 

def getwch(): 
    """Return a single character from user input without echoing. 

    ActiveState code, adapted from 
    <http://code.activestate.com/recipes/134892> by Danny Yoo under 
    the Python Software Foundation license. 

    """ 
    file_descriptor = sys.stdin.fileno() 
    old_settings = termios.tcgetattr(file_descriptor) 
    try: 
     tty.setraw(file_descriptor) 
     char = sys.stdin.read(1) 
    finally: 
     termios.tcsetattr(file_descriptor, termios.TCSADRAIN, old_settings) 
    return char 


def get_clipboard_data(): 
    """Return nothing; *NIX systems automagically change sys.stdin.""" 
    return '' 


def sigtstp(): 
    """Suspend the script.""" 
    os.kill(os.getpid(), signal.SIGTSTP) 


input_code = { 
    **ANSI, 
    'CSI': ['\x1b', '['], 
    'backspace': '\x7f', 
    'del': ['3', '~'], 
} 

En readline_available.py:

"""Provide functions for up and down arrows if readline is installed. 

Basically to prevent duplicate code and make it work on systems without 
readline. 

""" 
try: 
    import readline 
except ImportError: 
    import pyreadline as readline 
from shared_stuff import move_cursor 


def init_history_index(): 
    """Return index for last element of readline.get_history_item.""" 
    # readline.get_history_item is one-based 
    return readline.get_current_history_length() + 1 


def restore_history(history_index, replaced, cursor_position): 
    """Replace 'replaced' with history and return the replacement.""" 
    try: 
     replacement = readline.get_history_item(history_index) 
    except IndexError: 
     replacement = None 
    if replacement is not None: 
     move_cursor('right', len(replaced) - cursor_position) 
     print('\b \b' * len(replaced), end='', flush=True) 
     print(replacement, end='', flush=True) 
     return replacement 
    return replaced 


def store_and_replace_history(history_index, replacement, old_history): 
    """Store history and then replace it.""" 
    old_history[history_index] = readline.get_history_item(history_index) 
    try: 
     readline.replace_history_item(history_index - 1, replacement) 
    except AttributeError: 
    # pyreadline is incomplete 
     pass 


def handle_prev_history(history_index, replaced, old_history, 
         input_replaced, history_modified): 
    """Handle some up-arrow logic.""" 
    try: 
     history = readline.get_history_item(history_index - 1) 
    except IndexError: 
     history = None 
    if history is not None: 
     if history_index > readline.get_current_history_length(): 
      readline.add_history(replaced) 
      input_replaced = True 
     else: 
      store_and_replace_history(
       history_index, replaced, old_history) 
      history_modified = True 
     history_index -= 1 
    return (history_index, input_replaced, history_modified) 


def handle_next_history(history_index, replaced, old_history, 
         input_replaced, history_modified): 
    """Handle some down-arrow logic.""" 
    try: 
     history = readline.get_history_item(history_index + 1) 
    except IndexError: 
     history = None 
    if history is not None: 
     store_and_replace_history(history_index, replaced, old_history) 
     history_modified = True 
     history_index += 1 
     input_replaced = (not history_index 
          == readline.get_current_history_length()) 
    return (history_index, input_replaced, history_modified) 


def finalise_history(history_index, response, old_history, 
        input_replaced, history_modified): 
    """Change history before the response will be returned elsewhere.""" 
    try: 
     if input_replaced: 
      readline.remove_history_item(history_index - 1) 
     elif history_modified: 
      readline.remove_history_item(history_index - 1) 
      readline.add_history(old_history[history_index - 1]) 
    except AttributeError: 
    # pyreadline is also missing remove_history_item 
     pass 
    readline.add_history(response) 

En readline_unavailable.py:

"""Provide dummy functions for if readline isn't available.""" 
# pylint: disable-msg=unused-argument 


def init_history_index(): 
    """Return an index of 1 which probably won't ever change.""" 
    return 1 


def restore_history(history_index, replaced, cursor_position): 
    """Return the replaced thing without replacing it.""" 
    return replaced 


def store_and_replace_history(history_index, replacement, old_history): 
    """Don't store history.""" 
    pass 


def handle_prev_history(history_index, replaced, old_history, 
         input_replaced, history_modified): 
    """Return 'input_replaced' and 'history_modified' without change.""" 
    return (history_index, input_replaced, history_modified) 


def handle_next_history(history_index, replaced, old_history, 
         input_replaced, history_modified): 
    """Also return 'input_replaced' and 'history_modified'.""" 
    return (history_index, input_replaced, history_modified) 


def finalise_history(history_index, response, old_history, 
        input_replaced, history_modified): 
    """Don't change nonexistent history.""" 
    pass 

En shared_stuff.py:

"""Provide platform-independent functions and variables.""" 
ANSI = { 
    'CSI': '\x1b[', 
    'up': 'A', 
    'down': 'B', 
    'right': 'C', 
    'left': 'D', 
    'end': 'F', 
    'home': 'H', 
    'enter': '\r', 
    '^C': '\x03', 
    '^D': '\x04', 
    '^V': '\x16', 
    '^Z': '\x1a', 
} 


def move_cursor(direction, count=1): 
    """Move the text cursor 'count' times in the specified direction.""" 
    if direction not in ['up', 'down', 'right', 'left']: 
     raise ValueError("direction should be either 'up', 'down', 'right' " 
         "or 'left'") 
    # A 'count' of zero still moves the cursor, so this needs to be 
    # tested for. 
    if count != 0: 
     print(ANSI['CSI'] + str(count) + ANSI[direction], end='', flush=True) 


def line_insert(text, extra=''): 
    """Insert text between terminal line and reposition cursor.""" 
    if not extra: 
    # It's not guaranteed that the new line will completely overshadow 
    # the old one if there is no extra. Maybe something was 'deleted'? 
     move_cursor('right', len(text) + 1) 
     print('\b \b' * (len(text)+1), end='', flush=True) 
    print(extra + text, end='', flush=True) 
    move_cursor('left', len(text)) 

Y, por último, en input_no_newline.py:

#!/usr/bin/python3 
"""Provide an input function that doesn't echo a newline.""" 
try: 
from windows_specific import getwch, get_clipboard_data, sigtstp, input_code 
except ImportError: 
    from unix_specific import getwch, get_clipboard_data, sigtstp, input_code 
try: 
    from readline_available import (init_history_index, restore_history, 
            store_and_replace_history, 
            handle_prev_history, handle_next_history, 
            finalise_history) 
except ImportError: 
    from readline_unavailable import (init_history_index, restore_history, 
             store_and_replace_history, 
             handle_prev_history, handle_next_history, 
             finalise_history) 
from shared_stuff import ANSI, move_cursor, line_insert 


def input_no_newline(prompt=''): # pylint: disable=too-many-branches, too-many-statements 
    """Echo and return user input, except for the newline.""" 
    print(prompt, end='', flush=True) 
    response = '' 
    position = 0 
    history_index = init_history_index() 
    input_replaced = False 
    history_modified = False 
    replacements = {} 

    while True: 
     char = getwch() 
     if char in input_code['CSI'][0]: 
      char = getwch() 
      # Relevant input codes are made of two to four characters 
      if char == input_code['CSI'][1]: 
       # *NIX uses at least three characters, only the third is 
       # important 
       char = getwch() 
      if char == input_code['up']: 
       (history_index, input_replaced, history_modified) = (
        handle_prev_history(
         history_index, response, replacements, input_replaced, 
         history_modified)) 
       response = restore_history(history_index, response, position) 
       position = len(response) 
      elif char == input_code['down']: 
       (history_index, input_replaced, history_modified) = (
        handle_next_history(
         history_index, response, replacements, input_replaced, 
         history_modified)) 
       response = restore_history(history_index, response, position) 
       position = len(response) 
      elif char == input_code['right'] and position < len(response): 
       move_cursor('right') 
       position += 1 
      elif char == input_code['left'] and position > 0: 
       move_cursor('left') 
       position -= 1 
      elif char == input_code['end']: 
       move_cursor('right', len(response) - position) 
       position = len(response) 
      elif char == input_code['home']: 
       move_cursor('left', position) 
       position = 0 
      elif char == input_code['del'][0]: 
       if ''.join(input_code['del']) == '3~': 
        # *NIX uses '\x1b[3~' as its del key code, but only 
        # '\x1b[3' has currently been read from sys.stdin 
        getwch() 
       backlog = response[position+1 :] 
       response = response[:position] + backlog 
       line_insert(backlog) 
     elif char == input_code['backspace']: 
      if position > 0: 
       backlog = response[position:] 
       response = response[: position-1] + backlog 
       print('\b', end='', flush=True) 
       position -= 1 
       line_insert(backlog) 
     elif char == input_code['^C']: 
      raise KeyboardInterrupt 
     elif char == input_code['^D']: 
      raise EOFError 
     elif char == input_code['^V']: 
      paste = get_clipboard_data() 
      backlog = response[position:] 
      response = response[:position] + paste + backlog 
      position += len(paste) 
      line_insert(backlog, extra=paste) 
     elif char == input_code['^Z']: 
      sigtstp() 
     elif char == input_code['enter']: 
      finalise_history(history_index, response, replacements, 
          input_replaced, history_modified) 
      move_cursor('right', len(response) - position) 
      return response 
     else: 
      backlog = response[position:] 
      response = response[:position] + char + backlog 
      position += 1 
      line_insert(backlog, extra=char) 


def main(): 
    """Called if script isn't imported.""" 
    # "print(text, end='')" is equivalent to "print text,", and 'flush' 
    # forces the text to appear, even if the line isn't terminated with 
    # a '\n' 
    print('Hello, ', end='', flush=True) 
    name = input_no_newline() # pylint: disable=unused-variable 
    print(', how do you do?') 


if __name__ == '__main__': 
    main() 

Como se puede ver, se trata de una gran cantidad de trabajo para no mucho ya que se necesita para hacer frente a los diferentes operativo sistemas y, básicamente, reimplementar una función incorporada en Python en lugar de C. Recomendaría que simplemente use la clase TempHistory más simple que hice en otra respuesta, lo que deja todo el manejo complicado de la lógica a la función incorporada.

2

Puede usar getpass en lugar de raw_input si no desea que forme una nueva línea.

import sys, getpass 

def raw_input2(value="",end=""): 
    sys.stdout.write(value) 
    data = getpass.getpass("") 
    sys.stdout.write(data) 
    sys.stdout.write(end) 
    return data 
Cuestiones relacionadas