I have looked at many questions posted here to find an answer to my problem, but I wasn't successful. The problem might be, that I just don't know for what keywords I should look. So my problem is the following:
I've got a program, that has a multi-level inheritance and I am trying to figure out how the best way would be to change the class of an object to a subclass. Let's say I have the following code:
class A(object):
def __init(self, filename, ..)
super(A, self).__init__()
...some assignments here
class B(A):
def __init(self, filename, ..)
super(B, self).__init__()
...some assignments here
class C(A):
def __init(self, filename, ..)
super(C, self).__init__()
...some assignments here
and so on...
I always want to start initialising an object of class A. Depending on the type of the file that is used, the assignments may differ and depending on those assignments I can determine what kind of file it is. So now I want to change the class of the object to whatever class is suitable..
I know I could pass the A object to B or C and use copy or deepcopy, but in A i am assigning an object of which the reference should not change and some others where it should change. Also I would need to delete that object of A, after initialising B or C.
class B(A):
def __init__(self, filename, objA = None):
if objA is not None:
self.__dict__ = copy.deepcopy(objA.__dict__)
del(objA)
else:
super(B, self).__init__(filename)
Also there is another possibility by changing the _class attribute to another class and use some kind of update method of the new class.
I would like to know, which of the two approaches is recommended or is there even a better one. Thanks in advance.
What you want is a factory: a function that opens the file, reads the stuff it needs to read to figure out what kind of file it is, and then initializes and returns an object of the appropriate class.
If you want to keep it a class, you'd want to override __new__() and then return an object of the desired class instead of its own class. (You could also do it using a metaclass and overriding __call__() on that.)
You can change an instance's class after instantiating it as well, by changing its __class__ attribute to point to the desired class. That'll work, but the factory is going to be more familiar to other programmers who will read your code.
This code will explain what you want to do
class B(object):
def x(self):
print 'b'
class A(object):
def x(self):
print 'a'
Now we create two objects
a = a()
b = b()
a.x()
a
b.x()
b
now if you want 'a' to become a B object
a.__class__ = type(b)
or
a.__class__ = B
now the x attribute is from the B class.
a.x()
b
Related
Is it possible to check if an instance of a class has an attribute without (being able to) instantiating the class?
Example:
class A where I lack knowledge about arguments to instantiate:
class A:
def __init__(self, unkown):
self.a = 1
checking for attribute a in class A fails:
hasattr(A,'a')
>> False
checking for attribute a in instance of class A would succeed:
hasattr(A(1),'a')
>> True
Is it possible to check for attribute a without instantiating A? Maybe with something different than hasattr?
In general this is not possible. You can freely set any attribute on any instance of A at any time. Consider
a = A('foo')
setattr(a, input('attr_name: '), 'bar')
A cannot know what attributes may or may not be set on its instances in the future.
You could do some insane things like parse __init__ if you want to limit the question to attributes set in __init__, but most likely we're having an XY-problem here.
As it's not possible, a workaround can be making a a property:
class A:
def __init__(self, unkown):
self._a = 1
#property
def a(self):
return self._a
hasattr(A,'a')
>> True
Just leaving this here in the sense of completeness
I'm trying to get a better understanding of python class system. This question is intended only to satisfy my curiosity.
Is it possible to use somehow a class instance as a parent class for another class. So far what I tried to do is
class A:
pass
a = A()
class B(a):
pass
and it gives following error: TypeError: object() takes no parameters
class Meta(type):
pass
class A:
pass
a = A()
class B(a, metaclass=Meta):
pass
and it gives this error TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
I'm wondering, is it possible to somehow proxy all of the class instance methods to metaclass, so my instance would behave as a class
Is it possible to use somehow a class instance as a parent class for another class
Well, actually that's always the case since classes ARE instances (of their metaclass). But then the class of the instance you want to use as a parent class (yeah, re-read it thrice...) must behave as if it was a genuine metaclass - which can rapidily become impractical.
I'm wondering, is it possible to somehow proxy all of the class instance methods to metaclass, so my instance would behave as a class
There might be a way indeed (overriding __getattr__() or __getattribute__()), but devil is in the details and chances are that by the time you make this rube goldberg contraption "kind-of-work-with-severe-limitations-and-corner-cases", you will wonder if that was really worth the pain.
Note that OTHO both composition/delegation and dynamic (runtime) creation / modification of classes is absurdly easy in Python so I can hardly think of a problem that would be better solved by (ab)using an instance as a parent class.
Every instance has a __class__ attribute that refers to the underlying class structure.
Knowing this, you can actually do the following:
class A(object):
def foo(self):
return 'hello'
a = A()
class B(a.__class__):
pass
b = B()
print(b.foo()) # Prints 'hello'
Alternatively, you can also use type:
class B(type(a)):
pass
b = B()
print(b.foo()) # Prints 'hello'
Your instance needs to be a type in order to be used in inheritance chains. For example:
class A(type):
pass
a = A(str) # you need to pass the type's `type` to the construcor
class B(a):
pass
That being said, there's little, if no practical application for this that I can think of - or rather whatever you achieve this way will be easier and more Pythonic to achieve through normal inheritance and metaclasses.
EDIT:
OK, so it is possible to hack if you have control over the base class (either using metaclasses or __init_subclass__ (python 3.6 or later))
class A:
x = 1
def __init_subclass__(cls, inst, **kwargs):
super().__init_subclass__(**kwargs)
for k, v in vars(inst).items():
if k not in dir(cls): # i.e. don't override but remove if you want to
setattr(cls, k, v)
a = A()
a.y = 2
class B(A, inst=a):
pass
B.x # --> 1
B.y # --> 2
You're sort of on the right tracks with your second example. Yes it is possible.
class Meta(type): pass
class A(metaclass=Meta): pass
class B(A): pass
issubclass(B, A) # --> True
isinstance(B, Meta) # --> True
Thats because A is an instance of Meta (thats what a metaclass means, a class whose instance is also a class). Hence B is a subclass of an instance of Meta.
So yes, you could set for example
class Meta(type):
def __init__(cls, *args, **kwargs):
cls.x = 1
super().__init__(cls, *args, **kwargs)
class A(Meta):
def __init__(self):
self.y = 2
A.x # 1 (acts as an instance of Meta)
a = A()
a.y # 2 (acts as an instance of A)
a.x # AttributeError (does not act as an instance of Meta)
I have a series of Python classes in a file. Some classes reference others.
My code is something like this:
class A():
pass
class B():
c = C()
class C():
pass
Trying to run that, I get NameError: name 'C' is not defined. Fair enough, but is there any way to make it work, or do I have to manually re-order my classes to accommodate? In C++, I can create a class prototype. Does Python have an equivalent?
(I'm actually playing with Django models, but I tried not complicate matters).
Actually, all of the above are great observations about Python, but none of them will solve your problem.
Django needs to introspect stuff.
The right way to do what you want is the following:
class Car(models.Model):
manufacturer = models.ForeignKey('Manufacturer')
# ...
class Manufacturer(models.Model):
# ...
Note the use of the class name as a string rather than the literal class reference. Django offers this alternative to deal with exactly the problem that Python doesn't provide forward declarations.
This question reminds me of the classic support question that you should always ask any customer with an issue: "What are you really trying to do?"
In Python you don't create a prototype per se, but you do need to understand the difference between "class attributes" and instance-level attributes. In the example you've shown above, you are declaring a class attribute on class B, not an instance-level attribute.
This is what you are looking for:
class B():
def __init__(self):
self.c = C()
This would solve your problem as presented (but I think you are really looking for an instance attribute as jholloway7 responded):
class A:
pass
class B:
pass
class C:
pass
B.c = C()
Python doesn't have prototypes or Ruby-style open classes. But if you really need them, you can write a metaclass that overloads new so that it does a lookup in the current namespace to see if the class already exists, and if it does returns the existing type object rather than creating a new one. I did something like this on a ORM I write a while back and it's worked very well.
A decade after the question is asked, I have encountered the same problem. While people suggest that the referencing should be done inside the init method, there are times when you need to access the data as a "class attribute" before the class is actually instantiated. For that reason, I have come up with a simple solution using a descriptor.
class A():
pass
class B():
class D(object):
def __init__(self):
self.c = None
def __get__(self, instance, owner):
if not self.c:
self.c = C()
return self.c
c = D()
class C():
pass
>>> B.c
>>> <__main__.C object at 0x10cc385f8>
All correct answers about class vs instance attributes. However, the reason you have an error is just the order of defining your classes. Of course class C has not yet been defined (as class-level code is executed immediately on import):
class A():
pass
class C():
pass
class B():
c = C()
Will work.
In the following example, is there any way the a attribute in A can be accessed by the B class or C inner class?
class A:
def __init__(self, a):
self.a = a
def C_test(self):
for i in range(4):
c = self.C()
class C:
print(self.a)
class B(A):
def __init__(self):
print(self.a)
How come I get this error?
Traceback (most recent call last):
File "/Users/home/Desktop/Pygame/test.py", line 1, in <module>
class A:
File "/Users/home/Desktop/Pygame/test.py", line 10, in A
class C:
File "/Users/home/Desktop/Pygame/test.py", line 11, in C
print(self.a)
NameError: name 'self' is not defined
self is not a special variable name in Python - it's just the name that is typically given to the first argument of a method, which is bound to the object calling that method.
Your self.a in class C doesn't appear inside a method definition where self is listed as an argument, so self has no meaning there.
For your B example, this general idea does work, just not automatically. Python does Python does have broadly similar rules to Java when it comes to single inheritance. The reason it doesn't work automatically here is because of Python's data model - member variables are attached to each unique instance, rather than being an intrinsic part of the class. They're usually, as in your examples, attached to the instance when __init__ runs. So, for your B to have an a, it would need to be attached by A.__init__. Python will automatically run that for you if you don't write a B.__init__, but if you do have that, you need to call up explicitly. The easiest way is this:
class B(A):
def __init__(self):
super().__init__()
Again, you might recognise this idea of explicitly chaining up using super from other languages - although the spelling is a bit different here. But the only major technical difference between Python and Java in this respect is that Python needs the parent constructor to be called for the instance variables to even exist (in Java, they will still exist, but might not have the right value, or even be initialised).
For your C example, Python is very different from Java here. Python doesn't usually use the term 'inner class', although you can define a class inside another class (note that you haven't in your code sample, though) - but instances of the inner class won't be associated with an instance of the outer class. So, they behave a bit more like Java inner static classes. You could associate them explicitly by doing something like this:
class A:
def __init__(self, a):
self.a = a
self.my_C = A.C(self)
class C:
def __init__(self, A_self):
print(A_self.a)
But this isn't exactly common, and depending on exactly what your problem is, there is almost always a better way to solve it than trying to shoehorn Java idioms into Python code.
If you derive a class from your own base-class, you have to call the constructor of the base-class with super to inherit the base-classes attributes:
class A(object):
def __init__(self, a):
self.a = a
class B(A):
def __init__(self):
super(B, self).__init__(self)
After running this you can do this for example:
a = A(1)
b = B()
b.a = a.a
b has declared no attribute a, but inherits it from class A by calling the constructor of A via super in class B.
Now b.a evaluates 1 cause it is set to the value of a.a.
I have a series of Python classes in a file. Some classes reference others.
My code is something like this:
class A():
pass
class B():
c = C()
class C():
pass
Trying to run that, I get NameError: name 'C' is not defined. Fair enough, but is there any way to make it work, or do I have to manually re-order my classes to accommodate? In C++, I can create a class prototype. Does Python have an equivalent?
(I'm actually playing with Django models, but I tried not complicate matters).
Actually, all of the above are great observations about Python, but none of them will solve your problem.
Django needs to introspect stuff.
The right way to do what you want is the following:
class Car(models.Model):
manufacturer = models.ForeignKey('Manufacturer')
# ...
class Manufacturer(models.Model):
# ...
Note the use of the class name as a string rather than the literal class reference. Django offers this alternative to deal with exactly the problem that Python doesn't provide forward declarations.
This question reminds me of the classic support question that you should always ask any customer with an issue: "What are you really trying to do?"
In Python you don't create a prototype per se, but you do need to understand the difference between "class attributes" and instance-level attributes. In the example you've shown above, you are declaring a class attribute on class B, not an instance-level attribute.
This is what you are looking for:
class B():
def __init__(self):
self.c = C()
This would solve your problem as presented (but I think you are really looking for an instance attribute as jholloway7 responded):
class A:
pass
class B:
pass
class C:
pass
B.c = C()
Python doesn't have prototypes or Ruby-style open classes. But if you really need them, you can write a metaclass that overloads new so that it does a lookup in the current namespace to see if the class already exists, and if it does returns the existing type object rather than creating a new one. I did something like this on a ORM I write a while back and it's worked very well.
A decade after the question is asked, I have encountered the same problem. While people suggest that the referencing should be done inside the init method, there are times when you need to access the data as a "class attribute" before the class is actually instantiated. For that reason, I have come up with a simple solution using a descriptor.
class A():
pass
class B():
class D(object):
def __init__(self):
self.c = None
def __get__(self, instance, owner):
if not self.c:
self.c = C()
return self.c
c = D()
class C():
pass
>>> B.c
>>> <__main__.C object at 0x10cc385f8>
All correct answers about class vs instance attributes. However, the reason you have an error is just the order of defining your classes. Of course class C has not yet been defined (as class-level code is executed immediately on import):
class A():
pass
class C():
pass
class B():
c = C()
Will work.