可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
In Python, I would like to construct an instance of the Child's class directly from an instance of the Parent class. For example:
A = Parent(x, y, z)
B = Child(A)
This is a hack that I thought might work:
class Parent(object):
def __init__(self, x, y, z):
print "INITILIZING PARENT"
self.x = x
self.y = y
self.z = z
class Child(Parent):
def __new__(cls, *args, **kwds):
print "NEW'ING CHILD"
if len(args) == 1 and str(type(args[0])) == "<class '__main__.Parent'>":
new_args = []
new_args.extend([args[0].x, args[0].y, args[0].z])
print "HIJACKING"
return Child(*new_args)
print "RETURNING FROM NEW IN CHILD"
return object.__new__(cls, *args, **kwds)
But when I run
B = Child(A)
I get:
NEW'ING CHILD
HIJACKING
NEW'ING CHILD
RETURNING FROM NEW IN CHILD
INITILIZING PARENT
Traceback (most recent call last):
File "classes.py", line 52, in <module>
B = Child(A)
TypeError: __init__() takes exactly 4 arguments (2 given)
It seems the hack works just as I expected but the compiler throws a TypeError at the end. I was wondering if I could overload TypeError to make it ignore the B = Child(A) idiom but I wasn't sure how to do that. In any case, would you please give me your solutions for inheriting from instances?
Thanks!
回答1:
Once __new__
in class Child
returns an instance of Child
, Child.__init__
will be called (with the same arguments __new__
was given) on that instance -- and apparently it just inherits Parent.__init__
, which does not take well to being called with just one arg (the other Parent
, A
).
If there is no other way a Child
can be made, you can define a Child.__init__
that accepts either one arg (which it ignores) or three (in which case it calls Parent.__init__
). But it's simpler to forego __new__
and have all the logic in Child.__init__
, just calling the Parent.__init__
appropriately!
To make this concrete with a code example:
class Parent(object):
def __init__(self, x, y, z):
print "INITIALIZING PARENT"
self.x = x
self.y = y
self.z = z
def __str__(self):
return "%s(%r, %r, %r)" % (self.__class__.__name__,
self.x, self.y, self.z)
class Child(Parent):
_sentinel = object()
def __init__(self, x, y=_sentinel, z=_sentinel):
print "INITIALIZING CHILD"
if y is self._sentinel and z is self._sentinel:
print "HIJACKING"
z = x.z; y = x.y; x = x.x
Parent.__init__(self, x, y, z)
print "CHILD IS DONE!"
p0 = Parent(1, 2, 3)
print p0
c1 = Child(p0)
print c1
c2 = Child(4, 5, 6)
print c2
回答2:
OK, so I didn't realize that you where happy with a static copy of the arguments until I was already halfway done with my solution. But I decided not to waste it, so here it is anyway. The difference from the other solutions is that it will actually get the attributes from the parent even if they updated.
_marker = object()
class Parent(object):
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
class Child(Parent):
_inherited = ['x', 'y', 'z']
def __init__(self, parent):
self._parent = parent
self.a = "not got from dad"
def __getattr__(self, name, default=_marker):
if name in self._inherited:
# Get it from papa:
try:
return getattr(self._parent, name)
except AttributeError:
if default is _marker:
raise
return default
if name not in self.__dict__:
raise AttributeError(name)
return self.__dict__[name]
Now if we do this:
>>> A = Parent('gotten', 'from', 'dad')
>>> B = Child(A)
>>> print "a, b and c is", B.x, B.y, B.z
a, b and c is gotten from dad
>>> print "But x is", B.a
But x is not got from dad
>>> A.x = "updated!"
>>> print "And the child also gets", B.x
And the child also gets updated!
>>> print B.doesnotexist
Traceback (most recent call last):
File "acq.py", line 44, in <module>
print B.doesnotexist
File "acq.py", line 32, in __getattr__
raise AttributeError(name)
AttributeError: doesnotexist
For a more generic version of this, look at the http://pypi.python.org/pypi/Acquisition package. It is in fact, in some cases a bloody need solution.
回答3:
You don't define a constructor (init) for Child, so the Parent constructor is called, expecting 4 arguments while only 2 are passed in (from new). Here's one way to accomplish what you want:
class Child(Parent):
def __init__(self, *args, **kwargs):
if len(args) == 1 and isinstance(args[0], Parent):
Parent.__init__(self, args[0].x, args[0].y, args[0].z)
else:
# do something else
回答4:
I know this is an extremely old thread, but I recently ran into the same challenge as Alexandra, and this was the most relavent topic I could find. I had a parent class with many, many attributes, and I wanted to essentially 'patch' an instance of it, by keeping all of its methods and attributes, adding a few, and modifying/overwriting others. A simple subclass wouldn't work because the attributes would be filled in by the user during runtime, and I couldn't just inherit the default values from the parent class. After a lot of tinkering I found a very clean (though rather hacky) way to do it by using __new__
. Here's an example:
class Parent(object):
def __init__(self):
# whatever you want here
self.x = 42
self.y = 5
def f(self):
print "Parent class, x,y =", self.x, self.y
class Child(Parent):
def __new__(cls, parentInst):
parentInst.__class__ = Child
return parentInst
def __init__(self, parentInst):
# You don't call the Parent's init method here
self.y = 10
def f(self):
print "Child class, x,y =", self.x, self.y
c = Parent()
c.f() # Parent class, x,y = 42 5
c.x = 13
c.f() # Parent class, x,y = 13 5
c = Child(c)
c.f() # Child class, x,y = 13 10
The only special part is changing the __class__
attribute of the Parent class in the Child's constructor. Because of this, the Child's __init__
method will be called as usual, and to my knowledge the Child class should function exactly as any other inherited class.
回答5:
I find this (encapsulation) to be the cleanest way:
class Child(object):
def __init__(self):
self.obj = Parent()
def __getattr__(self, attr):
return getattr(self.obj, attr)
This way you can use all Parent's method and your own without running into the problems of inheritance.
回答6:
Thanks, guys, that was quick! I first read Alex's comment and I rewrote the Child's __init__
as
def __init__(self, *args, **kwds):
if len(args) == 1 and str(type(args[0])) == "<class '__main__.Parent'>":
new_args = [args[0].x, args[0].y, args[0].z]
super(Child, self).__init__(*new_args, **kwds)
else:
super(Child, self).__init__(*args, **kwds)
which is very similar to what abhinavg suggested (as I just found out). And it works. Only his and ars' line
if len(args) == 1 and isinstance(args[0], Parent):
is cleaner than mine.
Thanks again!!
回答7:
I hope not to be away from the subject, because i'm quite specific but it's the only related question i found.
If you want inherit attribute that are from a parent object through composition, and without copying any value you can do this:
class Agro:
def __init__(self, parent, prop1=None, prop2=None):
self.parent = parent
self._prop1 = prop1
self._prop2 = prop2
@property
def prop1(self):
try:
if self._prop1 is None:
return self.parent.prop1
else:
return self._prop1
except AttributeError as e:
return None
@property
def prop2(self):
try:
if self._prop2 is None:
return self.parent.prop2
else:
return self._prop2
except AttributeError as e:
return None
Then you instanciate:
ag = Agro(None, prop1="nada") # prop1: nada, prop2: None
ag2 = Agro(ag, prop1="nada2") # prop1: nada2, prop2: None
ag3 = Agro(ag2, prop2="youhou") # prop1: nada2, prop2: youhou
ag4 = Agro(ag3) # prop1: nada2, prop2: youhou
What's interesting is when you look at what is actually saved in the instances with __dict__
attributes are only saved once. You can therefore modify dynamically the attributes value:
ag3._prop2 = "yaya"
print(ag4.prop2) # yaya