问题
I'd like to create a class in Python that takes a single argument in the constructor, another Python class. The instance of the Copy class should have all the attributes and methods of the original class, without knowing what they should be beforehand. Here's some code that almost works:
import copy
class A():
l = 'a'
class Copy():
def __init__(self, original_class):
self = copy.deepcopy(original_class)
print(self.l)
c = Copy(A)
print(c.l)
The print statement in the constructor prints 'a', but the final one gives the error AttributeError: Copy instance has no attribute 'l'
.
回答1:
This is an interesting question to point out a pretty cool feature of Python's pass-by-value semantics, as it is intimately tied to why your original code doesn't work correctly and why @martineau's solution works well.
Why Your Code As Written Doesn't Work
Python doesn't support pure pass-by-reference or pass-by-value semantics - instead, it does the following:
# Assume x is an object
def f(x):
# doing the following modifies `x` globally
x.attribute = 5
# but doing an assignment only modifies x locally!
x = 10
print(x)
To see this in action,
# example
class Example(object):
def __init__(self):
pass
x = Example()
print(x)
>>> <__main__.Example instance at 0x020DC4E0>
f(e) # will print the value of x inside `f` after assignment
>>> 10
print(x) # This is unchanged
>>> <__main__.Example instance at 0x020DC4E0>
e.attribute # But somehow this exists!
>>> 5
What happens? Assignment creates a local x
which is then assigned a value. Once this happens, the original parameter that was passed in as an argument is inaccessible.
However, so long as the name x
is bound to the object that's passed in, you can modify attributes and it will be reflected in the object you passed in. The minute you 'give away' the name x
to something else, however, that name is no longer bound to the original parameter you passed in.
Why is this relevant here?
If you pay careful attention to the signature for __init__
, you'll notice it takes self
as a parameter. What is self
?
Ordinarily, self
refers to the object instance. So the name self
is bound to the object instance.
This is where the fun starts. By assigning to self
in your code, this property no longer holds true!
def __init__(self, original_class):
# The name `self` is no longer bound to the object instance,
# but is now a local variable!
self = copy.deepcopy(original_class)
print(self.l) # this is why this works!
The minute you leave __init__
, this new local variable self
goes out of scope. That is why doing c.l
yields an error outside of the constructor - you never actually assigned to the object in the first place!
Why @martineau's Solution Works
@martineau simply took advantage of this behaviour to note that the __dict__
attribute exists on the self
object, and assigns to it:
class Copy():
def __init__(self, original_class):
# modifying attributes modifies the object self refers to!
self.__dict__ = copy.deepcopy(original_class.__dict__)
print(self.l)
This now works because the __dict__
attribute is what Python calls when Python needs to lookup a method signature or attribute when it sees the namespace operator .
, and also because self
has not been changed but still refers to the object instance. By assigning to self.__dict__
, you obtain an almost exact copy of the original class ('almost exact' because even deepcopy
has limits).
The moral of the story should be clear: never assign anything to self
directly. Instead, only assign to attributes of self
if you ever need to. Python's metaprogramming permits a wide degree of flexibility in this regard, and you should always consult the documentation in this regard.
回答2:
I am unsure why you would wish to do this, but you probably have your reasons.
You can leverage off normal inheritance:
eg:
class A(object):
l = 'a'
class C(object):
l = 'c'
def createClass(cls):
class B(cls):
pass
return B
cls = createClass(C) # or A or whatever
print cls.l
=> result: 'c'
回答3:
You need to copy the __dict__
:
import copy
class A():
l = 'a'
class Copy():
def __init__(self, original_class):
self.__dict__ = copy.deepcopy(original_class.__dict__)
print(self.l)
c = Copy(A) # -> a
print(c.l) # -> a
来源:https://stackoverflow.com/questions/38601052/class-that-takes-another-class-as-argument-copies-behavior