1

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.

Exa
  • 185
  • 12
  • What is `x`? is it a `Configs` ? Where do you set the value of `x.name` ? – khelwood Mar 06 '17 at 10:30
  • 1
    `x` is a simple object with a single `name` attribute that has a string pre-assigned to it. I'm constructing these objects in another part of the code. `configs` is just a list with 3 or more of these objects. – Exa Mar 06 '17 at 10:38
  • So you're trying to set a property, called whatever is the value of `x.name`, that reads and writes from the database when it is accessed? Like an active record? – khelwood Mar 06 '17 at 10:44
  • Not really, I'm trying to create a property that makes a call to the database and returns a value whenever it's accessed and I can also update the database with a simple assignment syntax. – Exa Mar 06 '17 at 10:47
  • Is that not what I said? – khelwood Mar 06 '17 at 10:48
  • Oh, I thought you meant that I was keeping a record of the results whenever the properties were accessed. Then, yes I am trying to do what you said. – Exa Mar 06 '17 at 10:51

2 Answers2

1

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()
Alfe
  • 56,346
  • 20
  • 107
  • 159
0

If I follow what you're trying to do, you're quite correct that when your properties are called, they will use whatever is the current value of x.name (which will be whatever it was left at at the end of your loop).

Here is a possible way to fix it: assign x.name to another variable that your property functions have access to. A default argument might work well for that.

for x in configs:

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

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

    setattr(Configs, x.name, property(fget, fset))
khelwood
  • 55,782
  • 14
  • 81
  • 108