Changing an object's class while maintaining its attributes and functions - python

If I have 2 classes defined like this:
class A(object):
a = 10
class B(A):
b = 20
If I create an object:
c = A()
And then do:
c.__class__ = B
Is it a valid way to change ('upgrading') the class of the object, maintaining the primary class attributes and methods and gaining the secondary class attributes and methods?
If true, this only makes sense for this cases where the class to which we are changing the object inherits from the previous class? Best regards.
UPDATED:
To give more context.
I have the following class EmbrionDevice.
class EmbrionDevice(object):
def __init__(self, device_info, *args, **kwargs):
super(EmbrionDevice, self).__init__(*args, **kwargs)
# Serial number unique 64-bit address factory-set
self.shl = device_info['source_addr_long']
# 16-bit network address
self.my = device_info['source_addr']
# Node identifier
self.ni = device_info['node_identifier']
# Parent Address
self.pa = device_info['parent_address']
# Device type, 0-coordinator, 1-router, 2-End Device
self.dt = device_info['device_type']
# Device type identifier xbee or Digi device
self.dd = device_info['device_type_identifier']
# Device attributes summary in a dictionary
self.info = device_info
# Embrion future function
self.function_identifier = None
# Device state definition
self.state = DEV_STATE_CODES['embrion']
self.status = DEV_STATUS_CODES['no status']
That i would later like to change/upgrade, to one of the following specific device classes:
class PassiveDevice(EmbrionDevice):
pass
class ActiveDevice(EmbrionDevice):
pass
Basically i wanted to ease my copy, avoiding the copy of all the attributes.

This is not a valid way to change class of a instance object, A simple example can demonstrate it :-
class A(object):
a = 10
def __init__(self):
self.b = 20
self.c = 30
class B(A):
d = 35
def __init__(self):
self.x = 70
self.y = 80
c = A()
c.__class__ = B
print c
<__main__.B object at 0x02643F10>
So now c is instance of class B, Try printing instance attributes:
print c.x
print c.y
It says:
AttributeError: 'B' object has no attribute 'x'

That's definitely a hack, and this is also a hack, but I find it do be a bit cleaner:
In [1]: class A(object):
...: a = 10
...:
In [2]: class B(A):
...: b = 20
...:
In [3]: c = A()
In [4]: new_c = B()
In [5]: new_c.__dict__.update(c.__dict__.copy())
In [7]: repr(new_c)
Out[7]: '<__main__.B object at 0x102f32050>'
In [8]: new_c.b
Out[8]: 20
I'm not sure if your approach would work or not, but, this way, you're copying the properties of the old object into a new object that was properly instantiated. If you change .__class__, you can't guarantee that the old variable will reference a properly-created new-class object, as __init__(), __new__(), etc. wouldn't run.
To copy functions, and, this is ugly... but, you could take an approach like this:
In [18]: for name, obj in c.__class__.__dict__.iteritems():
....: if hasattr(obj, '__call__'):
....: # Copy the function.
....:
test
There are various hacky methods of adding functions to an existing object dynamically. They're all ugly, but, they can be found here.

You have a misunderstanding of what are "class attributes" in Python -
All instance attributes are kept in the instance itself: it does have a __dict__ attribute which is a dictionary where all the attributes defined by code like self.shl = device_info['source_addr_long'] is kept. (This statement creates an shl entry on that dict, for example).
These assignments are run inside the __init__method. If you change an object's class by assigning to its __class__ , it works in a sense: that is its new class. The methods the new class may have defined are now acessible. But all the attributes which were set in the previous class' __init__ still exist, because they are set on the instance's __dict__ that was not changed;. From what I got, this may be exactly what you want - but please note that methods on the original class (as well as class attributes - i.e., attributes defined on the class body itself) will not be acessible, unless the new class itself inherits from the original class. As in the example you show, this is what you are doing, this approach might actually work for you.
But be careful, and do some extensive unit testing, and testing on the interactive console.
An alternative for you might be to use zope.interface - tis will allow you to have a single object, but that "looks like" an object with different attributes and methods to other parts of the code, which might need an specific interface.

Related

Is there a way in python to create a class that inherits from another class/object passed by an object?

I want to create a class that inherits another class and instantiates it with the object from the prev. class to have a new object from the new class ( old class attrs. and methods with new attrs. and methods).
example:
class A():
attrs...
methods...
class B(A):
def __init__(self, a_obj):
...
A_attrs + B_attrs...
A_methods + B_methods...
a = A()
# assign some values to 'a'
b = B(a)
# a and b should have the same params and behaviors
Is there a way to implement such an alternative class and use the new object?
It is hard to tell what exactly you are trying to achieve, but having the exact things you want to forward to a, it is easy to achieve.
No need to use metaclasses, but depending on the behavior you want, the special __getattribute__ and __setattr__ methods might help.
Take in mind that as far as methods are concerned, the inheritance mechanism will already do that: any methods called in an instance of B will be forwarded to the method defined in A, unless there is an overriding implementation of it in B: in this case the overriding method have to explicitly run the method in A by using a super() call, or skip it altoghether: it is up to the implementation.
Method overriding is independent of instances. If you want them to "see" a particular instance of A passed at B object instantiation, it is just the attributes in that instance that matter.
Now, if you want instances of B to proxy over to the attributes a particular instance of A, the special methods I mentioned can do the same bridge. We can implement those in a way that if any attribute access is attempted in attribute that existis in the a instance, that one is used instead. Also, the special behavior can be implemented in a mixin class, so you are free to implement your business logic in B, and deffer all special attribute handling mechanisms to the mixin instead.
_SENTINEL = object()
class ProxyAttrMixins:
def __init__(self):
# Do nothing: just prevent the proxied class` __init__ from being run
pass
def _inner_get(self, attrname):
bound_getattr = super().__getattribute__
try:
proxied = bound_getattr("proxied")
except AttributeError:
# No associated object to proxy to!
# just pass an try to retrieve the attribute from `self`
pass
else: # no AttributeError: there is a proxied object
associated_attr = getattr(proxied, attrname, _SENTINEL)
if associated_attr is not _SENTINEL:
# if object is a callable: it is a method. A mehtod in the derived class should
# be called if it exists, and just otherwise in the proxied object:
if callable(associated_attr):
try:
own_method = bound_getattr(attrname)
except AttributeError:
pass
else:
return "own", own_method
return "proxy", associated_attr
# if there is no proxied object, or if the proxied does not have the desired attribute,
# return the regular instance attribute:
return "own", bound_getattr(attrname)
def __getattribute__(self, attrname):
bound_getattr = super().__getattribute__
whose, attr = bound_getattr("_inner_get")(attrname)
return attr
def __setattr__(self, attrname, value):
bound_getattr = super().__getattribute__
try:
whose, attr = bound_getattr("_inner_get")(attrname)
except AttributeError:
whose = "own"
if whose != "own":
proxied = bound_getattr("proxied")
return setattr(proxied, attrname, value)
super().__setattr__(attrname, value)
class A:
def __init__(self, c: int):
self.c = c
class B(ProxyAttrMixins, A):
def __init__(self, a: A):
self.proxied = a
super().__init__() # this ensure B can still have colaborative inheritance.
# The Mixin's __init__ prevents A __init__ from being run and
# report on the missing `c` argument
And this code allows this kind of scenario:
In [18]: b = B(a:=A(5))
In [19]: b.c
Out[19]: 5
In [20]: b.c = 10
In [21]: a.c
Out[21]: 10

How to define different addresses for class attributes and instance attributes?

How to define different addresses for class attributes and instance attributes?
This problem has bothered me for a long time, unless I delete the definition of the class attribute, but want to use the class attribute.
I have defined a dict with the same name in the class attribute and instance attribute. How can I make the memory address different? I tried a variety of methods to delete the content of the class attribute. Is there any other method?
My demo code is as follows:
class MyClass:
bar: dict = {}
def __init__(self):
bar: dict = {}
print(id(MyClass.bar))
a = MyClass()
print(id(a.bar))
1914627629760
1914627629760
class MyClass:
bar: dict = {}
def __init__(self):
self.bar = {}
print(id(MyClass.bar))
a = MyClass()
print(id(a.bar))
2318292079808
2318295104384
That said, I have no idea why we are doing this, and there is an almost 100% chance this will make whomever (next) maintains this codebase go insane within the next 2 years.
Explanation:
You are not "saving" your variable in your __init__() function.
Try running:
class MyClass:
def __init__(self):
self.a = 1 # setting attribute a to value 1
b = 2 # b is not an attribute, it's just a local variable
m = MyClass()
print(m.a) # this will work
print(m.b) # this will not

Python: access a parent attribute from the child class

In Python, I have the following code that came up as a quiz question:
class Big_Cat:
def __init__(self):
self.x = "dangerous"
class Cat(Big_Cat):
def __init__(self):
self.y = "quiet"
new_cat = Cat()
print(new_cat.x, new_cat.y)
Since the cat class is inheriting from the BigCat class, it should also have access to variable x. Then why is it throwing an error on the print screen line. How else can new_cat get access the variable x from parent?
After inheriting from the super class, you must call the parent's __init__ (constructor). You can get a reference to the parent class by using super().
Here is an example:
class Big_Cat:
def __init__(self):
self.x = "dangerous"
class Cat(Big_Cat):
def __init__(self):
super().__init__()
self.y = "quiet"
new_cat = Cat()
print(new_cat.x, new_cat.y)
Output:
dangerous quiet
You can use super to call parent class' __init__
In [1829]: class Big_Cat:
...: def __init__(self):
...: self.x = "dangerous"
...:
...: class Cat(Big_Cat):
...: def __init__(self):
...: super(Cat, self).__init__()
...: self.y = "quiet"
...:
...: new_cat = Cat()
In [1830]: new_cat.x
Out[1830]: 'dangerous'
In Python there is a different approach than in true OOP languages as C++ or Java.
There is no such thing as declaring an attribute in a direct way in a class definition so that this attribute will become automatically the instance's attribute:
class A:
an_attribute = 0
The an_attribute is an attribute of the class A, but not an attribute of instances of this class:
a = A() # an instance of the class A
print(a.an_attribute) # 0 - so IS the an_attribute an instance's attribute?
It seems that an_attribute is the instance's attribute, but ...
A.an_attribute = 100 # changing the value of a CLASS attribute
print(a.an_attribute) # 100; so it is NOT the independent OBJECT 's attribute
So how to create an object's attribute? Very easy:
a.an_attribute = 200 # creating an OBJECT's attribute
print(a.an_attribute) # 200 — the OBJECT's attribute, independent of a CLASS' one
print(A.an_attribute) # 100 — the CLASS attribute
From this moment the object a has its own attribute, different from the class attribute of the same name.
It means that different instances of the same class may have not only different values of the same attributes, but even totally different attributes:
b = A()
b.different_attribute = 500
Very weird situation:
the object a has the attribute an_attribute, but the object b of the same class not,
the object b has the attribute different_attribute , but the object a not.
Is there a way to prescribe / initialize instances' attributes in a class definition?
Luckily, there is a special method __init__(), which runs automatically when you create an instance of a class, and which automatically receives just created object as its first parameter (commonly named as this).
So you may assign to just created object an attribute by using this automatically filled parameter:
class A:
def __init__(self):
self.an_attribute = 20
self.different_attribute = 50
Now all new instances of the class A will have their own, object's attributes an_attribute and different_attribute (initialized with values 20 and 50, respectively, which is not important here).
So, instance variables are not automatically inherited by a subclass. Other people already explained, how to go around it — not surprisingly in the __init__() method of a subclass with the help of the super() built-in function.
You need to call the constructor of the parent class inside the constructor of the child class in order for the child class to access the methods and attributes of the parent class. You can do so with the help of super() method.
class Big_Cat:
def __init__(self):
self.x = "dangerous"
class Cat(Big_Cat):
def __init__(self):
super().__init__()
self.y = "quiet"
new_cat = Cat()
print(new_cat.x, new_cat.y)

Is it possible to refer to the owner class that an object belongs to as an attribute?

I am not quite sure this is possible (or something similar) in python. I want to access a method (or another object) of a class from an object that is an attribute of such class.
Consider the following code:
class A():
def __init__(self):
self.b = B()
self.c = C()
def print_owner(self):
print('owner')
class B():
def __init__(self):
pass
def call_owner(self):
self.owner().print_owner()
so that b as an object attribute of class A, can refer to a method or attribute of A?
Or similarly, is it possible that b can access c?
It's possible. You can pass a reference to A to B constructor:
...
self.b = B(self)
...
class B:
def __init__(self, a):
self.a = a
So, B.a stores the reference to its owner A.
There can be many references to object B(), not only the one in instance of class A. So it's not possible as it is in your code. (Well you could try a hack, like finding all instances of class A in memory and find the one whose attribute b points to your B instance, but that's a really bad idea).
You should explicitly store in instance of B a reference to the owner.
You have a couple of options here. The better one is probably #Sianur suggests. It's simple, effective, and explicit. Give that answer an upvote.
Another option is to have the owner force itself on its minions. B can do something like
def call_owner(self):
if hasattr(self, 'owner'):
self.owner().print_owner()
else:
print('I am free!')
Meanwhile, A would set the owner attribute to itself:
def __init__(self):
self.b = B()
self.c = C()
self.b.owner = self.c.owner = self
In any case, if you want an object to have access to another object, store the reference into an accessible place. There's no magic here.

What is the difference between declaring data attributes inside or outside __init__ [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Python: Difference between class and instance attributes
I'm trying to get my head around OOP in Python and I'm a bit confused when it comes to declare variables within a class. Should I declare them inside of the __init__ procedure or outside it? What's the difference?
The following code works just fine:
# Declaring variables within __init__
class MyClass:
def __init__(self):
country = ""
city = ""
def information(self):
print "Hi! I'm from %s, (%s)"%(self.city,self.country)
me = MyClass()
me.country = "Spain"
me.city = "Barcelona"
me.information()
But declaring the variables outside of the __init__ procedure also works:
# Declaring variables outside of __init__
class MyClass:
country = ""
city = ""
def information(self):
print "Hi! I'm from %s, (%s)"%(self.city,self.country)
me = MyClass()
me.country = "Spain"
me.city = "Barcelona"
me.information()
In your first example you are defining instance attributes. In the second, class attributes.
Class attributes are shared between all instances of that class, where as instance attributes are "owned" by that particular instance.
Difference by example
To understand the differences let's use an example.
We'll define a class with instance attributes:
class MyClassOne:
def __init__(self):
self.country = "Spain"
self.city = "Barcelona"
self.things = []
And one with class attributes:
class MyClassTwo:
country = "Spain"
city = "Barcelona"
things = []
And a function that prints out information about one of these objects:
def information(obj):
print "I'm from {0}, ({1}). I own: {2}".format(
obj.city, obj.country, ','.join(obj.things))
Let's create 2 MyClassOne objects and change one to be Milan, and give Milan "something":
foo1 = MyClassOne()
bar1 = MyClassOne()
foo1.city = "Milan"
foo1.country = "Italy"
foo1.things.append("Something")
When we call information() on the foo1 and bar1 we get the values you'd expect:
>>> information(foo1)
I'm from Milan, (Italy). I own: Something
>>> information(bar1)
I'm from Barcelona, (Spain). I own:
However, if we were to do exactly the same thing, but using instances of MyClassTwo you'll see that the class attributes are shared between instances.
foo2 = MyClassTwo()
bar2 = MyClassTwo()
foo2.city = "Milan"
foo2.country = "Italy"
foo2.things.append("Something")
And then call information()...
>>> information(foo2)
I'm from Milan, (Italy). I own: Something
>>> information(bar2)
I'm from Barcelona, (Spain). I own: Something
So as you can see - things is being shared between the instances. things is a reference to a list that each instance has access to. So if you append to things from any instance that same list will be seen by all other instances.
The reason you don't see this behaviour in the string variables is because you are actually assigning a new variable to an instance. In this case that reference is "owned" by the instance and not shared at the class level. To illustrate let's assign a new list to things for bar2:
bar2.things = []
This results in:
>>> information(foo2)
I'm from Milan, (Italy). I own: Something
>>> information(bar2)
I'm from Barcelona, (Spain). I own:
You're two versions of the code are very different. In python, you have 2 distinct entities: classes and class instances. An instance is what is created when you do:
new_instance = my_class()
You can bind attributes to an instance within __init__ via self (self is the new instance).
class MyClass(object):
def __init__(self):
self.country = "" #every instance will have a `country` attribute initialized to ""
There's nothing terribly special about self and __init__. self is the customary name that is used to represent the instance that gets passed to every method (by default).
a.method() #-> Inside the class where `method` is defined, `a` gets passed in as `self`
The only thing special here is that __init__ gets called when the class is constructed:
a = MyClass() #implicitly calls `__init__`
You can also bind attributes to the class (putting it outside __init__):
class MyClass(object):
country = "" #This attribute is a class attribute.
At any point, you can bind a new attribute to an instance simply by:
my_instance = MyClass()
my_instance.attribute = something
Or a new attribute to a class via:
MyClass.attribute = something
Now it gets interesting. If an instance doesn't have a requested attribute, then python looks at the class for the attribute and returns it (if it is there). So, class attributes are a way for all instances of a class to share a piece of data.
Consider:
def MyClass(object):
cls_attr = []
def __init__(self):
self.inst_attr = []
a = MyClass()
a.inst_attr.append('a added this')
a.cls_attr.append('a added this to class')
b = MyClass()
print (b.inst_attr) # [] <- empty list, changes to `a` don't affect this.
print (b.cls_attr) # ['a added this to class'] <- Stuff added by `a`!
print (a.inst_attr) #['a added this']
When you define a variable in class scope (outside any method), it becomes a class attribute. When you define a value in method scope, it becomes a method local variable. If you assign a value to an attribute of self (or any other label referencing an object), it becomes (or modifies) an instance attribute.

Categories