How to colourise user input in Python? - python

I'm creating a command-line calculator tool, and I'd like to get it to format the user's input as they type, similar to what Fish and Powershell do (screenshots).
Currently, the only easy method for doing this that I can think of is getting characters one at a time, and then reprinting the formatted line to the screen.
# This code is probably very dodgy,
# it's just to give you an idea what I'm thinking
# Use external getch module to get input without echoing to screen
from getch import getch
inp = ""
while True:
char = getch()
if char == '\n': break
if char == '\b': inp = inp[:-1]
else: inp += char
# Print with carriage return so that next input overwrites it
print(colourFormatInput(inp) + '\r')
# Outside loop: process input and devliver results
Whilst this would techincally work, I feel like it is a ton of manual effort, and would only become more complex if I wanted to add functionality such as using arrow keys to move the cursor's position.
Is there a simple way to get this kind of functionality without having to code all of it up myself?

I use the library colorama.
import colorama
import sys
def hook(tp, *args):
if tp is KeyboardInterrupt:
print(colorama.Fore.RESET)
exit()
def colored_input(text: str, color):
sys.excepthook = hook
inp = input(text + color)
print(colorama.Fore.RESET, end="", flush=True)
sys.excepthook = sys.__excepthook__
return inp
name = colored_input("What's your name? ", colorama.Fore.RED)
age = colored_input("What's your age? ", colorama.Fore.YELLOW)
print(f"Nice to meet you {name}({age})")
I use sys.excepthook to catch the KeyboardInterrupt so I can reset the color back when the user types CTRL+C, and then I set the original excepthook back (sys.__excepthook__)

You can do this with prompt_toolkit
here is the documentation if you need:
Getting Started
Asking for input/prompts
You can add Syntax Highlighting to input by the following as given in examples:
Adding syntax highlighting is as simple as adding a lexer. All of the Pygments lexers can be used after wrapping them in a PygmentsLexer. It is also possible to create a custom lexer by implementing the Lexer abstract base class.
from pygments.lexers.html import HtmlLexer
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.lexers import PygmentsLexer
text = prompt('Enter HTML: ', lexer=PygmentsLexer(HtmlLexer))
print('You said: %s' % text)
In the same way as above you can create custom prompt_toolkit.lexers.Lexer for calculator highlighting just like the following example. Here I create a custom helper class:
from typing import Callable
from prompt_toolkit.document import Document
from prompt_toolkit.formatted_text.base import StyleAndTextTuples
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.shortcuts import prompt
import prompt_toolkit.lexers
import re
class CustomRegexLexer(prompt_toolkit.lexers.Lexer):
def __init__(self, regex_mapping):
super().__init__()
self.regex_mapping = regex_mapping
def lex_document(self, document: Document) -> Callable[[int], StyleAndTextTuples]:
def lex(_: int):
line = document.text
tokens = []
while len(line) != 0:
for pattern, style_string in self.regex_mapping.items():
match: re.Match = pattern.search(line)
if not match:
continue
else:
# print(f"found_match: {match}")
pass
match_string = line[:match.span()[1]]
line = line[match.span()[1]:]
tokens.append((style_string, match_string))
break
return tokens
return lex
Now with the above helper class implemented we can create our regex patterns and their respective styles, to learn more about what you can have in styling string go to this page
# Making regex for different operators. Make sure you add `^` anchor
# to the start of all the patterns
operators_allowed = ["+", "-", "/", "*", "(", ")", "=", "^"]
operators = re.compile("^["+''.join([f"\\{x}" for x in operators_allowed])+"]")
numbers = re.compile(r"^\d+(\.\d+)?")
text = re.compile(r"^.")
regex_mapping = {
operators: "#ff70e5", # Change colors according to your requirement
numbers: "#ffa500",
text: "#2ef5ff",
}
MyCalculatorLexer = CustomRegexLexer(regex_mapping)
With the lexers created you can now use the lexer in the function prompt:
text = prompt("Enter Equation: ", lexer=MyCalculatorLexer)
# Or
def input_maths(message):
return prompt(message, lexer=MyCalculatorLexer)
text = input_maths("Enter Equation: ")
Here is some example output:
And now everything works. Also do check out prompt_toolkit, you can
create tons of custom things as shown in there gallery

Another example:
CGREEN = '\33[32m'
CYELLOW = '\33[33m'
CBLUE = '\33[34m'
CVIOLET = '\33[35m'
CBEIGE = '\33[36m'
CWHITE = '\33[37m'
CGREY = '\33[90m'
CRED = '\033[91m'
CYELLOW = '\33[33m'
CYELLOW2 = '\33[93m'
CEND = '\033[0m'
print(CGREEN + "This is green text" + CEND)
print(CYELLOW + "This is yellow text" + CEND)
# Another interesting example (courtesy: https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html)
for i in range(0, 16):
for j in range(0, 16):
code = str(i * 16 + j)
colorCode = u"\u001b[48;5;" + code + "m"
print(colorCode + " Color {} ".format(code) + CEND)

Related

readline() tab completion automatically in uppercase and replace in place?

Using the library readline I managed to provide tab completion when using input(). My code until now looks like this:
import readline
class TabComplete:
def __init__(self, wordList):
self.wordList = wordList
def complete(self,text,state):
results = [x for x in self.wordList if x.startswith(text)] + [None]
return results[state]
readline.parse_and_bind("tab: complete")
tabComplete = ["IF", "IF_ELSE", "FOR", "WHILE"]
completer = TabComplete(tabComplete)
readline.set_completer(completer.complete)
userTyped = input("prompt > ")
If I start typing I and then hit twice on tab it will propose me IF and IF_ELSE as expected.
What I am searching now is:
if I start typing i and then hit twice tab is there a way to still make it propose IF and IF_ELSE ?
if I type IF EL can I tell him on real time to replace the space by _ so the completion works?
you could always convert your text to upper and replace spaces with "_"
def complete(self,text,state):
text = tex.upper().replace(" ","_")
results = [x for x in self.wordList if x.startswith(text)] + [None]
return results[state]

print on same input line Python 3.x [duplicate]

I know I can stop print from writing a newline by adding a comma
print "Hello, world!",
# print("Hello, world!", end='') for Python 3.x
But how do I stop raw_input (or input for Python 3.x) from writing a newline?
print "Hello, ",
name = raw_input()
print ", how do you do?"
Result:
Hello, Tomas
, how do you do?
Result I want:
Hello, Tomas, how do you do?
But how do I stop raw_input from writing a newline?
In short: You can't.
raw_input() will always echo the text entered by the user, including the trailing newline. That means whatever the user is typing will be printed to standard output.
If you want to prevent this, you will have to use a terminal control library such as the curses module. This is not portable, though -- for example, curses in not available on Windows systems.
I see that nobody has given a working solution, so I decided I might give it a go.
As Ferdinand Beyer said, it is impossible to get raw_input() to not print a new line after the user input. However, it is possible to get back to the line you were before.
I made it into an one-liner. You may use:
print '\033[{}C\033[1A'.format(len(x) + y),
where x is an integer of the length of the given user input and y an integer of the length of raw_input()'s string. Though it might not work on all terminals (as I read when I learned about this method), it works fine on mine. I'm using Kubuntu 14.04.
The string '\033[4C' is used to jump 4 indexes to the right, so it would be equivalent to ' ' * 4. In the same way, the string '\033[1A' is used to jump 1 line up. By using the letters A, B, C or D on the last index of the string, you can go up, down, right and left respectively.
Note that going a line up will delete the existing printed character on that spot, if there is one.
This circumvents it, somewhat, but doesn't assign anything to variable name:
print("Hello, {0}, how do you do?".format(raw_input("Enter name here: ")))
It will prompt the user for a name before printing the entire message though.
You can use getpass instead of raw_input if you don't want it to make a new line!
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
An alternative to backtracking the newline is defining your own function that emulates the built-in input function, echoing and appending every keystroke to a response variable except Enter (which will return the response), whilst also handling Backspace, Del, Home, End, arrow keys, line history, KeyboardInterrupt, EOFError, SIGTSTP and pasting from the clipboard. It's very simple.
Note that on Windows, you'll need to install pyreadline if you want to use line history with the arrow keys like in the usual input function, although it's incomplete so the functionality is still not quite right. In addition, if you're not on v1511 or greater of Windows 10, you'll need to install the colorama module (if you're on Linux or macOS, nothing needs to be done).
Also, due to msvcrt.getwch using '\xe0' to indicate special characters, you won't be able to type 'à'. You should be able to paste it though.
Below is code that makes this work on updated Windows 10 systems (at least v1511), Debian-based Linux distros and maybe macOS and other *NIX operating systems. It should also work regardless of whether you have pyreadline installed on Windows, though it'll lack some functionality.
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',
}
In 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', '~'],
}
In 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)
In 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
In 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))
And finally, in 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()
As you can see, it's a lot of work for not that much since you need to deal with the different operating systems and basically reimplement a built-in function in Python rather than C. I'd recommend that you just use the simpler TempHistory class I made in another answer, which leaves all the complicated logic-handling to the built-in function.
Like Nick K. said, you'll need to move the text cursor back to before the newline was echoed. The problem is that you can't easily get the length of the previous line in order to move rightward, lest you store every string printed, prompted and inputted in its own variable.
Below is a class (for Python 3) that fixes this by automatically storing the last line from the terminal (provided you use its methods). The benefit of this compared to using a terminal control library is that it'll work in the standard terminal for both the latest version of Windows as well as *NIX operating systems. It'll also print the 'Hello, ' prompt before getting input.
If you're on Windows but not v1511 of Windows 10, then you'll need to install the colorama module or else this won't work, since they brought ANSI cursor movement support in that version.
# 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?)
As already answered, we can't stop input() from writing a newline. Though it may not satisfy your expectation, somehow the following codes satisfy the condition if -
you don't have any issue clearing the screen
import os
name = input("Hello, ")
os.system("cls") # on linux or mac, use "clear"
print(f"Hello, {name}, how do you do?")
or no issue using the gui dialog box, as dialog box disappears after taking user input, you will see exactly what you expected
import easygui
name = easygui.enterbox("Hello, ", "User Name")
print("Hello, "+name+", how do you do?")
I think you can use this:
name = input("Hello , ")
It should be something like this:-
print('this eliminates the ', end=' ')
print('new line')
The output is this:-
this eliminates the new line.

Replacing multiple words in a string from different data sets in Python

Essentially I have a python script that loads in a number of files, each file contains a list and these are used to generate strings. For example: "Just been to see $film% in $location%, I'd highly recommend it!" I need to replace the $film% and $location% placeholders with a random element of the array of their respective imported lists.
I'm very new to Python but have picked up most of it quite easily but obviously in Python strings are immutable and so handling this sort of task is different compared to other languages I've used.
Here is the code as it stands, I've tried adding in a while loop but it would still only replace the first instance of a replaceable word and leave the rest.
#!/usr/bin/python
import random
def replaceWord(string):
#Find Variable Type
if "url" in string:
varType = "url"
elif "film" in string:
varType = "film"
elif "food" in string:
varType = "food"
elif "location" in string:
varType = "location"
elif "tvshow" in string:
varType = "tvshow"
#LoadVariableFile
fileToOpen = "/prototype/default_" + varType + "s.txt"
var_file = open(fileToOpen, "r")
var_array = var_file.read().split('\n')
#Get number of possible variables
numberOfVariables = len(var_array)
#ChooseRandomElement
randomElement = random.randrange(0,numberOfVariables)
#ReplaceWord
oldValue = "$" + varType + "%"
newString = string.replace(oldValue, var_array[randomElement], 1)
return newString
testString = "Just been to see $film% in $location%, I'd highly recommend it!"
Test = replaceWord(testString)
This would give the following output: Just been to see Harry Potter in $location%, I'd highly recommend it!
I have tried using while loops, counting the number of words to replace in the string etc. however it still only changes the first word. It also needs to be able to replace multiple instances of the same "variable" type in the same string, so if there are two occurrences of $film% in a string it should replace both with a random element from the loaded file.
The following program may be somewhat closer to what you are trying to accomplish. Please note that documentation has been included to help explain what is going on. The templates are a little different than yours but provide customization options.
#! /usr/bin/env python3
import random
PATH_TEMPLATE = './prototype/default_{}s.txt'
def main():
"""Demonstrate the StringReplacer class with a test sting."""
replacer = StringReplacer(PATH_TEMPLATE)
text = "Just been to see {film} in {location}, I'd highly recommend it!"
result = replacer.process(text)
print(result)
class StringReplacer:
"""StringReplacer(path_template) -> StringReplacer instance"""
def __init__(self, path_template):
"""Initialize the instance attribute of the class."""
self.path_template = path_template
self.cache = {}
def process(self, text):
"""Automatically discover text keys and replace them at random."""
keys = self.load_keys(text)
result = self.replace_keys(text, keys)
return result
def load_keys(self, text):
"""Discover what replacements can be made in a string."""
keys = {}
while True:
try:
text.format(**keys)
except KeyError as error:
key = error.args[0]
self.load_to_cache(key)
keys[key] = ''
else:
return keys
def load_to_cache(self, key):
"""Warm up the cache as needed in preparation for replacements."""
if key not in self.cache:
with open(self.path_template.format(key)) as file:
unique = set(filter(None, map(str.strip, file)))
self.cache[key] = tuple(unique)
def replace_keys(self, text, keys):
"""Build a dictionary of random replacements and run formatting."""
for key in keys:
keys[key] = random.choice(self.cache[key])
new_string = text.format(**keys)
return new_string
if __name__ == '__main__':
main()
The varType you are assigning will be set in only one of your if-elif-else sequence and then the interpreter will go outside. You would have to run all over it and perform operations. One way would be to set flags which part of sentence you want to change. It would go that way:
url_to_change = False
film_to_change = False
if "url" in string:
url_to_change = True
elif "film" in string:
film_to_change = True
if url_to_change:
change_url()
if film_to_change:
change_film()
If you want to change all occurances you could use a foreach loop. Just do something like this in the part you are swapping a word:
for word in sentence:
if word == 'url':
change_word()
Having said this, I'd reccomend introducing two improvements. Push changing into separate functions. It would be easier to manage your code.
For example function for getting items from file to random from could be
def load_variable_file(file_name)
fileToOpen = "/prototype/default_" + file_name + "s.txt"
var_file = open(fileToOpen, "r")
var_array = var_file.read().split('\n')
var_file.clos()
return var_array
Instead of
if "url" in string:
varType = "url"
you could do:
def change_url(sentence):
var_array = load_variable_file(url)
numberOfVariables = len(var_array)
randomElement = random.randrange(0,numberOfVariables)
oldValue = "$" + varType + "%"
return sentence.replace(oldValue, var_array[randomElement], 1)
if "url" in sentence:
setnence = change_url(sentence)
And so on. You could push some part of what I've put into change_url() into a separate function, since it would be used by all such functions (just like loading data from file). I deliberately do not change everything, I hope you get my point. As you see with functions with clear names you can write less code, split it into logical, reusable parts, no needs to comment the code.
A few points about your code:
You can replace the randrange with random.choice as you just
want to select an item from an array.
You can iterate over your types and do the replacement without
specifying a limit (the third parameter), then assign it to the same object, so you keep all your replacements.
readlines() do what you want for open, read from the file as store the lines as an array
Return the new string after go through all the possible replacements
Something like this:
#!/usr/bin/python
import random
def replaceWord(string):
#Find Variable Type
types = ("url", "film", "food", "location", "tvshow")
for t in types:
if "$" + t + "%" in string:
var_array = []
#LoadVariableFile
fileToOpen = "/prototype/default_" + varType + "s.txt"
with open(fname) as f:
var_array = f.readlines()
tag = "$" + t + "%"
while tag in string:
choice = random.choice(var_array)
string = string.replace(tag, choice, 1)
var_array.remove(choice)
return string
testString = "Just been to see $film% in $location%, I'd highly recommend it!"
new = replaceWord(testString)
print(new)

Why does my program add ('', ' to the name of my file?

Here is my code (sorry for the messy code):
def main():
pass
if __name__ == '__main__':
main()
from easygui import *
import time
import os
import random
import sys
##multenterbox(msg='Fill in values for the fields.', title=' ', fields=(), values=())
msg = "Enter your personal information"
title = "Credit Card Application"
fieldNames = ["First name",'Last name','email',"Street Address","City","State","ZipCode",'phone','phone 2)']
fieldValues = [] # we start with blanks for the values
fieldValues = multenterbox(msg,title, fieldNames)
# make sure that none of the fields was left blank
def make(x):
xys = x,".acc"
xyzasd = str(xys)
tf = open(xyzasd,'a+')
tf.writelines(lifes)
tf.writelines("\n")
tf.writelines("credits = 0")
tf.close
def add(x):
nl = "\n"
acc = ".acc"
xy = x + acc
exyz = xy
xyz = exyz
xxx = str(xyz)
tf = open('accounts.dat',"a+")
tf.writelines(nl)
tf.writelines(xxx)
tf.close
while 1:
if fieldValues == None: break
errmsg = ""
for i in range(len(fieldNames)-1):
if fieldValues[i].strip() == "":
errmsg += ('"%s" is a required field.\n\n' % fieldNames[i])
if errmsg == "":
break # no problems found
fieldValues = multenterbox(errmsg, title, fieldNames, fieldValues)
names = enterbox(msg= ('confirm FIRST name and the FIRST LETTER of the persons LAST name'))
##txt = "acc"
##na = str(name)
##name = (names)
life = ( str(fieldValues))
lifes = life,'\n'
herro = ("Reply was: %s" % str(fieldValues))
correct = buttonbox(msg=(herro,'\n is that correct'),choices = ('yes','no','cancel'))
if correct == "yes":
make(names)
add(names)
elif correct == "no":
os.system('openacc.py')
time.sleep(0.5)
sys.exit()
else:
os.system('cellocakes-main.py')
sys.exit()
os.system('cellocakes-main.py')
I don't know what the problem is also I am sorry about how sloppy it was programmed I have a white board to help me out still new to programming (I'm only 13) sorry. Personally I think the issue is in the def add area's syntax but because I am still new I don't see the issue personally I am hoping to have a more experienced programmer help me out.
This is an answer not directly answering your question.
Alas, comment fields are STILL not capable to hold formatted code, so I choose this way.
def main():
pass
if __name__ == '__main__':
main()
This is a nice coding pattern, but used by you in a useless way.
It is supposed to prevent executing of the stuff if it is imported as a module and not executed as a script.
Nevertheless, it is not bad to use it always, but then put your code inside the main() function instead of adding it below.
fieldNames = ["First name",'Last name','email',"Street Address","City","State","ZipCode",'phone','phone 2)']
There is a ) too much.
fieldValues = [] # we start with blanks for the values
fieldValues = multenterbox(msg,title, fieldNames)
The second line makes the first one useless, as you don't use fieldValues in-between.
It would be different if you expected multenterbox() to fail and would want [] as a default value.
def make(x):
xys = x,".acc"
xyzasd = str(xys)
tf = open(xyzasd,'a+')
tf.writelines(lifes)
tf.writelines("\n")
tf.writelines("credits = 0")
tf.close
You was already told about this: x, ".acc" creates a tuple, not a string. To create a string, use x + ".acc".
Besides, your close call is no call, because it is missing the (). This one just references the function and ignores the value.
A better way to write this would be (please name your variables appropriately)
with open(xyzs, 'a+') as tf:
tf.writelines(lifes)
tf.writelines("\n")
tf.writelines("credits = 0")
The with statement automatically closes the file, even if an error occurs.
Besides, you use writelines() wrong: it is supposed to take a sequence of strings and write each element to the file. As it doesn't add newlines in-between, the result looks the same,. but in your case, it writes each byte separately, making it a little bit more inefficient.
Additionally, you access the global variable lifes from within the function. You should only do such things if it is absolutely necessary.
def add(x):
Here the same remarks hold as above, plus
xy = x + acc
exyz = xy
xyz = exyz
xxx = str(xyz)
why that? Just use xy; the two assignments do nothing useful and the str() call is useless as well, as you already have a string.
for i in range(len(fieldNames)-1):
if fieldValues[i].strip() == "":
errmsg += ('"%s" is a required field.\n\n' % fieldNames[i])
Better:
for name, value in zip(fieldNames, fieldValues):
if not value.strip(): # means: empty
errmsg += '"%s" is a required field.\n\n' % name
Then:
life = ( str(fieldValues))
makes a string from a list.
lifes = life,'\n'
makes a tuple from these 2 strings.
os.system('openacc.py')
os.system('cellocakes-main.py')
Please don't use os.system(); it is deprecated. Better use the subprocess module.
The problem of the question is here:
# assign the tuple (x, ".acc") to xys
xys = x,".acc"
# now xyzasd is the tuple converted to a string, thus
# making the name of your file into '("content of x", ".acc")'
xyzasd = str(xys)
# and open file named thus
tf = open(xyzasd,'a+')
What you wanted to do is:
# use proper variable and function names!
def make_account(account):
filename = account + '.acc'
the_file = open(filename, 'a+')
....
On the other hand there are other problems with your code, for example the
def main():
pass
if __name__ == '__main__':
main()
is utterly useless.

Zelle Graphics interface issue

I'm using Zelle Graphics library and I'm having trouble replacing graphics objects (which, in this case, happens to be text objects).
Here's the code:
from Graphics import *
winName = "Window"
win = Window(winName,600,500)
win.setBackground(Color('silver'))
title = Text((300,20),"Zack's Flash Card Maker")
title.draw(win)
p1 = Rectangle((50, 100),(550,400))
p1.setFill(Color("black"))
p1.draw(win)
class FlashCard:
def __init__(self):
self.commands = {'addQuestion':self.addQuestion,'startGame':self.startGame}
self.stack = []
self.questions = {}
self.questionAnswered = False
self.questionsCorrect = 0
self.questionsIncorrect = 0
def addQuestion(self):
question = ' '.join(self.stack)
self.stack = []
answer = input(question)
self.questions[question] = answer
def startGame(self):
for question in self.questions:
if(self.questionAnswered == False):
answer=input(question)
questionText = Text((300,150),question)
questionText.setFill(Color("white"))
questionText.draw(win)
if(answer == self.questions[question]):
questionAnswer = Text((300,200),answer + " is correct!")
questionAnswer.setFill(Color("green"))
questionAnswer.draw(win)
self.questionsCorrect = self.questionsCorrect + 1
continue
else:
questionAnswer = Text((300,200),answer + " is incorrect. Study this one.")
questionAnswer.setFill(Color("red"))
questionAnswer.draw(win)
self.questionsIncorrect = self.questionsIncorrect + 1
continue
def interpret(self,expression):
for token in expression.split():
if token in self.commands:
operator = self.commands[token]
operator()
else:
self.stack.append(token)
i = FlashCard()
i.interpret('What is your dog\'s name? addQuestion')
i.interpret('What is your favorite thing to do? addQuestion')
i.interpret('startGame')
This is essentially a mini flash card program I'm making. It takes the interpret commands at the bottom and executes them based on the dictionary in the FlashCard class. It basically works: it does the correct text objects. However, text begins to overlap other text objects because it re-draws. I've tried moving the .draw function all over, but it either doesn't appear at all or it overlaps.
Anyone have any suggestions? I want the text to replace for each new flashcard question.
Thanks!
there's an undraw() command that you need to use if you want to make something invisible. I'd recommend placing it right before your continue statements. It's used like
questionText.undraw()
questionAnswer.undraw()
Alternatively, you can use the del command to get rid of each questionText/questionAnswer instance when you're done with it. That's probably a better option since you're actually freeing up the memory that way instead of storing data and not doing anything with it.
You can use setText method to change the text.
example:
string = Text(Point(1, 1), 'original string')
sting.setText('new string')

Categories

Resources