Search code examples
pythonsetattr

Different functions, same result


So I'm trying to dynamically set properties to a class with different names where each property makes a unique call to my database. However, when I access the property objects, all of them return the same result despite looking up on different columns with completely different values each. Here is the code:

class Configs:

    def __init__(self, guild_id):
        self.guild_id = guild_id

for x in configs:

    def fget(self):
        return cur.execute(f'SELECT {x.name} FROM configs WHERE guild_id = ?', (self.guild_id,)).fetchone()[0], x.name

    def fset(self, value):
        cur.execute(f'UPDATE configs SET {x.name} = ? WHERE guild_id = ?', (value, self.guild_id))
        con.commit()

    setattr(Configs, x.name, property(fget, fset))

The configs variable is a list of objects where each object has a name attribute which points to a string. The result is always the one that the last element of the configs array would produce, I suspect this is happening because x.name is used to make the calls and once the for loop is done, x remains as the last element of the array.


Solution

  • You are under the false impression that defining a function binds the function to the variable values at time of defining. This sounds complex, sorry. I'll try to explain.

    You are defining functions in a loop (fget, fset). In the functions you use a variable of the loop (x). This will work, but not in the way you expect it to work. All functions will be exactly alike, always accessing the value of a global variable x at the time of their calling. The value at the time of defining will not be taken into consideration.

    See for example this:

    a = []
    for i in range(3):
      def f(): return i
      a.append(f)
    
    a[0]()  # will return 2
    del i
    a[0]()  # will raise a NameError because there is no i anymore
    

    To solve your issue you need to pass the value at time of defining into the functions:

    def fget(self, x=x):
        return cur.execute(f'SELECT {x.name} FROM configs WHERE guild_id = ?', (self.guild_id,)).fetchone()[0], x.name
    
    def fset(self, value, x=x):
        cur.execute(f'UPDATE configs SET {x.name} = ? WHERE guild_id = ?', (value, self.guild_id))
        con.commit()