I'm working on a piece of code in Python 3 that acts as an interface for various dead by daylight cheats. I'm using a very basic setup, just input() and os.system() to find and open specific files. It works fine, but there's one small issue.
The interface uses cmd prompt, and I have it set up so that entering numbers 1-4 will open programs and executables used to modify the game. However, some of the programs are required to stay open while others run. For example, the BVHR Session Grabber must be running along with the SaveInjector Interface, because the SaveInjector needs to receive a certain code from the Grabber.
There's a problem here, the code is set up in such a way that you can only run one file at a time. I'm not sure what exactly causes this, but I'll try to explain what happens. When entering the number 1, for example, into the cmd prompt window, it opens the BHVR Session Grabber (as intended). After that, the interface becomes unusable until I close the BHVR Session Grabber. I can't type anything into it while it's active, so I can't open multiple programs at once.
Not entirely sure if this is intended or not, but I'm hoping it's avoidable. If anyone has any knowledge on the issue let me know how to find a way around this in the comments please.
import os.path
def interface():
os.system('cls' if os.name == 'nt' else 'clear')
print("""
\n\nSelect a cheat below:
\n
\n1: BHVR Session Grabber
\n2: SaveInjector Interface
\n3: Rank / Shards Editor
\n4: Exit\n
""")
def checker():
interface()
lst = ['1','2','3','4']
good_input = input(">")
global user_input
user_input = None
while not user_input:
if good_input in lst:
user_input = good_input
else:
print("Enter a valid integer.")
good_input = input(">")
checker()
cwd = os.getcwd()
def selection():
if user_input == '1':
f = (os.path.join(cwd, 'Programs', 'BHVRSession', 'CookieFinder.exe'));
os.system(f)
checker()
selection()
elif user_input == '2':
os.system('cmd /k "cd Programs & cd Injector & SI.exe & cd.. & cd.. & Ultimate.py"')
elif user_input == '3':
f = (os.path.join(cwd, 'Programs', 'RankShards', 'Sender.exe'));
os.system(f)
checker()
selection()
elif user_input == '4':
os.system('cmd /k "taskkill/im py.exe"')
selection()
The problem here is that os.system() is blocking. This means that it will only return and continue executing your Python code after the program it runs finishes. Instead, you should look at the subprocess package to learn how to fork a new process that can run in parallel with your Python program.
Related
I've recently started to code and wanted to try my luck on a beginners program after 10 hrs of Udemy courses.
I've coded a "Guess the number" minigame, where a number is generated between 1-10 and I want the program to restart if someone guesses wrong.
import random
import os
import sys
def restart_program():
python = sys.executable
os.execv(sys.executable, ['python'] + sys.argv)
number = str(random.randrange(1,10))
choice = input("Which number did the PC pick?\n")
if choice == number:
print("You won!")
restart_program()
elif choice != number:
print("You lose!")
restart_program()
For some reason JupyterLab' kernel keeps dying on me the second I input a number.
I've tried restructuring the code and using completely different code but I always kill the kernel.
Can someone tell me if I did smth wrong?
I believe it is not a good idea to spawn a new Python interpreter as means of "restarting" program, especially if you are not its owner (did you spawn the program in the first place? No, a Jupyter kernel did it for you → you should not try to kill it and replace by another process as os.execv does).
Also, depending on what is in sys.argv you may actually invoke a different module which also could lead to a crash. See it for yourself - when running a notebook with IPython kernel sys.argv contains IPython internal commands, you should not play with those!
For this simple beginner program you probably should use a loop:
import random
done = False
while not done:
number = str(random.randrange(1,10))
choice = input("Which number did the PC pick?\n")
if choice == number:
print("You won!")
elif choice != number:
print("You lose!")
if choice == 'x':
done = True
But if you really need to restart the kernel you should use one of the solutions for restarting IPython kernel from code cell (or equivalent for any other kernel that you may be using).
After a run, the user is asked whether he wants to end the programme [with sys.exit()] or restart it [os.system('main.py')].
If he restarts the programme, the programme will run through until he can decide again whether to restart or exit.
If the user then wants to end the programme, however, this is not possible, the programme is restarted anyway.
Also quit() or exit() do not work.
This is the prompt that asks the user to restart or quit:
while (res := input("Do you want to play again [1] oder exit[2]?\n").lower()) not in {"1", "2"}:
pass
if res == "1":
os.system('main.py')
else:
end_game = True # Stops the loop, but is not necessary
print("Sys.exit game")
sys.exit(0)
When I use subprocess.call(sys.executable + ' "' + os.path.realpath(__file__) + '"'),
exiting the program works, but the program is not really restarted [variables set to 0 at the beginning are not at 0].
Small note, the reboot will restart another py file (main.py), which is the main file, with the following content:
class main:
game_logic()
if __name__ == '__main__':
main()
game_logic() is the function from the other Py file in which the query for restarting and exiting is.
import os
import sys
while (res := input("Do you want to play again [1] oder exit[2]?\n").lower()) not in {"1", "2"}:
pass
if res == "1":
python = sys.executable
os.execl(python, python, * sys.argv)
else:
end_game = True # Stops the loop, but is not necessary
print("Sys.exit game")
sys.exit(0)
and
class main:
game_logic()
if __name__ == '__main__':
main()
The example above should work for you. You should use os.execl(...) instead of os.system(...). The last one creates new processes recursively and could cause an out of memory problem. You shouldn't create new processes, instead you want to replace the current process with a new one. This could be done with execl() or other calls from exec family.
To understand it properly, you may want to look here. It referes to C language, but it is kind of the same, because Python is wrapping around native calls.
Like all of the exec functions, execv replaces the calling process
image with a new process image. This has the effect of running a new
progam with the process ID of the calling process. Note that a new
process is not started; the new process image simply overlays the
original process image. The execv function is most commonly used to
overlay a process image that has been created by a call to the fork
function.
I have been able to answer this question myself in the meantime.
I have done the following:
game_logic() is started via another py file (main.py).
At the restart which is executed within the game_logic() with os.system('main.py'), the current py file containing game_logic() is not terminated.
So if the main.py file is restarted, I have the file containing the game_logic() terminate afterwards.
It looks like this:
import os
import sys
while (res := input("Do you want to play again [1] oder exit[2]?\n").lower()) not in {"1", "2"}:
pass
if res == "1":
os.system('main.py')
exit()
else:
exit()
The best way to explain my question is with the help of an example.
Consider a Python Script that asks a question and gives an answer depending on the question.
QandA.py
import os,sys,re
ans = input('How you doin\' \(Say Yes\/No\)')
if (ans == "Yes"):
print("Nice to hear")
elif (ans == "No") :
print("Oh Too Bad")
The above Script would normally prompt the User for an Input, BUT I'd also like it to be called by a Batch Script which will provide the input to the called Python Script.
I'll hardcode these inputs in the Batch File itself (as seen below).
call.bat
set ans="Yes"
D:
cd "Automate Answers"
python "QandA.py"
Is there any way to achieve this (make the Python Script aware of ans) without modifying the Python Script itself to handle optional command line arguments ?
I'd prefer the Batch Script to run as if it were the user giving inputs to the script from an array of variables.
Variables set in .bat, .cmd, should show up as environment variables.
Python can read environment variables passed in via the os.environ
import os
print(os.environ['ans'])
thus you could write:
import os
ans = os.environ.get('ans')
if ans is None: # it was not set, so ask user
ans = input("How you doin' (Say Yes/No)")
if ans == "Yes":
print("Nice to hear")
elif ans == "No":
print("Oh Too Bad")
or even do a function:
def env_or_input(var_name, prompt):
val = os.environ.get(var_name)
if val is None: # it was not set, so ask user
val = input(prompt)
return val
...
ans = env_or_input("ans", "How you doin' (Say Yes/No)")
if ans == "Yes":
print("Nice to hear")
elif ans == "No":
print("Oh Too Bad")
I suppose that echo 'Yes' | python QandA.py would work. You can add several answers in a fixed sequence, separated by newlines:
cat <<EOF | python QandA.py
Yes
No
Why are you asking?
EOF
For longer scenarios when you e.g. have to react to changing prompts, you can use expect.
Piping in a single answer is trivial:
set ans="Yes"
cd /d "D:Automate Answers"
echo %ans%|python "QandA.py"
But piping in multiple answers is tricky. The following is broken because the batch parser inserts an extra space before each & and )
(echo val1&echo val2&echo val3)|python "script.py"
The easiest way to provide multiple answers without extra space is to delay the appearance of the & so the initial parsing pass that prepares the pipe sees only a single ECHO on the left side
set "+=&"
echo val1%%+%%echo val2%%+%%echo val3|python "script.py"
I'm trying to make this printing calculator program in Python 2.7.8, but whenever I run it, it outputs this:
0
0
0
0
And so on. It does this infinitely and never stops. I would really appreciate any help. Here is my code. Thanks!
import msvcrt # Windows only!
def getch():
return msvcrt.getch()
def putch(ch):
msvcrt.putch(ch)
total = 0
pendingOp = 0
print "*** Calculator ***"
while True:
ch = getch()
if ch == "q": break # quit
if ch == "c": # clear the display
total = 0
pendingOp = 0
elif ch == "a" or "s" or "m" or "d": # add, subtract, multiply, divide
pendingOp = ch
elif (ch >= "0" and ch <= "9"):
newNumber = int(ch)
if pendingOp == 0:
total = newNumber
else:
if pendingOp == "a": total = total + newNumber
elif pendingOp == "s": total = total - newNumber
elif pendingOp == "m": total = total * newNumber
elif pendingOp == "d": total = total / newNumber
pendingOp = 0
print total
Your problem is that, for one reason or another, you don't have a console.
Python's msvcrt.getch is a thin wrapper around the MSVCRT function _getch. While the docs there say "there is no error return", that isn't really true. If there is no console for the app, these functions return immediately, returning -1 (cast to the appropriate type, which means you get '\xff' for getch and u'\uffff' for getwch—which is exactly what you're seeing).
So, why would there be no console?
The most common reason is that you're running the app in some GUI IDE, like IDLE or PyWin, instead of from the cmd shell (aka "DOS prompt"). You can't use msvcrt in IDLE.*
There are other possibilities, like explicitly closing or detaching from the console, but I doubt those are relevant here.
So, what can you do?
Well, if you're using IDLE, it's actually simulating command-line input on top of a Tkinter GUI. That simulation doesn't provide any way to get input a character at a time, only a line at a time. So, you could just use raw_input instead of msvcrt.getch here.
You could theoretically hook the IDLE simulated input, but that would be a lot more work than just, say, building your own Tkinter GUI (or Qt or Win32 or whatever GUI) for your app, for a lot less benefit.
Or, alternatively, you can stop running the program in IDLE. Open a cmd window and run the program with python myscript.py (or C:\Python27\python D:\mystuff\myscript.py, or whatever). If you don't know how to use the shell on Windows, this is a good time to learn.
* Well, you can call the Windows API functions to explicitly allocate a console, via ctypes or win32api. But if you do that, it's going to pop up a separate cmd shell window for you to type into anyway, it's still not going to affect IDLE's input.
Welcome to the wonderful world of converting between different string encodings. That particular API returns a byte string which is different from Python's normal string UTF encoding, and the two won't compare. Try for example if ch == b'q'.
Another solution is to get the wide characters directly:
def getch():
return msvcrt.getwch()
Of some question to me is that Ctrl-C is returned (on Windows, obviously), even though the API documentation for my python says it shouldn't be.
Update:
abarnert's answer seems to be the root cause. I'm leaving this answer because string encoding issues causes the same symptoms on Python 3 running in the console, and in general are a cause of similarly interesting headaches.
I am starting to learn python and I decided to start with what I though was a simple script but I guess not. All I want to do is return what current network location I am using on my mac. Every time I run it, it gives me back the correct location but then when I try to change it, nothing happens. I don't know what I am doing wrong so here is my code. I really hope some one can help me. This script is unique to MACS btw. I know that the "scselect" is built in but I wanted to see if it could be done with Python. The code below is rough I know but as I learn I will make it better. I am using Python 2.7.
import subprocess
loc = "scselect"
srn = "scselect SRN"
home = "scselect HOME"
a = subprocess.check_output(loc, shell=True)
print "\n##### NETWORK SELECTION #####"
print "\nYour current location is set to \n\n%s" % a
choice = raw_input("Please select what location you want to change to: ")
b = subprocess.Popen(srn, shell=True, stdout=subprocess.PIPE)
c = subprocess.Popen(home, shell=True, stdout=subprocess.PIPE)
choice = int(choice)
if choice == 1:
print "\nSwitching to HOME...\n"
c
elif choice == 2:
print "\nSwitching to SRN...\n"
b
else:
print "\nShutting down...\n"
You run both scselect commands first, then only display the output of one of the commands.
By the time you come to if choice == 1, etc. both b and c have already been set and have captured the output of the commands. Both scselect SRN and scselect HOME have been run, with the last one undoing anything the first command achieved.
Move the subprocess calls into the if statements:
if choice == 1:
print "\nSwitching to HOME...\n"
output = subprocess.Popen(home, shell=True, stdout=subprocess.PIPE)
print output