I have created a simple function for reporting current values of variables in some engineering scripts, by passing the variable name in an eval() function. The argument is passed as string then the eval() reads it and reports back the value with some additional info. The function works properly in a single script. However when i am importing the same function from a module i get back an error saying that the variable has is not defined.
I have trying setting it up as a global variable but still get the same problem
def report(name,units = '-',comment ='NC'):
if type(eval(name)) == str:
print('{0:<12}= {1:^10} {2:^5} {3}'.format(name,eval(name),units,comment))
else:
print('{0:<12}= {1:8.3f} {2:^5} {3}'.format(name,eval(name),units,comment))
While trying to use the function from the imported module i get the following
>>>from reporting import*
>>> from shapes import*
>>> Iyy = rec_Iyy(40,60)
>>> report('Iyy')
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
File "C:\Users\vousvoukisi\OneDrive\11.Python\03_myScripts\design_mod\reporting.py", line 8, in report
if type(eval(name)) == str:
File "<string>", line 1, in <module>
NameError: name 'Iyy' is not defined
## while i would expect the outcome to be :
>>> %Run reporting.py
Iyy = 720000.000 - NC
I have a file named file.py containing the following script:
def b():
print("b")
def proc():
print("this is main")
b()
proc()
And I have another file named caller.py, which contains this script:
text = open('file.py').read()
exec(text)
When I run it, I get the expected output:
this is main
b
However, if I change caller.py to this:
def main():
text = open('file.py').read()
exec(text)
main()
I get the following error:
this is main
Traceback (most recent call last):
File "./caller.py", line 7, in <module>
main()
File "./caller.py", line 5, in main
exec(text)
File "<string>", line 10, in <module>
File "<string>", line 8, in main
NameError: global name 'b' is not defined
How is function b() getting lost? It looks to me like I'm not violating any scope rules. I need to make something similar to the second version of caller.py work.
exec(text) executes text in the current scope, but modifying that scope (as def b does via the implied assignment) is undefined.
The fix is simple:
def main():
text = open('file.py').read()
exec(text, {})
This causes text to run in an empty global scope (augmented with the default __builtins object), the same way as in a regular Python file.
For details, see the exec documentation. It also warns that modifying the default local scope (which is implied when not specifying any arguments besides text) is unsound:
The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.
Would it work for you if you imported and called the function instead?
myfile.py
def b():
print("b")
def proc():
print("this is main")
b()
caller.py
import myfile
myfile.proc()
A bit of background
I'm writing a python module for my own use, and I'm using Python's logging module. There are handlers and formatters and even a pair of functions I create that (for the most part) won't be used anywhere else. However, I still want to be able to access and modify these variables elsewhere (for instance, other closely-coupled modules or scripts)
A simple namespace
What I'm currently doing is using a class definition to group all of my variables together, like this:
class _Logging:
'''A little namespace for our logging facilities. Don't try to instantiate
it: all it does is group together some logging objects and keep them out of
the global namespace'''
global logger
def __init__(self):
raise TypeError("that's not how this works...")
def gz_log_rotator(source, dest):
'''accept a source filename and a destination filename. copy source to
dest and add gzip compression. for use with
logging.handlers.RotatingFileHandler.rotator.'''
with gzip.open(dest, 'wb', 1) as ofile, open(source, 'rb') as ifile:
ofile.write(ifile.read())
os.remove(source)
def gz_log_namer(name):
'''accept a filename, and return it with ".gz" appended. for use with
logging.handlers.RotatingFileHandler.namer.'''
return name + ".gz"
fmtr = logging.Formatter(
'[%(asctime)s:%(name)s:%(thread)05d:%(levelname)-8s] %(message)s')
gz_rotfile_loghandler = logging.handlers.RotatingFileHandler(
'%s.log' % __name__, mode='a', maxBytes=(1024**2 * 20), backupCount=3)
gz_rotfile_loghandler.setLevel(5)
gz_rotfile_loghandler.setFormatter(fmtr)
gz_rotfile_loghandler.rotator = gz_log_rotator
gz_rotfile_loghandler.namer = gz_log_namer
simplefile_loghandler = logging.FileHandler(
'%s.simple.log' % __name__, mode='w')
simplefile_loghandler.setLevel(15)
simplefile_loghandler.setFormatter(fmtr)
stream_loghandler = logging.StreamHandler()
stream_loghandler.setLevel(25)
stream_loghandler.setFormatter(fmtr)
logger = logging.getLogger(__name__)
logger.setLevel(5)
logger.addHandler(gz_rotfile_loghandler)
logger.addHandler(simplefile_loghandler)
logger.addHandler(stream_loghandler)
However, pylint complains (and i agree) that methods defined in a class should either be static methods, or follow the naming conventions for first parameters (e.g. gz_log_rotator(self, dest)), which is not how the function is used, and would be much more confusing.
Fun Fact
During this process i've also discovered that instances of classmethod and staticmethod are not in and of themselves callable (???). While a method defined in a class namespace is callable both within and without, classmethods and staticmethods are only callable when accessed through their class (at which point they refer to the underlying function, not the classmethod/staticmethod object)
>>> class Thing:
... global one_, two_, three_
... def one(self):
... print('one')
... #classmethod
... def two(cls):
... print('two')
... #staticmethod
... def three():
... print('three')
... one_, two_, three_ = one, two, three
...
>>> Thing.one()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: one() missing 1 required positional argument: 'self'
>>> Thing.two()
two
>>> Thing.three()
three
>>> # all as expected
>>> one_()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: one() missing 1 required positional argument: 'self'
>>> # so far so good
>>> two_()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'classmethod' object is not callable
>>> # what?
>>> three_()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'staticmethod' object is not callable
>>> # ???
My Question
Is there a better way to hold these variables without polluting my namespace?
The code I have works correctly, but it makes me feel a little unclean. I could define a function that would only be called once and then immediately call it, but then I either lose references to everything I don't return, or i'm back to polluting the global namespace. I could just make everything _hidden, but I feel like they should be logically grouped. I could make _Logging a bona fide class, put all of my stuff in an __init__ function and tack all my little variables onto self, but that also feels inelegant. I could create another file for this, but so far I've gotten by with everything held in the same file. The only other option that seemed palatable is to make the two functions staticmethods and only refer to them through our class (i.e. _Logging.gz_log_namer), but it would seem that is also impossible.
>>> class Thing:
... #staticmethod
... def say_hello():
... print('hello!')
... Thing.say_hello()
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in Thing
AttributeError: type object 'Thing' has no attribute 'say_hello'
>>>
As it stands, the best option I see is to use the selfless methods.
you can create a new class that inherit from staticmethod class, and add __call__ method to the class.
for example:
class callablestatic(staticmethod):
def __init__(self, func):
super().__init__(func)
self.func = func
def __call__(self, *args, **kwargs):
# the __call__ method allows you to call the class instance
return self.func(*args, **kwargs)
then use it in your class:
class Thing:
#callablestatic
def hello(name):
print(f"hello {name}")
hello("John") # works
but better create new file and import it as a module
Sorry for answering 2 years later, but this could help someone.
You could make your methods static, and create another static method (ex. init), calling it right after initializing the class. Then use setattr to keep the references to your variables.
For setting multiple class variables, you can use
[setattr(Class, name, value) for name,value in locals().items()]
inside the method.
Full code:
class _Logging:
'''A little namespace for our logging facilities. Don't try to instantiate
it: all it does is group together some logging objects and keep them out of
the global namespace'''
def __init__(self):
raise TypeError("that's not how this works...")
#staticmethod
def gz_log_rotator(source, dest):
'''accept a source filename and a destination filename. copy source to
dest and add gzip compression. for use with
logging.handlers.RotatingFileHandler.rotator.'''
with gzip.open(dest, 'wb', 1) as ofile, open(source, 'rb') as ifile:
ofile.write(ifile.read())
os.remove(source)
#staticmethod
def gz_log_namer(name):
'''accept a filename, and return it with ".gz" appended. for use with
logging.handlers.RotatingFileHandler.namer.'''
return name + ".gz"
#staticmethod
def init():
global logger
fmtr = logging.Formatter(
'[%(asctime)s:%(name)s:%(thread)05d:%(levelname)-8s] %(message)s')
gz_rotfile_loghandler = logging.handlers.RotatingFileHandler(
'%s.log' % __name__, mode='a', maxBytes=(1024**2 * 20), backupCount=3)
gz_rotfile_loghandler.setLevel(5)
gz_rotfile_loghandler.setFormatter(fmtr)
gz_rotfile_loghandler.rotator = _Logging.gz_log_rotator
gz_rotfile_loghandler.namer = _Logging.gz_log_namer
simplefile_loghandler = logging.FileHandler(
'%s.simple.log' % __name__, mode='w')
simplefile_loghandler.setLevel(15)
simplefile_loghandler.setFormatter(fmtr)
stream_loghandler = logging.StreamHandler()
stream_loghandler.setLevel(25)
stream_loghandler.setFormatter(fmtr)
logger = logging.getLogger(__name__)
logger.setLevel(5)
logger.addHandler(gz_rotfile_loghandler)
logger.addHandler(simplefile_loghandler)
logger.addHandler(stream_loghandler)
[setattr(_Logging, name, value) for name,value in locals().items()]
_Logging.init()
There is this code:
>>> if True:
... a = 4
...
>>> print a
4
Why variable a is still alive after if block? Shouldn't it be destroyed when block if ends?
Python variables have scope inside a function, class or module. Variables initialised in if statements, while statements and for statements are available outside the if/while/for statement for use
This is different to many other languages where accessing the variable would throw an exception because of it being out of scope
Just to note, if the if/while/for statement is false and does not execute, a for example would not be initialised and it would throw an error like so:
>>> if False:
... a = 5
...
>>> print a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
I have the following decorator with parameters:
from functools import wraps
def pdecor(p):
def decorator(fn):
#wraps(fn)
def wrapper(*args, **kwargs):
p -= 1
return fn(*args, **wargs)
return wrapper
return decorator
Trying to use the decorator results in :
>>> #pdecor(1)
... def run(): pass
...
>>> run()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in wrapper
UnboundLocalError: local variable 'p' referenced before assignment
>>>
Why can't I change the p?
Because you assign to p inside wrapper, Python treats the p inside wrapper as local to wrapper. In Python 3 you can use nonlocal p to mark p as referenced from an outer scope. In Python 2 there is no way to assign to the intermediate p, although you can get a reference to the same value by passing it into the nested functions as a keyword argument (e.g., def decorator(fn, p=p)).
However, it's not clear what you're getting at with this anyway. p is already only local to pdecor. No code outside pdecor can access p, so decrementing it won't have any effect on any code elsewhere. So whether you can decrement p or not, it won't really accomplish anything.