Let's say that I have an input("> ")
, and if you try to input a lowercase "Hello, world!" it will look like this:
> HELLO WORLD!
Let's say that I have an input("> ")
, and if you try to input a lowercase "Hello, world!" it will look like this:
> HELLO WORLD!
No, there isn't a practical way to do this
It might be possible to edit the function somewhere deep in the source code, but that would be messy. You'd be best off writing a custom input function, using something like curses or keyboard to deal with the IO.
You could somewhat emulate this with pyautogui
.
import pyautogui, ctypes
def upper_input(msg:str='write something: '):
if not ctypes.WinDLL("User32.dll").GetKeyState(0x14):
pyautogui.press('capslock')
input(msg)
pyautogui.press('capslock')
upper_input()
Here is a version that uses sys.stdout
and pynput
.
IMO, it's more useful to have a function that can be configured to output uppercase, as-well-as lowercase, capitalized, title or any other string method. It may also be useful to pass everything through a regex
, to restrict input.
import sys, re, operator
from pynput import keyboard
from dataclasses import dataclass, field
#commands / print only
BACKSPACE = 8
ENTER = 13
L_ARROW = 37
R_ARROW = 39
#ranges to "keep" from
ALL = tuple((9, *range(32, 127))) #all accepted characters range
ALL_AE = (9,32) #AttributeError accepted characters range (tab,space,np_dot)
ALL_TE = tuple((46, *range(48,58))) #TypeError accepted characters range (0-9)
#numpad vk range
NPVK = range(96,106)
NPVK_OFS = 48
#numpad decimal
DCVK = 110
DCVK_OFS = 64
@dataclass
class _data:
input:list[str] = field(default_factory=list[str])
caret:int = 0
#conveniences
@property
def output(self) -> str:
return "".join(self.input)
@property
def xput(self) -> list[str]:
return (self.input or [None]*(self.caret+1))
def i(self, func:callable, d:int=0) -> int:
return (-1, self.caret+d)[func(self.caret,len(self.input))]
""" INPUT 2
*all arguments are optional
@prompt - the user prompt
@fmt - str.lower, str.upper, str.title... or any similar function reference
@expr/flags - `re.compile(@expr, @flags).match(@fmt(input_data))`
@req - `re.compile(@req, @flags).match(final_data)`
supports:
* backspace
* numpad
* arrow keys (locked to span of input)
! home, end, pgup, pgdn, ins, del, prt sc & pause, do nothing
"""
def input2(prompt:str='', fmt:callable=None, expr:str=None, flags:int=0, req:str=None) -> str:
#default
fmt = fmt or (lambda c: c)
#for matching `fmt` results
match = re.compile(fr'{expr or r"."}|[\b\n]$', flags).match
#for matching final results, upon submission
req = False if not req else re.compile(fr'{req}', flags).match
#print prompt
sys.stdout.write(prompt)
sys.stdout.flush()
#input storage, caret position
d = _data()
#keypress handler
def emulator(key) -> bool:
#get and test ordinal, one way or another
try:
i = ord(key.char)
ok = i in ALL
except AttributeError:
i = key.value.vk
ok = i in ALL_AE + (BACKSPACE, ENTER)
except TypeError:
i = key.vk
#reformat numpad vk to number ord
i -= NPVK_OFS * (i in NPVK)
#reformat numpad decimal vk to dot ord
i -= DCVK_OFS * (i == DCVK)
ok = i in ALL_TE
if ok:
#get character
c = chr(i)
e = False #assume enter is not being pressed
#character filtering / storage
if t := (i in ALL):
#copy
tmp = d.input[:]
#modify
if d.caret >= len(tmp): tmp.append(c)
else : tmp[d.caret] = c
#format
tmp = fmt(''.join(tmp))
#check
if not match(tmp): return True
#update
d.input = list(tmp)
#backspace
elif i==BACKSPACE:
#accomodate for maximum tab space and/or character to be popped
n = sum(3*(c=='\t') for c in d.input)+1
#adjust console if d.input was modified
if d.xput.pop(d.i(operator.le, -1)):
L = len(d.input)
#blank line
blank = chr(0)*(len(prompt)+L+n)
#update caret
d.caret -= 1
sys.stdout.write(f'\r{blank}\r') #erase line
sys.stdout.write(f'{prompt}{d.output}') #reprint line
sys.stdout.write('\b'*(L-d.caret)) #place caret
sys.stdout.flush()
return True
#enter with required
elif (e := (i==ENTER)) and req:
if not req(d.output): return True
#decide the proper character to print
tmp = d.xput[d.i(operator.lt)]
tmp = ('\n', tmp or '')[(not e) & t]
#update caret
d.caret += 1
#print character
sys.stdout.write(tmp)
sys.stdout.flush()
#quit on enter
return not e
#arrow keys
elif i in (L_ARROW, R_ARROW):
r = i==R_ARROW
if -r < d.caret <= (len(d.input)-r):
tmp = d.xput[d.i(operator.lt)]
tmp = ('\b',tmp or '')[r]
d.caret += (-1,1)[r]
sys.stdout.write(tmp)
sys.stdout.flush()
return True
#listen for keys
with keyboard.Listener(on_press=emulator) as listener:
listener.join()
return d.output
if __name__ == '__main__':
strawman = input2('Full Name: ', str.upper, r'^[a-z ]+$', re.I, r'^[a-z]{2,} [a-z]{2,}$')
print(strawman)
repository: input2 (github)
In response to TheTridentGuy's answer, here is that (slightly) impractical way:
If you're restricting yourself to UNIX, then you can use the built-in termios
and tty
modules to put the terminal in cbreak mode. From there, you can uppercase each individual character you get and use print()
and sys.stdout.flush()
. You'll also want to register a function with atexit
to restore the terminal then unregister at the end of your function, just to make sure the terminal settings are always restored (if you don't do this, it'll leave your terminal in a weird mode and make other parts of your program not work). Part of this idea was inspired by this answer:
import atexit
import sys
import select
import tty
import termios
def upper_input(prompt):
# get current terminal settings
old_settings = termios.tcgetattr(sys.stdin)
# Returns true if there's data for us to read using select
def isData():
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
# restores the terminal settings to what they were
def restoreSettings():
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
# print the prompt
print(prompt, end='')
sys.stdout.flush()
try:
# change the terminal settings
tty.setcbreak(sys.stdin.fileno())
atexit.register(restoreSettings)
# keep looping until we get a newline, adding each character to a
# growing string and printing out each character we get as uppercase
s = ''
while 1:
if isData():
c = sys.stdin.read(1)
print(c.upper(), end='')
sys.stdout.flush()
if c == '\n':
break
s += c.upper()
finally:
# restore the terminal settings
restoreSettings()
atexit.unregister(restoreSettings)
return s
inp = upper_input('> ')
print(f'Hi, {inp}')
You could probably port this to Windows using something similar, but I don't have a Windows machine.