The following piece of code:
class A:
def __init__(self):
self.__var = 123
def getV(self):
return self.__var
a = A()
a.__var = 10
print a.getVar(), a.__var
prints 123 10. Why does this behavior occur? I would expect a.getVar() to print out 10. Does the class internally interpret self.__var as self._A__var?
The double underscore attributes in Python has a special effect, it does "name mangling" that is it converts the attribute __var to _A__var i.e. _<classname>__<attributename> at runtime.
In your example when you assign 10 to the attribute __var of a object, it is essentially creating a new attribute __var and not modifying the self.__var. This is because the self.__var is now _A__var due to name mangling.
This can be seen if you print the __dict__ of the a object:
class A:
def __init__(self):
self.__var = 123
def getV(self):
return self.__var
a = A()
print (a.__dict__)
>> {'_A__var': 123}
If you don't assign any value to __var and try to print it directly, it will result in an AttributeError:
class A:
def __init__(self):
self.__var = 123
def getV(self):
return self.__var
a = A()
print (a.__var)
>> AttributeError: 'A' object has no attribute '__var'
Now if you try to assign to the new mangled attribute name, you would get the right result (but this process is meant to prevent accidental usage of such variables):
class A:
def __init__(self):
self.__var = 123
def getV(self):
return self.__var
a = A()
a._A__var = 10
print (a.getV())
>> 10
Related
I'm not even sure if this is possible. What I'm trying to do is having an object call a method, which would reassign a new class object to the variable
Definition:
class A():
def __init__(self):
self.number = 1
def becomeB(self):
# a method that will assign this variable a class B object
class B():
def __init__(self):
self.name = "I am B"
What I'm trying to achieve:
myObj = A()
myObj.becomeB()
print(myObj.name)
Output: I am B
It doesn't make any sense to convert object of type A to be of type B.
Instead, I would recommend you to make becomeB a function that returns object B.
For example:
class B():
def __init__(self):
self.name = "I am B"
class A():
def __init__(self):
self.number = 1
def becomeB(self):
return B()
myObj = A()
b = myObj.becomeB()
print(b.name) # output: I am B
If you want an object to behave like an instance from another class you can reassign the __class__ attribute:
def becomeB(self):
self.__class__ = B
Here's a more complete example:
class A:
def __init__(self, name):
self.name = name
def foo(self):
return f'A says: Hello {self.name}'
def becomeB(self):
self.__class__ = B
class B:
def foo(self):
return f'B says: Hello {self.name}'
a = A('World')
print(a.foo()) # prints: A says: Hello World
a.becomeB()
print(a.foo()) # prints: B says: Hello World
Here is my class implementation
class A:
def __init__(self,a,b):
self.result = None
self.a = a
self.b = b
self.add()
def add(self):
self.result = self.a+self.b
return
My class A has result as an attribute. I want to access the class attribute i.e; result by reading the result string from dictionary. Below is the implementation I tried.
x = 'result' # I will get from other source
obj = A(1,2)
obj.x # Here x = result and the result is the actual class attribute
Error:
AttributeError: A instance has no attribute 'x'
Could anyone tell me how to access the class attributes by converting the string to object?
Use getattr
getattr will do exactly what you're asking.
class A:
def __init__(self,a,b):
self.result = ''
self.a = a
self.b = b
self.add()
def add(self):
self.result = self.a+self.b
return
x = 'result' # I will get from other source
obj = A(1,2)
obj.add() #this was missing before thus obj.result would've been 0
print getattr(obj, x) # Here x = result and the result is the actual class attribute
As John mentions, getattr is probably what you are looking for. As an alternative, every object has a __dict__ variable containing key value pairs:
obj = A(1,2)
obj.add()
print(obj.__dict__.get('result'))
You are better off in the general case, however, using getattr
Python has a magic __getattr__ method that allows custom values to be returned:
class A(object):
def __getattr__(self, name):
return name
B = A()
print B.foo # 'foo'
However, calling A.foo has no similar effect, because A is not an instance.
Using metaclasses, Google App Engine raises this error on instantiation:
File "/base/python27_runtime/python27_lib/versions/1/google/appengine/ext/db/__init__.py", line 913, in __init__
key_name.__class__.__name__)
BadKeyError: Name must be string type, not tuple
Assuming the referenced question is correctly implemented, what other ways can a magic class __getattr__ be implemented?
The metaclass solution should work, here is an example:
class GetAttrMeta(type):
def __getattr__(self, name):
return name
class A(object):
__metaclass__ = GetAttrMeta
print A.foo # 'foo'
Or with Python 3.x:
class GetAttrMeta(type):
def __getattr__(self, name):
return name
class A(object, metaclass=GetAttrMeta):
pass
print(A.foo) # 'foo'
Not sure if this answers your question, but maybe checkout property descriptors ..
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print 'Retrieving', self.name
return self.val
def __set__(self, obj, val):
print 'Updating' , self.name
self.val = val
>>> class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5
>>> MyClass.x
Retrieving var "x"
10
>>> MyClass().x
Retrieving var "x"
10
>>>
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5
Given an arbitrary object:
class Val(object):
def __init__(self):
this_val = 123
I want to create an abstract base class which has an attribute that is a Val():
class A(object):
foo = Val()
I would expect that when my children inherit from that class, they would get copies of Val(). For example:
class B(A):
pass
class C(A):
pass
I would expect the following behavior:
>>> b = B()
>>> c = C()
>>> c.foo.this_val = 456
>>> b.foo.this_val
123
But instead I get:
>>> b.this_val
456
I understand that I could just self.foo = Val() into the init to achieve that behavior, but I have a requirement that foo remain an attribute (it is a model manager in django). Can anyone suggest a work around for this?
EDIT: I really need to be able to access the value as a class attribute, so my desired behavior is:
>>> C.foo.this_val = 456
>>> B.foo.this_val
123
The attribute foo only exists on A. You will have to use a metaclass to add a new Val to each class.
class Val(object):
def __init__(self):
self.this_val = 123
class MC(type):
def __init__(self, name, bases, d):
super(MC, self).__init__(name, bases, d)
self.foo = Val()
class A(object):
__metaclass__ = MC
class B(A):
pass
B.foo.this_val = 456
print A.foo.this_val
print B.foo.this_val
Maybe using a descriptor would suit your requirements:
class Val(object):
def __init__(self):
self.this_val = 123
class ValDesc(object):
def __init__(self):
self.cls_lookup = {}
def __get__(self, obj, objtype=None):
return self.cls_lookup.setdefault(objtype, Val())
class A(object):
foo = ValDesc()
class B(A):
pass
class C(A):
pass
Now, as long as you make sure you don't set the instance attribute "foo" of any of your objects, they will have a class attribute that is individual to each subclass:
b = B()
c = C()
cc = C()
c.foo.this_val = 456
print c.foo.this_val # 456
print cc.foo.this_val # 456
print b.foo.this_val # 123
EDIT: With the edit I made some hours ago, changing the key in __get__ to be objtype instead of obj.__class__, this also works when accessing the class attributes directly:
print B.foo.this_val # 123
print C.foo.this_val # 456
Do both.
Make it a class attribute, but also initialize it to a fresh instance in the __init__ function. That way the reference stored isn't a shared one.
class A():
def __init__(self):
self.__var = 5
def get_var(self):
return self.__var
def set_var(self, value):
self.__var = value
var = property(get_var, set_var)
a = A()
a.var = 10
print a.var == a._A__var
Can anyone explain why result is False?
The property decorator only works on new-style classes. In Python 2.x, you have to extend the object class:
class A(object):
def __init__(self):
self.__var = 5
def get_var(self):
return self.__var
def set_var(self, value):
self.__var = value
var = property(get_var, set_var)
Without the behavior of the new-style class, the assignment a.var = 10 just binds a new value (10) to a new member attribute a.var.