I was going through classes doc. And I found the following paragraph.
Notice that code passed to exec() or eval() does not consider the classname of the invoking class to be the current class; this is similar to the effect of the global statement, the effect of which is likewise restricted to code that is byte-compiled together. The same restriction applies to getattr(), setattr() and delattr(), as well as when referencing __dict__ directly.
I have doubts regarding the 'byte-compiled restriction'. I understand exec creates a new environment for code execution where locals and globals are shared to it. And the restriction for global keyword makes sense to me through the following code.
x = 2
def fun():
exec('global x')
x = 3
print("Local - ", x)
fun()
print("Global - ", x)
#Output
Local - 3
Global - 2
The exec statement above will not convert the x inside the function to global.
However, for setattr, getattr and delattr, why it will be affected? Since locals and globals are shared with the exec and getattr and other commands deal will objects, why would they be restricted?
class cl:
def nest_fun(self):
exec("setattr(cl, 'var', 2)")
print(cl.var)
cl().nest_fun()
#Output
2
Maybe there's something in setattr and other commands that I'm missing?
1 Answer 1
This is about name mangling in class definitions.
Consider this example :
class MyClass:
__class_var = 2 # Mangled to _MyClass__class_var
exec("__exec_var = 1") # NOT mangled—sets literal '__exec_var'
print(MyClass.__dict__.keys())
# Output
# dict_keys(['__module__', '__dict__', ..., '_MyClass__class_var', '__exec_var', ...])
The key __exec_var in MyClass is executed not in the context of the class, so no mangling.
That's why this code would not work :
class MyClass:
__class_var = 2
def method(self):
# '__class_var' isn't mangled here
exec("print(self.__class_var)")
MyClass().method()
Output :
# AttributeError: 'MyClass' object has no attribute '__class_var'
A direct print(self.__class_var) in the method would be mangled (to _MyClass__class_var) at compile time and work.
In the end, what about getattr ?
Consider this code :
class MyClass:
__class_var = 2 # Sets '_MyClass__class_var'
def get_direct(self):
return self.__class_var # Mangled
def get_via_getattr(self):
return getattr(self, '__class_var') # No mangling, AttributeError
obj = MyClass()
print(obj.get_direct())
print(obj.get_via_getattr())
# Output
# 2
# AttributeError: 'MyClass' object has no attribute '__class_var'
getattr(self, '__class_var') doesn't work,
however if you followed me, you should now understand that : getattr(self, '_MyClass__class_var') works (but please never do that).
And last but not least working example code :
class MyClass:
__class_var = 2 # Sets '_MyClass__class_var'
exec("__exec_var=2")
def get_direct(self):
return self.__class_var # Mangled
def get_exec_var_via_getattr(self):
return getattr(self, '__exec_var')
obj = MyClass()
print(obj.get_direct())
print(obj.get_exec_var_via_getattr())
I let you guess the output!
What to remember?
- Essentially, these restrictions are here as a "warning" for class definitions involving name mangling.
exec(andeval) expressions are not compiled at the same time. getattr/setattr/delattrare "restricted" similarly because they use runtime strings and bypass mangling entirely as you seen on the examples above. (your example works because it's runtime, non-mangled, and outside the class body)
bonus : test this code (but never do that please, it's just for the example)
class MyClass:
__class_var = 2
def method(self):
exec("print(self._MyClass__class_var)")
MyClass().method()
6 Comments
self.__class_var converted to its mangled name at compile time? And for getattr it is not the case, as it is left to be executed dynamically? 2. I thought the restriction was in using setattr and other commands inside exec. But as you told and I want to confirm, getattr, setattr are themselves dynamic commands and are not touched by compiler, is it?self.__class_var is mangled (as you can see on my first example (in the keys of the __dict__) -- and Yes, the names in getattr are taken "as is" so it'll look into MyClass.__dict__ with the raw given name. --------- 2. Correct, all 3 getattr, setattr and delattr are dynamic; runtime functions. --------- See exec like a vehicle that "protects" variables from the outside, the passengers will behave exactly like people outside the vehicle, it's just that they are in a different environment. Being in the car "protects" them from being altered.super command also doesn't work inside exec. Is there any general statement the sentence is talking about? ----------- 2. The first sentence which talks about global and exec. Does it mention two different types of restrictions? For exec it is that it cannot see the context. And for global it is that it can't work inside exec? And which of these two restrictions is for setattr? Sorry, if it feels that I am asking similar questions. But, I can't seem to grasp it.Explore related questions
See similar questions with these tags.