Call a function through a variable in Python - python

I'm trying to make a game engine in Python as a "weekend project" but I have a problem.
I'm trying to make it that a user can declare a keybind by typing the key and the function into a text file what they want to run but when I run the code from that sheet with exec it runs the function and I have no idea on how to call the function through the variable. (Also I don't want it to run the function when it runs the code.)
Here is the code I use for executing the code from binds.zdata
for line in bind:
try:
exec line
except:
try:
error.write(localtime + " : " + "ERROR: Could not bind key from the bind file : " + line)
except:
pass
Here's the text in binds.zdata
_w = Functions.motion.move().forward()
_a = Functions.motion.move().left()
_s = Functions.motion.move().back()
_d = Functions.motion.move().right()

You want to lose the () at the end of each line:
_w = Functions.motion.move().forward
_a = Functions.motion.move().left
_s = Functions.motion.move().back
_d = Functions.motion.move().right
now you can call the function through the variable by simply applying parenthesis, such as:
_w()

I'm not sure I recommend "exec" since somebody could put any code in there.
But here is your problem.
_w = Functions.motion.move().forward()
Calls the function 'forward' and puts the results in _w.
_w = Functions.motion.move().forward
Assigns the function 'forward' to the variable '_w'.
Since you asked what I would do, I would create a set of tokens that represent the various functions and then let them do the mapping in side a config file (see: ConfigParser). Then parse the config file. It is a little more work, but a lot more secure.

I'd first change the lines in the binds.zdata file like this so they assign a function to each of the variables:
_w = Functions.motion.move().forward
_a = Functions.motion.move().left
_s = Functions.motion.move().back
_d = Functions.motion.move().right
Then, assuming that Functions is defined, I'd execute all the lines in the file at once with something like:
try:
execfile('binds.zdata')
except Exception as exc:
error.write(localtime + " : " + "ERROR: executing binds file")
Afterwards, the _w, _a, _s, _d variables will have the desired function assigned to them, which you can call as usual — i.e. _w(), _a(), etc.

Reading the config like that with exec is going to open you to all kinds of pain at runtime, as the user's config file is going to be executable code that you are just trusting. Malicious or unintentionally just bad commands will get execute. You could template it like this:
def read_config(config_file):
user_button_map = {}
with open(config_file, 'r') as fh:
for line in fh.readlines():
key,func_key = line.strip().split(',')
assert(key in BUTTON_MAP_FUNCTIONS),"no such action"
user_button_map[key] = BUTTON_MAP_FUNCTIONS[func_key]
return user_button_map
BUTTON_MAP_FUNCTIONS = {
"forward" : Functions.motion.move().forward,
"left" : Functions.motion.move().left,
# etc...
}
# sample config file
# w, forward
# a, left
# etc...

Related

why python get me this error: filenotfound

i have a irrational error,
code:
#====================================================#
#X Programming Language 2022(for Jadi) License: GPL #
#====================================================#
from os import system #importing system func. for cmd
code_location = "" #saving code location in this
code = "" #saving code in this
def main(): #main func.
code_location = input() #get code location from user
code = get_code() #cal get_code and save res to code var
print(code) #print code
end() #calling end
def get_code(): #get_code func. for getting code from file
code_file = open(code_location, 'r') #openning file with method read and saving to code_file
res = code_file.readlines() #saving the content from file to res
return res #returning res
def compiler(): #compiler func. for compiling code
pass
def end(): #when program end...
input("Press Enter to Countinue...")
if __name__ == "__main__":
main()
and this is code directory:
enter image description here
running:
enter image description here
Short answer: Your two code_location variables are not the same thing.
Variable scopes
Variables have a property called a scope, which is essentially which context they belong to. Unless specified otherwise, variables within a function exist only within that function. Consider:
a = 0
def set_a():
a = 1
set_a()
print(a)
This will print 0. This is because the a variable within the function set_a is actually a different variable to the a defined in line 1. Although they have the same name, they point to different places in memory.
Solutions
There are a few ways to do this:
Defining scope
Either, you can set the scope of a within the function to global (instead of local). What this does is now, instead of a within the function pointing to a different memory location, it points to the same memory location as a outside the variable. This is done like so:
a = 0
def set_a():
global a
a = 1
set_a()
print(a)
In this case, a will be set to 1, and "1" will be printed
Passing as an argument
Another way to do this, and may be more relevant in your circumstance, is to pass the value as a variable to the function. In your case, you are using code_location as the file path, so therefore what you want to pass code_location into the function. You would then have to define your function like this:
def get_code(code_location):
and call the function (from your main function) like this:
code = get_code(code_location)
Notes
When operating on files, it is best practice to use a with block. This handles closing your file when you are done with it, and can prevent corruption of files in the rare case that something goes wrong with your code. This can be done like this:
with open(code_location, 'r') as code_file:
res = code_file.readlines()
return res
Python global variables are read-only in local scopes (e.g. your function's scope) by default.
So in the line code_location = input() you are essentially creating a new new local variable of the same name and assigning the input to it.
In order to write to your global variable instead, you first have to declare your intention:
def main(): #main func.
global code_location # DECLARE WRITE INTENTION FOR GLOBAL
code_location = input() #get code location from user
code = get_code() #cal get_code and save res to code var
print(code) #print code
end() #calling end
You don't have to do the same thing in get_code() since you are only reading from code_location there.
PS:
As was alluded to in the comment, it's good practice to close opened files after you've finished with them:
def get_code(): #get_code func. for getting code from file
code_file = open(code_location, 'r') #openning file with method read and saving to code_file
res = code_file.readlines() #saving the content from file to res
code_file.close() # CLOSE FILE
return res #returning res
Or have it done automatically by a context manager:
def get_code(): #get_code func. for getting code from file
with open(code_location, 'r') as code_file: #openning file with method read and saving to code_file
res = code_file.readlines() #saving the content from file to res
return res #returning res

How to add aliases to an input dictionary?

Recently I started a project. My goal was it to have a script, which, once launched, could be able to control actions on the hosts computer if an instruction was send via email. (I wanted this so I could start tasks which take a long time to complete while I'm away from home)
I started programming and not long after I could send emails, receive emails and analyze their content and take actions responding to the content in the email.
I did this by using an input dictionary, which looked like this:
contents_of_the_email = "!screen\n!wait 5\n!hotkey alt tab"
def wait(sec):
print(f"I did nothing for {sec} seconds!")
def no_operation():
print("Nothing")
def screenshot():
print("I took an image of the screen and send it to your email adress!")
def hotkey(*args):
print(f"I pressed the keys {', '.join(args)} at the same time")
FUNCTIONS = {
'':no_operation,
'!screen': screenshot,
'!hotkey': hotkey,
'!wait': wait
}
def call_command(command):
function, *args = command.split(' ')
FUNCTIONS[function](*args)
for line in contents_of_the_email.split("\n"):
call_command(line)
In total I have around 25 functions each with its own response. I replaced the actual code for the commands with simple print statements as they are not needed to understand or replicate my problem.
I then wanted to add aliases for the command, so for example you would be able to type "!ss" instead of "!screen".
I did achieve this using another line in the dictionary:
FUNCTIONS = {
'':no_operation,
'!screen': screenshot,
'!ss':screenshot,
'!hotkey': hotkey,
'!wait': wait
}
But I didn't like this. It would fill up the whole dictionary if I did it for every alias I am planning to add and it would make my code very messy.
Is there any way to define aliases for commands separately and still keep the dictionary clean and simple? I would desire something like this in a separate aliases.txt file:
screen: "!screen", "!ss","!screenshot","!image"
wait: "!wait","!pause","!sleep","!w"
hotkey: "!hk","!tk"
If this is possible in python I would really appreciate to know!
You can use following solution:
import json
contents_of_the_email = "!screen\n!wait 5\n!hotkey alt tab"
def wait(sec):
print(f"I did nothing for {sec} seconds!")
def no_operation():
print("Nothing")
def screenshot():
print("I took an image of the screen and send it to your email address!")
def hotkey(*args):
print(f"I pressed the keys {', '.join(args)} at the same time")
# FUNCTIONS DICT FROM JSON
with open("aliases.json") as json_file:
aliases_json = json.load(json_file)
FUNCTIONS = {}
for func_name, aliases in aliases_json.items():
FUNCTIONS.update({alias: globals()[func_name] for alias in aliases})
def call_command(command):
function, *args = command.split(' ')
FUNCTIONS[function](*args)
for line in contents_of_the_email.split("\n"):
call_command(line)
aliases.json:
{
"screenshot": ["!screen", "!ss","!screenshot","!image"],
"wait": ["!wait","!pause","!sleep","!w"],
"hotkey": ["!hk","!tk", "!hotkey"]
}
is that what you looking for?
You can go from a dictionnary of callables and list of shortcuts to a dictionnary of shortcuts to callables fairly easily with for loops.
# long dict of shortcuts to callables
goal = {'A': 0, 'B': 0, 'C': 1}
# condensed dict, not in .txt, but storable in python
condensed = {0: ['A', 'B'], 1: ['C']}
# expand the condensed dict
commands = {}
for func, shortcuts in condensed.items():
for shortcut in shortcuts:
commands[shortcut] = func
# or with a comprehension
commands = {s: f for f, ls in condensed.items() for s in ls}
# verify expanded and goal are the same
assert commands == goal
You could do what you want by first creating a dictionary mapping each alias to one of the functions. This would require parsing the aliases.txt file — which fortunately isn't too difficult. It make use of the ast.literal_eval() function to convert the quoted literal strings in the file into Python strings, as well as the built-in globals() function to look up the associated functions given their name it the file. A KeyError will be raised if there are any references to undefined functions.
Note I changed your aliases.txt file to the following (which makes a little more sense):
screenshot: "!screen", "!ss","!screen","!image"
wait: "!wait","!pause","!sleep","!w"
hotkey: "!hk","!tk"
Below is a runnable example of how to do it:
from ast import literal_eval
ALIASES_FILENAME = 'aliases.txt'
# The functions.
def wait(sec):
print(f"I did nothing for {sec} seconds!")
def no_operation():
print("Nothing")
def screenshot():
print("I took an image of the screen and send it to your email adress!")
def hotkey(*args):
print(f"I pressed the keys {', '.join(args)} at the same time")
# Create dictionary of aliases from text file.
aliases = {}
with open(ALIASES_FILENAME) as file:
namespace = globals()
for line in file:
cmd, other_names = line.rstrip().split(':')
aliases[cmd] = namespace[cmd] # Allows use of actual function name.
for alias in map(literal_eval, other_names.replace(',', ' ').split()):
aliases[alias] = namespace[cmd]
def call_command(command):
function, *args = command.split(' ')
if function in aliases:
aliases[function](*args)
# Sample message.
contents_of_the_email = """\
!screen
!wait 5
!hk alt tab
"""
# Execute commands in email.
for line in contents_of_the_email.split("\n"):
call_command(line)
Output:
I took an image of the screen and send it to your email adress!
I did nothing for 5 seconds!
I pressed the keys alt, tab at the same time

How to solve class objecto has no atribute

beginner Python user here.
So, I´m trying to make a program that orders the files of my (many) Downloads folder.
I made a class object to work with the many folders:
class cContenedora:
def __int__(self, nCarp, dCarp): #nCarp Stands is the file name and dCarp Stands for file directory.
self.nCarp = nCarp
self.dCarp = dCarp
So, y wrote a instance like this:
Download = cContenedora()
Download.nCarp = "Downloads/"
#The side bar is for making a path to move my archives from with shutil.move(path, dest)
Download.dCarp = "/Users/MyName/Download/"
#This is for searching the folder with os.listdir(Something.dCarp)
Then, I wrote my function, and it goes something like this:
def ordenador(carpetaContenedora, formato, directorioFinal): #carpetaContenedora is a Download Folder
carpetaContenedora = cContenedora() #carpetaContenedora one of the class objects
dirCCont = os.listdir(carpetaContenedora.dCarp) #The to directory is carpetaContenedora.cCarp
for a in dirCCont:
if a.endswith(formato):
path = "/Users/Aurelio Induni/" + carpetaContenedora().nCarp + a
try:
shutil.move(path, directorioFinal)
print(Fore.GREEN + a + "fue movido exitosamente.")
except:
print(Fore.RED + "Error con el archivo" + a)
pass
for trys in range(len(listaCarpetasDestino)-1): #Is a list full of directories.
for container in listaCarpetasFuente: #A short list of all my Downloads Folder.
for formatx in listaFormatos: #listaFormatos is a list ful of format extensions like ".pdf"
#try: #I disabled this to see the error istead of "Error Total"
ordenador(container, formatx, listaCarpetasDestino[trys])
#except:
#print(Fore.RED + "Error Total") #I disabled this to see the error.
But every time I run it I get the following:
AttributeError: 'cContenedora' object has no attribute 'dCarp'
It says the error is in line 47 (the one with the os.listdir(carpetaContenedora.dCarp))
I´m sure is something small. Python is so amazing, but it also can be so frustrating not knowing what´s wrong.
There is a spelling mistake in the initialization of your instance. It should be "init" instead of "int".
In the class cContenedora, the function should be
class cContenedora:
def __init__(self, nCarp, dCarp):
self.nCarp = nCarp
self.dCarp = dCarp
Additionally, When you are passing in the parameter. Make sure to pass in both of the parameters in the line with Value.
CContenedora(nCarp="something",dCarp="something")
Your class initializer, i.e., __init__() function has 2 parameters nCarp and dCarp but when you are actually creating the object there are no parameters passed.
Your function ordenador takes the first parameter as carpetaContenedora, on the first line same variable is assigned a new object of cContenedora, at this line the original values you passed are lost forever.
This could be the reason it is giving for the error.
Refer this link for more details on how to create classes and instantiate the object.

python - Improve script and exception handling

I wrote my first python script which read a ini file using configparser.
First my ini file is define like:
;ini
[hypervisor1]
name = hyp1
how = ssh
vms = vm1 vm2 vm3
[hypervisor2]
name = hyp2
how = vpn
vms = vm4 vm5 vm6
Second my script:
import configparser
import os.path
import argparse
file_path = os.path.join(os.path.dirname(__file__),"config.ini")
parser = configparser.SafeConfigParser()
parser.read(file_path)
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument("--vm", help="display info")
args = parser.parse_args()
vm = args.vm
return vm
vm = get_args()
def print_hyp(name_s):
for info in parser.sections():
for key, value, in parser.items(info):
for value in value.split(): # split whitespace vms value
if (value == name_s):
global info_s
info_s = info
print(name_s, "is deployed on", info)
if (print_hyp):
print_hyp(vm)
name = parser.get(info_s, 'name')
how = parser.get(info_s, 'how')
print("Name:", name)
print("How:", how)
So I need to get section which belongs to value: function print_hyp. And when I get it, I have to print other key with their value.
I'm sure my script is ugly, how can I improve it ?
And I have not errors management, for exemple the output with a vm that does not exist:
./script.py --vm stackoverflow
Traceback (most recent call last): File "script.py", line 43, in <module>
name = parser.get(info_s, 'name')
NameError: global name 'info_s' is not defined
Same thing without argument.
Could you help me?
Many thanks!
I would add
vm = get_args()
print('vm', vm) # to verify vm arg is what you want
This if doesn't make sense:
if (print_hyp):
print_hyp(vm)
...
Do you want something like if vm is not None:?
print_hyp(name_s) doesn't return anything. In prints something(s). It's use of global info_s is unwise. I use global so little that I don't even remember whether it has to also be initialized outside the function. I think your function should should return it, or a list of its values, rather than use global.
You should also be using the if __name__=='__main__:`. If you don't know what that is for, I'd suggest reviewing some basic script layout documentation.

Get file syntax selection in sublime text 3 plugin

I have a very small plugin to open a perl file module starting from the use statement. It's really basic and it just replaces '::' with '/' and then if the file exists in one of the paths specified in PERL5LIB, it opens it.
I want it to run only when the open file syntax is selected as perl.
Is there any API to get that information?
This is the code that I have now:
class OpenPerlModule(sublime_plugin.TextCommand):
def run(self, edit=None, url=None):
perl_file = url.replace("::", "/")
perl_dirs = os.environ.get('PERL5LIB')
for perl_dir in perl_dirs.split(':'):
if (os.path.exists(perl_dir + '/' + perl_file + '.pm')):
self.view.window().open_file(perl_dir + '/' + perl_file + '.pm')
return
(The OS is Ubuntu)
Here is the code snippet you're looking for
self.view.settings().get("syntax")
You should check whether it's a syntax related to Perl or not. I suggest something like this:
syntax = self.view.settings().get("syntax")
syntax.endswith("Perl.tmLanguage") or syntax.endswith("Perl.sublime-syntax")
The second or clause is to cover the new syntax that's introduced in >=3080
Aside from self.view.settings().get("syntax") as described in Allen Bargi's answer, you could also get the scope for the current cursor position and check for source.perl in it:
import sublime_plugin
class FindScopeCommand(sublime_plugin.TextCommand):
def run(self, edit):
# `sel()` returns a list of Regions that are selected.
# Grab the beginning point of the first Region in the list.
first_point = self.view.sel()[0].a
# now, get the full scope name for that point
scope = self.view.scope_name(first_point)
if "source.perl" in scope:
print("You're using Perl. Yay!")
else:
print("Why don't you love Perl?")

Categories