Class that takes another class as argument, copies behavior

北城以北 提交于 2019-12-13 07:26:45

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!