Home > Software engineering >  Error in Comprehensions list but correct in for loop
Error in Comprehensions list but correct in for loop

Time:02-13

I'm trying to create static variables in a class with exec and Comprehensions list:


class DictTranslator:
    
    def __init__(self):
        self.create_static_variables()

    def get_value(self, key):

        return self.dict_.get(key)
    
    def create_static_variables(self):
        [exec("self."   str(value)   " = "   str(key))   for key, value in self.dict_.items()]
        
    
class AppImportanceTranslator(DictTranslator):


    dict_ = {
        -1: 'Unknown',
        0: 'Gone',
        1: 'Foreground',
        2: 'ForegroundService'
    }

but I've this error: NameError: name 'self' is not defined

but I can change the code to:

class DictTranslator:
    
    def __init__(self):
        self.create_static_variables()

    def get_value(self, key):

        return self.dict_.get(key)
    
    def create_static_variables(self):
        for key, value in self.dict_.items():
            exec("self."   str(value)   " = "   str(key))
        
    
class AppImportanceTranslator(DictTranslator):


    dict_ = {
        -1: 'Unknown',
        0: 'Gone',
        1: 'Foreground',
        2: 'ForegroundService'
    }
    

And this code is correct.

Any idea?

My python version is 3.8.10

CodePudding user response:

First of all:

  • Don't use list comprehensions for side effects. You are creating a list object with None references, then discarding it again. That's a waste of computer resources.

    Always use a for loop if all you needed was a loop.

  • You can set attributes on self with the setattr() function. exec() should be a last resort, as it opens you to all sorts of performance and security issues.

You can use:

for key, value in self.dict_.items():
    setattr(self, value, str(key))

to achieve the same thing.

Alternatively, give your class a __getattr__ hook and have it look up attributes lazily:

def __getattr__(self, name):
    try:
        return str(self.dict_[name])
    except KeyError:
        # raise an AttributeError instead
        raise AttributeError(name) from None

The __getattr__ hook is called for any attribute not explicitly set on your instance.

Now, for what actually goes wrong:

exec() can't see self because a list comprehension is a new scope, as if you ran:

def create_static_variables(self):
    def inner_function():
        for key, value in self.dict_.items():
            exec("self."   str(value)   " = "   str(key))
    inner_function()

At compile time, Python can see the self in self.dict_.items() and bind it as a closure (taking it from the local namespace of the create_static_variables() call. But exec() doesn't run at compile time and so can only see globals or locals.

You could use exec(..., globals(), {"self": self}) to pass along the self binding in a new local namespace, to work around that.

  • Related