Python variable scope across modules - python

The title might be misleading or inaccurate, so please correct me if I'm wrong.
I have a package structured like this:
common
__init__.py
foo.py
And here are the codes:
common/__init__.py
name = 'name_common'
def print_current_file_name():
print('current file name: ' + __file__)
def print_name():
print('name is: ' + eval('name'))
foo.py
from common import print_current_file_name, print_name
name = 'name_foo'
if __name__ == '__main__':
print_current_file_name()
print_name()
If I do this:
>>> python foo.py
I'll get this:
current file name: /tmp/common/__init__.py
name is: name_common
But I expect the results to be:
current file name: /tmp/common/foo.py
name is: name_foo
What did I miss? How can I make this right?
I don't even know which keywords should I google...
The use of eval is weird, but these codes are just for demonstration purpose only.

That's not at all how variables work in Python, or in any language I'm aware of.
The global scope of a function is always where it is defined, not where it is executed. There is no way for print_name to access the value of name in foo.py.
Rather, you should pass it in as a parameter. Or, depending on what you actually want to do, you might want to create a class that defines the value of name at class level.

Actually, this is possible.
I Found a similar question:
How to use inspect to get the caller's info from callee in Python?
And I think this is what the built-in library inspect comes for.
common/__init__.py
from os.path import abspath
import inspect
name = 'name_common'
def print_current_file_name():
print('current file name: ' + abspath(inspect.getfile(inspect.currentframe().f_back)))
# or
print('current file name: ' + abspath(inspect.currentframe().f_back.f_globals['__file__']))
def print_name():
print('name is: ' + inspect.currentframe().f_back.f_globals['name'])
Finally,
$ python foo.py
current file name: /tmp/common/foo.py
name is: name_foo

Related

Is there a dunder attribute that gets me the filename of where the function is currently being used at? [duplicate]

If I want the path of the current module, I'll use __file__.
Now let's say I want a function to return that. I can't do:
def get_path():
return __file__
Because it will return the path of the module the function has been declared in.
I need it to work even if the function is not called at the root of the module but at any level of nesting.
This is how I would do it:
import sys
def get_path():
namespace = sys._getframe(1).f_globals # caller's globals
return namespace.get('__file__')
Get it from the globals dict in that case:
def get_path():
return globals()['__file__']
Edit in response to the comment: given the following files:
# a.py
def get_path():
return 'Path from a.py: ' + globals()['__file__']
# b.py
import a
def get_path():
return 'Path from b.py: ' + globals()['__file__']
print get_path()
print a.get_path()
Running this will give me the following output:
C:\workspace>python b.py
Path from b.py: b.py
Path from a.py: C:\workspace\a.py
Next to the absolute/relative paths being different (for brevity, lets leave that out), it looks good to me.
I found a way to do it with the inspect module. I'm ok with this solution, but if somebody find a way to do it without dumping the whole stacktrace, it would be cleaner and I would accept his answer gratefully:
def get_path():
frame, filename, line_number, function_name, lines, index =\
inspect.getouterframes(inspect.currentframe())[1]
return filename

Call a function from different file where the file name and function name are read from a list

I have multiple functions stored in different files, Both file names and function names are stored in lists. Is there any option to call the required function without the conditional statements?
Example, file1 has functions function11 and function12,
def function11():
pass
def function12():
pass
file2 has functions function21 and function22
def function21():
pass
def function22():
pass
and I have the lists
file_name = ["file1", "file2", "file1"]
function_name = ["function12", "function22", "funciton12"]
I will get the list index from different function, based on that I need to call the function and get the output.
If the other function will give you a list index directly, then you don't need to deal with the function names as strings. Instead, directly store (without calling) the functions in the list:
import file1, file2
functions = [file1.function12, file2.function22, file1.function12]
And then call them once you have the index:
function[index]()
There are ways to do what is called "reflection" in Python and get from the string to a matching-named function. But they solve a problem that is more advanced than what you describe, and they are more difficult (especially if you also have to work with the module names).
If you have a "whitelist" of functions and modules that are allowed to be called from the config file, but still need to find them by string, you can explicitly create the mapping with a dict:
allowed_functions = {
'file1': {
'function11': file1.function11,
'function12': file1.function12
},
'file2': {
'function21': file2.function21,
'function22': file2.function22
}
}
And then invoke the function:
try:
func = allowed_functions[module_name][function_name]
except KeyError:
raise ValueError("this function/module name is not allowed")
else:
func()
The most advanced approach is if you need to load code from a "plugin" module created by the author. You can use the standard library importlib package to use the string name to find a file to import as a module, and import it dynamically. It looks something like:
from importlib.util import spec_from_file_location, module_from_spec
# Look for the file at the specified path, figure out the module name
# from the base file name, import it and make a module object.
def load_module(path):
folder, filename = os.path.split(path)
basename, extension = os.path.splitext(filename)
spec = spec_from_file_location(basename, path)
module = module_from_spec(spec)
spec.loader.exec_module(module)
assert module.__name__ == basename
return module
This is still unsafe, in the sense that it can look anywhere on the file system for the module. Better if you specify the folder yourself, and only allow a filename to be used in the config file; but then you still have to protect against hacking the path by using things like ".." and "/" in the "filename".
(I have a project that does something like this. It chooses the paths from a whitelist that is also under the user's control, so I have to warn my users not to trust the path-whitelist file from each other. I also search the directories for modules, and then make a whitelist of plugins that may be used, based only on plugins that are in the directory - so no funny games with "..". And I'm still worried I forgot something.)
Once you have a module name, you can get a function from it by name like:
dynamic_module = load_module(some_path)
try:
func = getattr(dynamic_module, function_name)
except AttributeError:
raise ValueError("function not in module")
At any rate, there is no reason to eval anything, or generate and import code based on user input. That is most unsafe of all.
Another alternative. This is not much safer than an eval() however.
Someone with access to the lists you read from the config file could inject malicious code in the lists you import.
I.e.
'from subprocess import call; subprocess.call(["rm", "-rf", "./*" stdout=/dev/null, stderr=/dev/null, shell=True)'
Code:
import re
# You must first create a directory named "test_module"
# You can do this with code if needed.
# Python recognizes a "module" as a module by the existence of an __init__.py
# It will load that __init__.py at the "import" command, and you can access the methods it imports
m = ["os", "sys", "subprocess"] # Modules to import from
f = ["getcwd", "exit", "call; call('do', '---terrible-things')"] # Methods to import
# Create an __init__.py
with open("./test_module/__init__.py", "w") as FH:
for count in range(0, len(m), 1):
# Writes "from module import method" to __init.py
line = "from {} import {}\n".format(m[count], f[count])
# !!!! SANITIZE THE LINE !!!!!
if not re.match("^from [a-zA-Z0-9._]+ import [a-zA-Z0-9._]+$", line):
print("The line '{}' is suspicious. Will not be entered into __init__.py!!".format(line))
continue
FH.write(line)
import test_module
print(test_module.getcwd())
OUTPUT:
The line 'from subprocess import call; call('do', '---terrible-things')' is suspicious. Will not be entered into __init__.py!!
/home/rightmire/eclipse-workspace/junkcode
I'm not 100% sure I'm understanding the need. Maybe more detail in the question.
Is something like this what you're looking for?
m = ["os"]
f = ["getcwd"]
command = ''.join([m[0], ".", f[0], "()"])
# Put in some minimum sanity checking and sanitization!!!
if ";" in command or <other dangerous string> in command:
print("The line '{}' is suspicious. Will not run".format(command))
sys.exit(1)
print("This will error if the method isnt imported...")
print(eval(''.join([m[0], ".", f[0], "()"])) )
OUTPUT:
This will error if the method isnt imported...
/home/rightmire/eclipse-workspace/junkcode
As pointed out by #KarlKnechtel, having commands come in from an external file is a gargantuan security risk!

Getting function name and file name through another function

I want python to show me which function has been executed and from what file... so I have the following as test1.py
import sys,os, test2
def some_function():
print (sys._getframe().f_code.co_name +" "+ os.path.basename(__file__) , 'executed')
print (test2.function_details(), 'executed')
test2.py is:
import sys,os
def function_details():
return sys._getframe().f_code.co_name + " " +os.path.basename(__file__)
now when I run it
import test1
test1.some_function()
I get the following output:
('some_function test1.pyc', 'executed')
('function_details test2.pyc', 'executed')
When I try to make a function for calling the file and function of the executed, it tells me the sub function I made instead of the original.
My question is how do I modify test2.py so it will output
('some_function test1.pyc', 'executed')
('some_function test1.pyc', 'executed')
So there are two issues with function_details:
The current frame is function_details so you need to go up one frame to get to the calling frame. To do this you pass 1 to sys._getframe.
__file__ is the name of the current file for whatever module you happen to be in (if defined), this needs to be replaced with the co_filename attribute of the f_code object associated with the correct frame.
Correcting both of these things, we redefine function_details as:
def function_details():
code_obj = sys._getframe(1).f_code
return " ".join([code_obj.co_name, os.path.basename(code_obj.co_filename)])
Which produces the desired result. In my opinion, the inspect module accomplishes this same thing far better than using sys._getframe directly. Here's the new function_details written using inspect:
import inspect
def function_details():
frame = inspect.getouterframes(inspect.currentframe())[1]
return " ".join([frame[3], os.path.basename(frame[1])])

How do I get the Python line number and file name of the point this function was called from? [duplicate]

In C++, I can print debug output like this:
printf(
"FILE: %s, FUNC: %s, LINE: %d, LOG: %s\n",
__FILE__,
__FUNCTION__,
__LINE__,
logmessage
);
How can I do something similar in Python?
There is a module named inspect which provides these information.
Example usage:
import inspect
def PrintFrame():
callerframerecord = inspect.stack()[1] # 0 represents this line
# 1 represents line at caller
frame = callerframerecord[0]
info = inspect.getframeinfo(frame)
print(info.filename) # __FILE__ -> Test.py
print(info.function) # __FUNCTION__ -> Main
print(info.lineno) # __LINE__ -> 13
def Main():
PrintFrame() # for this line
Main()
However, please remember that there is an easier way to obtain the name of the currently executing file:
print(__file__)
For example
import inspect
frame = inspect.currentframe()
# __FILE__
fileName = frame.f_code.co_filename
# __LINE__
fileNo = frame.f_lineno
There's more here http://docs.python.org/library/inspect.html
Building on geowar's answer:
class __LINE__(object):
import sys
def __repr__(self):
try:
raise Exception
except:
return str(sys.exc_info()[2].tb_frame.f_back.f_lineno)
__LINE__ = __LINE__()
If you normally want to use __LINE__ in e.g. print (or any other time an implicit str() or repr() is taken), the above will allow you to omit the ()s.
(Obvious extension to add a __call__ left as an exercise to the reader.)
You can refer my answer:
https://stackoverflow.com/a/45973480/1591700
import sys
print sys._getframe().f_lineno
You can also make lambda function
I was also interested in a __LINE__ command in python.
My starting point was https://stackoverflow.com/a/6811020 and I extended it with a metaclass object. With this modification it has the same behavior like in C++.
import inspect
class Meta(type):
def __repr__(self):
# Inspiration: https://stackoverflow.com/a/6811020
callerframerecord = inspect.stack()[1] # 0 represents this line
# 1 represents line at caller
frame = callerframerecord[0]
info = inspect.getframeinfo(frame)
# print(info.filename) # __FILE__ -> Test.py
# print(info.function) # __FUNCTION__ -> Main
# print(info.lineno) # __LINE__ -> 13
return str(info.lineno)
class __LINE__(metaclass=Meta):
pass
print(__LINE__) # print for example 18
wow, 7 year old question :)
Anyway, taking Tugrul's answer, and writing it as a debug type method, it can look something like:
def debug(message):
import sys
import inspect
callerframerecord = inspect.stack()[1]
frame = callerframerecord[0]
info = inspect.getframeinfo(frame)
print(info.filename, 'func=%s' % info.function, 'line=%s:' % info.lineno, message)
def somefunc():
debug('inside some func')
debug('this')
debug('is a')
debug('test message')
somefunc()
Output:
/tmp/test2.py func=<module> line=12: this
/tmp/test2.py func=<module> line=13: is a
/tmp/test2.py func=<module> line=14: test message
/tmp/test2.py func=somefunc line=10: inside some func
import inspect
.
.
.
def __LINE__():
try:
raise Exception
except:
return sys.exc_info()[2].tb_frame.f_back.f_lineno
def __FILE__():
return inspect.currentframe().f_code.co_filename
.
.
.
print "file: '%s', line: %d" % (__FILE__(), __LINE__())
Here is a tool to answer this old yet new question!
I recommend using icecream!
Do you ever use print() or log() to debug your code? Of course, you
do. IceCream, or ic for short, makes print debugging a little sweeter.
ic() is like print(), but better:
It prints both expressions/variable names and their values.
It's 40% faster to type.
Data structures are pretty printed.
Output is syntax highlighted.
It optionally includes program context: filename, line number, and parent function.
For example, I created a module icecream_test.py, and put the following code inside it.
from icecream import ic
ic.configureOutput(includeContext=True)
def foo(i):
return i + 333
ic(foo(123))
Prints
ic| icecream_test.py:6 in <module>- foo(123): 456
To get the line number in Python without importing the whole sys module...
First import the _getframe submodule:
from sys import _getframe
Then call the _getframe function and use its' f_lineno property whenever you want to know the line number:
print(_getframe().f_lineno) # prints the line number
From the interpreter:
>>> from sys import _getframe
... _getframe().f_lineno # 2
Word of caution from the official Python Docs:
CPython implementation detail: This function should be used for internal and specialized purposes only. It is not guaranteed to exist in all implementations of Python.
In other words: Only use this code for personal testing / debugging reasons.
See the Official Python Documentation on sys._getframe for more information on the sys module, and the _getframe() function / submodule.
Based on Mohammad Shahid's answer (above).

Importing and storing the data from a Python file

How do I import a Python file and use the user input later?
For example:
#mainprogram
from folder import startup
name
#startup
name = input('Choose your name')
What I want is to use the startup program to input the name, then be able to use the name later in the main program.
You can access that variable via startup.name later in your code.
name will be in startup.name. You can use dir(startup) to see it.
Or, as an alternate solution:
# Assuming from the names that 'folder' is a folder and 'startup' is a Python script
from folder.startup import *
now you can just use name without the startup. in front of it.
I think is better do your code in classes and functions.
I suggest you to do:
class Startup(object):
#staticmethod
def getName():
name = ""
try:
name = input("Put your name: ")
print('Name took.')
return True
except:
"Can't get name."
return False
>> import startup
>> Startup.getName()

Categories

Resources