0

I'm trying to create a static variable to be accessed through different classes, assigning value to it, and getting this value when needed. I did use this way in order to achieve this, and that leads me to including a property as following:

class GetPartition(Partition):
    _i = 100
    def __init__(self):
        super(Partition,self).__init__("get")
    def get_i(self):
        return type(self)._i
    def set_i(self,val):
        type(self)._i = val
    i = property(get_i, set_i)

and this is class Partition if needed:

class Partition(BaseCommand):
    def __init__(self,type):
        super(Partition,self).__init__("databaseTest")
        self.type = type

So, when assigning a value to ifrom another class, I'm assigning it directly like:

GetPartition.i = 5

and among that class when printing GetPartition.i it gives me 5, but when trying to get this value from another class:

partitionNum = GetPartition()
print(partitionNum.i) # 100
print(partitionNum.get_i()) # 100
print(GetPartition.i) # <property object at 0x12A938D0>
print(GetPartition._i) # 100
Community
  • 1
  • 1
Muhammed Refaat
  • 8,914
  • 14
  • 83
  • 118
  • 1
    When you assign 5 to `i` by way of `GetPartition.i = 5`, you are **overwriting the property**, and *bypassing* the property setter. The property no longer exists at that point. You can confirm this by doing `GetPartition.i = 5`, `gp = GetPartition()`, `gp.i = 2`, `print(gp.i, GetPartition.i)`. The property getter and setter only works with instances of `GetPartition`, not the class itself. If you want to update the "static variable" (keeping in mind that Python *does not actually have static variables*), you'll need to do `GetPartition._i = 5` as in your example code. – Rick Mar 29 '15 at 18:55
  • @RickTeachey At first thx for your response, then I tried to set it like that `GetPartition._i = 5` but I faced similar problems when trying to get it, any ideas how to get it from another class? – Muhammed Refaat Mar 30 '15 at 10:20
  • Well the easy way to get it from somewhere else is to do `print(GetPartition._i)` instead of `print(GetPartition.i)`. I realize this isn't optimal. There are other ways to accomplish what you want, but all of the ones I am aware of are pretty complicated. – Rick Mar 31 '15 at 02:08

1 Answers1

1

As I explained in my comment, the problem comes when you assign 5 to i by way of:

GetPartition.i = 5

With this line of code, you are overwriting the property, and "bypassing" the property setter. What I mean by that is: the property setter is not called when you call its attribute name from the class; it is only called when you call its attribute name from a class instance.

Since it has been overwritten, the property no longer exists at that point and all references to the i attribute, whether from class instances or from the class itself, are distinct. They will no longer retrieve the same object, but distinct objects.

You can confirm this problem by doing this:

gp = GetPartition()
print(GetPartition.i) # the property is returned
GetPartition.i = 5 # here the property is overwritten
print(GetPartition.i) # 5 ; the property is gone
print(gp.i) # 5 because gp instance doesn't have its own i
gp.i = 2 # now gp does have its own i
print(gp.i) # 2
print(GetPartition.i) # 5 ; i is not synced

As I said above, the property getters and setters (and descriptors in general) only work with instances of GetPartition, not the class itself. They can be forced to work with the class itself by creating a metaclass - which is the class of a class - for your class; this is considered "deep black magic" by many people, and I don't recommend going that route if you can avoid it.

I believe the below example is probably the simplest way to implement the behavior you want. This approach abandons the use of properties in favor of overriding the attribute getter and setter methods directly:

class Example():
    i = 1 # this is a "static variable"
    j = 3 # this is a regular class attribute
    #designate which of the class attributes are "static"
    statics = {'i'}
    def __getattribute__(self, attr):
        '''Overrides default attribute retrieval behavior.'''
        if attr in Example.statics:
            #use class version if attr is a static var
            return getattr(Example, attr)
        else:
            #default behavior if attr is not static var
            return super().__getattribute__(attr)
    def __setattr__(self, attr, value):
        '''Overrides default attribute setting behavior.'''
        if attr in Example.statics:
            #use class version if attr is a static var
            setattr(Example, attr, value)
        else:
            #default behavior if attr is not static var
            super().__setattr__(attr, value)

#testing
if __name__ == '__main__':
    print("\n\nBEGIN TESTING\n\n")

    e = Example()
    #confirm instance and class versions of i are the same
    test = "assert e.i is Example.i"
    exec(test)
    print(test)

    e.i = 5
    #confirm they remain the same after instance change
    test = "assert e.i is Example.i"
    exec(test)
    print(test)

    Example.i = 100
    #confirm they remain the same after class change
    test = "assert e.i is Example.i"
    exec(test)
    print(test)

    e.j = 12
    #confirm both versions of j are distinct
    test = "assert e.j is not Example.j"
    exec(test)
    print(test)

    print("\n\nTESTING COMPLETE\n\n")

If you are not familiar with __getattribute__ and __setattr__, I should let you know that overriding them is often quite perilous and can cause big problems (especially __getattribute__). You'll find many people simply say "don't do it; rethink your problem and find another solution". Doing the overrides correctly requires a deep understanding of a wide range python topics.

I do not claim to have this deep understanding (though I think I have a pretty good understanding), so I cannot be 100% certain that my overrides as given above will not lead to some other problem for you. I believe they are sound, but just be aware that these particular corners of python can be pretty tricky.

Rick
  • 43,029
  • 15
  • 76
  • 119
  • Thanks for this great illustration, in fact I tried this way but can't manage it, it gives me `super() takes at least 1 argument (0 given)` at the line `super().__setattr__(attr, value) ` when trying to set the value `e.i = 5`. and giving it 'self' doesn't work as it requires a certain type. – Muhammed Refaat Apr 05 '15 at 14:41
  • Also worth to mention that I won't get the value from the same class but from another one with different layout – Muhammed Refaat Apr 05 '15 at 14:42