Property setter not working when attribute starts with "__"? - python

I am using Python 3.8.6 and this works fine
class A:
#property
def _a(self):
return getattr(self, '_a_', 0)
#_a.setter
def _a(self, value):
self._a_ = value
a = A()
print(a._a) # prints 0
a._a = 10
print(a._a) # prints 10 as expected
This doesn't work
class A:
#property
def _a(self):
return getattr(self, '__a', 0)
#_a.setter
def _a(self, value):
self.__a = value
a = A()
print(a._a) # prints 0
a._a = 10
print(a._a) # prints 0 again
That's mind blowing! the only difference between the first and second example is that the private attribute is __a instead of _a_
Any idea why? I wasn't able to figure it out

It's due to private name mangling, but it doesn't apply to the contents of string literals like the one you're passing to getattr().
Fortunately the fix is simple:
class A:
#property
def _a(self):
return getattr(self, '_A__a', 0)
#_a.setter
def _a(self, value):
self.__a = value
a = A()
print(a._a) # prints 0
a._a = 10
print(a._a) # prints 10 now

Related

Why #property broke synchronizing with source object on proxy class

I want to create a proxy in Python because of function and attributes access (something like private). I create the proxy with references to functions in the source object. But I have a problem, that functions have no problem with changing attributes but property yes. Here is an example:
A working example
class A:
def __init__(self):
self.value = 1
def get_value(self):
return self.value
class Proxy:
def __init__(self, cls):
self.get_value = cls.get_value
# del cls
a = A()
p = Proxy(a)
print(a.get_value(), p.get_value())
a.value = 2
print(a.get_value(), p.get_value())
Output:
1 1
2 2
Not working:
class A:
def __init__(self):
self.value = 1
#property
def get_value(self):
return self.value
class Proxy:
def __init__(self, cls):
self.get_value = cls.get_value
# del cls
a = A()
p = Proxy(a)
print(a.get_value, p.get_value)
a.value = 2
print(a.get_value, p.get_value)
Output:
1 1
2 1
Can someone explain me where the problem is and if there is any solution for this? I could use functions, but I think #property is more Python solution. And I really want to know what is the difference. Thank you
In Proxy.__init__, you end up executing:
self.get_value = a.get_value
and very different things happen in your two examples.
In the first case, a.get_value is a method of a. So, calling p.get_value() is the same as calling a.get_value(). You get identical results.
In the second case, you have already defined a.get_value as a property, so self.get_value = a.get_value is basically self.get_value = 2, it is just an int attribute of p.

how to pass the name of a variable as a variable

How can I set the variable I want to change as a function argument? I want to define only one function, and not set_a(value), set_b(value), set_c(value), ...
class MyVarClass:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
# this works, but I don't want to write n functions
def set_a(myvar_object, value):
myvar_object.a = value
# this is what I actually want:
def set_vars(myvar_object, var_name, value):
myvar_object.var_name = value
myvar = MyVarClass()
# I want to do the same as myvar.a = 4
set_a(myvar, 4) # works as intended, now myvar.a is 4
set_vars(myvar, a, 4) # error, a is not defined
What you usually do is to create a method to the class like this:
class MyVarClass:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
def set_a(self, value):
self.a = value
If, for some reason, can't do it like this and you only have the name of the attribute as string, then you can use setattr:
setattr(myvar_object, 'a', value)
But usually what you do is just this line:
myvar_object.a = 4
This is done with setattr.
def set_vars(myvar_object, var_name, value):
setattr(myvar_object, var_name, value)
This isn't necessarily the best way of doing this. It often suggests a different data structure would be better, but in case that isn't the case here.
You'll note if you go this route, there isn't much reason to have a def rather than just call setattr directly, unless you think you'll change things in the future.
what about using a dict:
class MyVarClass:
def __init__(self):
self.vars = {"a": 1, "b": 2, "c": 3}
def set_vars(myvar_object, var_name, value):
myvar_object.vars[var_name] = value
**
var_name = ''
class MyVarClass:
def __init__(self):
self.a = 1
self.b = 2
self.c = 3
# this is what I actually want:
def set_vars(myvar, var_name, value):
myvar.var_name = value
print (myvar.var_name)
myvar = MyVarClass()
set_vars(myvar, "b", 6)
**

Extending class attribute in Python

I'm having a hard time summarizing my question so I apologize if this is a duplicate.
I have a class like such:
class MyClass:
timer = 60
I want timer to represent an integer but also extend some custom methods. For example:
>>> c = MyClass()
>>> c.timer
60
>>> c.timer.wait() # This would wait 60 seconds
True
>>> c.timer
0
How can I do this in Python?
Not sure what you are trying to achieve, but your MyClass can be implemented as follows:
class MyClass:
def __init__(self):
self.a = 0
self.b = 0
def do_some_operation(self):
# do something using a and b
# and return True if success
self.a += 100;
return True
c = MyClass()
print(c.do_some_operation()) # True
print(c.a) # 100
Maybe you should try to make a class that simulates the integers:
class Integer:
value = 0
def __init__(self, value):
self.value = value
def __add__(self, other):
return Integer(self.value + other.value)
#Would need to do the other basic arithmetic things. I'll leave that to you...
def __str__(self):
return str(self.value)
def do_some_operation(self):
return True
class MyClass:
a = Integer(0)
b = Integer(0)
c = MyClass()
print c.a
print c.b
print c.a + c.b
print c.a.do_some_operation()
Gives:
0
0
0
True
I would be careful about doing this though. There may be another more suitable way.

Python Class Objects Attribute Referencing

I have two classes. a and b.
In one of class a's methods, I created an object of class b. One of class b attributes takes a function. So say I gave it a random function but does this function of class b have access to class a's attribute? even though I didn't pass it in directly as a parameter?
class b:
def __init__(self):
self.attribute_function = None
class a:
def __init__(self):
self.temp = 10
self.counter = 0
def temp(self):
obj = b()
obj.attribute_function = lambda self: self.counter < self.temp
return obj.attribute_function()
if __name__ == "__main__":
#pass
obj = a()
print obj.temp()
In the above example, I tried to provide a really basic example, but if you run it, it doesn't work...
Revised Code, class a should look like this:
class a:
def __init__(self):
self.temp = 10
self.counter = 0
def temp(self):
obj = b()
obj.attribute_function = lambda args: self.counter < self.temp
return obj.attribute_function(1) # i added this 1 to fill in arg
This works:
class b:
def __init__(self):
self.attribute_function = None
class a:
def __init__(self):
self._temp = 10
self.counter = 0
def temp(self):
obj = b()
obj.attribute_function = lambda self=self: self.counter < self._temp
return obj.attribute_function()
if __name__ == "__main__":
obj = a()
print obj.temp()
On problem you had is self.temp = 10 which shadowed your method temp().
Another problem: lambda self: self.counter < self._temp. Your lambda function was expecting an argument. But omitting self is not a good idea lambda : self.counter < self._temp, because if you call obj.attribute_function() somewhere where self is not available or has changed - it will not find self or use another self. self=self fixes that.
But generally such magic is an anti-pattern. Tell us what are your trying to achieve, and there should be a better way to do what you want. Otherwise this kind of code will ensure many headaches.
I think this is a better solution (called strategy pattern):
class B:
def __init__(self, a):
self.a = a
def temp(self):
return self.a.temp()
class A:
def __init__(self):
self._temp = 10
self.counter = 0
def temp(self):
return self.counter < self._temp
if __name__ == "__main__":
obj = B(A())
print obj.temp()
Your example does not work because you have a name collision at temp
You have assigned temp to be both a method:
def temp(self):
and an attribute:
self.temp = 10

how to create nicer variable interface in python class

I want a variable to do more than just be set when I set it.
and the interface to be as clean as possible.
short: what I'd want:
# have class with a variable that I can access:
print myInstance.var
42
# change the variable
myInstance.var = 23
# have the change kick off another method:
self.var was changed: 23!!
hmm.. so what I can do: Use the variable and a setter method:
class Test:
def __init__(self):
self.var = 1
print( 'self.var is: ' + str(self.var) )
def setVar(self, value):
self.var = value
print( 'self.var changed: ' + str(self.var) )
t = Test()
self.var is: 1
# so I have t.var at hand:
print t.var
1
# and change it this way
t.setVar(5)
self.var changed: 5
But then i have 2 different things to work with..
Ok I could make a method to interact with the var:
class Test:
def __init__(self):
self.var = 1
print( 'self.var is: ' + str(self.var) )
def method(self, value=None):
if value == None:
return self.var
self.var = value
print( 'self.var changed: ' + str(self.var) )
t = Test()
self.var is: 1
# to get the value then:
print t.method()
1
# to set it:
t.method(4)
self.var changed: 4
# and verifiy:
print t.method()
4
This is nice already. I've seen it in different post on other languages. but I dunno. Is there a be better solution in python?!?
Maybe I'm paranoid but but to me it'd just feel nicer to just do t.var = 5 and have something kicked off too.
I think you want python Properties. Check this out. Something like:
class Test:
def __init__(self):
self._var = 1
#property
def var(self):
return self._var
#var.setter
def var(self, value):
# add stuff here that you want to happen on var assignment
self._var = value
You can use a property. Note that if the setter method is expensive, it's better to use a method. People expect attribute access to be fast.
class Foo(object):
def __init__(self):
self._var = None
#property
def var(self):
return self._var
#var.setter
def var(self, whatever):
self._var = whatever
do_whatever()
x = Foo()
print x.var # prints None
x.var = 2 # sets x.var and does whatever

Categories