Возможно ли получить пользовательский ввод без вставки новой строки? - PullRequest
14 голосов
/ 24 августа 2011

Я знаю, что могу остановить печать от написания новой строки, добавив запятую

print "Hello, world!",

Но как мне остановить raw_input от написания новой строки?

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

Результат:

Привет, Томас
как дела?

Результат, который я хочу:

Привет, Томас, как поживаешь?

Ответы [ 7 ]

8 голосов
/ 24 августа 2011

Но как мне остановить raw_input от записи новой строки?

Короче говоря: вы не можете.

raw_input() всегда будет повторять текст, введенныйпользователь, включая завершающий перевод строки.Это означает, что все, что печатает пользователь, будет напечатано на стандартный вывод.

Если вы хотите предотвратить это, вам придется использовать библиотеку управления терминалом, такую ​​как модуль curses.Однако это не переносимо - например, curses недоступно в системах Windows.

7 голосов
/ 25 марта 2016

Я вижу, что никто не дал рабочего решения, поэтому я решил попробовать. Как сказал Фердинанд Бейер , невозможно заставить raw_input() не печатать новую строку после ввода пользователя. Тем не менее, можно вернуться к той линии, которой вы были раньше. Я сделал это в одну строку. Вы можете использовать:

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

где x - целое число длины данного пользовательского ввода, а y - целое число длины строки raw_input(). Хотя он может работать не на всех терминалах (как я читал, когда узнал об этом методе), он отлично работает на моем. Я использую Kubuntu 14.04.
Строка '\033[4C' используется для перехода на 4 индекса вправо, поэтому она будет эквивалентна ' ' * 4. Таким же образом строка '\033[1A' используется для перехода на 1 строку вверх. Используя буквы A, B, C или D в последнем индексе строки, вы можете идти вверх, вниз, вправо и влево соответственно.

Обратите внимание, что при переходе вверх по строке удаляется существующий печатный символ в этом месте, если он есть.

7 голосов
/ 24 августа 2011

Это несколько обходит, но ничего не присваивает переменной name:

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

Тем не менее, пользователю будет предложено ввести имя перед печатью всего сообщения.

4 голосов
/ 22 декабря 2017

Вы можете использовать getpass вместо raw_input, если не хотите, чтобы он создавал новую строку!

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
1 голос
/ 04 января 2017

Альтернативой возврату новой строки является определение собственной функции, которая эмулирует встроенную функцию input, повторяя и добавляя каждое нажатие клавиши в переменную response, за исключением Enter (которая будет возвращать ответ ), а также обрабатывает Backspace , Del , Home , End , клавиши со стрелками, историю строк, KeyboardInterrupt, EOFError, SIGTSTP и вставку из буфера обмена. Это очень просто.

Обратите внимание, что в Windows вам необходимо установить pyreadline, если вы хотите использовать историю строк с клавишами со стрелками, как в обычной функции input, хотя она неполная, поэтому функциональность по-прежнему не совсем верна. Кроме того, если вы не используете Windows 10 v1511 или выше, вам нужно установить модуль colorama (если вы работаете в Linux или macOS, ничего не нужно делать).

Кроме того, из-за msvcrt.getwch, использующего '\ xe0' для обозначения специальных символов, вы не сможете набирать 'à'. Вы должны быть в состоянии вставить это все же.

Ниже приведен код, который делает эту работу на обновленных системах Windows 10 (по крайней мере v1511), дистрибутивах Linux на основе Debian и, возможно, macOS и других * операционных системах NIX. Он также должен работать независимо от того, установлен ли в Windows pyreadline, хотя некоторые функции будут отсутствовать.

In 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',
}

В 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', '~'],
}

В 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)

В 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

В 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))

И, наконец, в 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()

Как видите, это не так уж много работы, так как вам нужно иметь дело с различными операционными системами и в основном переопределять встроенную функцию в Python, а не C. Я бы порекомендовал вам просто использовать Более простой TempHistory класс, который я сделал в другом ответе, который оставляет всю сложную обработку логики встроенной функции.

1 голос
/ 03 января 2017

Как сказал Ник К., вам нужно переместить текстовый курсор назад до того, как отобразится новая строка. Проблема в том, что вы не можете легко получить длину предыдущей строки, чтобы двигаться вправо, чтобы не хранить каждую строку, напечатанную, запрошенную и введенную в ее собственной переменной.

Ниже приведен класс (для Python 3), который исправляет это, автоматически сохраняя последнюю строку из терминала (при условии, что вы используете его методы). Преимущество этого по сравнению с использованием библиотеки управления терминалом состоит в том, что она будет работать в стандартном терминале как для самой последней версии Windows, так и для операционных систем * NIX. Он также выведет подсказку «Hello» перед вводом.

Если вы работаете в Windows, но не в v1511 Windows 10, то вам нужно установить модуль colorama, иначе это не сработает, поскольку в этой версии появилась поддержка перемещения курсора ANSI.

# 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 голосов
/ 30 июля 2018

Я думаю, вы можете просто использовать это.

 name=input("Hello , ")
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...