88

deepcopy from copy does not copy a class:

>>> class A(object):
>>>     ARG = 1

>>> B = deepcopy(A)

>>> A().ARG
>>> 1

>>> B().ARG
>>> 1

>>> A.ARG = 2

>>> B().ARG
>>> 2

Is it only way?

B(A):
    pass
martineau
  • 119,623
  • 25
  • 170
  • 301
I159
  • 29,741
  • 31
  • 97
  • 132

9 Answers9

80

In general, inheritance is the right way to go, as the other posters have already pointed out.

However, if you really want to recreate the same type with a different name and without inheritance then you can do it like this:

class B(object):
    x = 3

CopyOfB = type('CopyOfB', B.__bases__, dict(B.__dict__))

b = B()
cob = CopyOfB()

print b.x   # Prints '3'
print cob.x # Prints '3'

b.x = 2
cob.x = 4

print b.x   # Prints '2'
print cob.x # Prints '4'

You have to be careful with mutable attribute values:

class C(object):
    x = []

CopyOfC = type('CopyOfC', C.__bases__, dict(C.__dict__))

c = C()
coc = CopyOfC()

c.x.append(1)
coc.x.append(2)

print c.x   # Prints '[1, 2]' (!)
print coc.x # Prints '[1, 2]' (!)
Florian Brucker
  • 9,621
  • 3
  • 48
  • 81
  • Do the dict of copied class linked to the dict of class which was created first? – I159 Nov 14 '12 at 13:50
  • @I159: That depends on the values in `__dict__`. Non-mutable attributes are fine, but mutable attribute values (like lists) or background mechanisms of new style classes (e.g. descriptors) will probably bite you. – Florian Brucker Nov 14 '12 at 14:15
  • 1
    This works good only if to the attribute `x` is assigned an immutable type and not a mutable type, because `dict(C.__dict__)` does only a shallow copy, not a deepcopy. – hynekcer Nov 22 '12 at 21:21
  • 1
    @hynekcer: Correct. You can use `deepcopy` to copy `C.__dict__`, but you'll eventually run into troubles regarding data and method descriptors for new style classes, IIRC. – Florian Brucker Nov 23 '12 at 06:26
  • Beware of globals, this is not enough in case if class functions calls to `globals()`. Inner `globals()` calls would be pointed to the module from which the function has been copied. To workaround this you have additionally copy all functions: `type(foo)(foo.__code__, globals())`, where `globals()` can point different globals on a moment of copy. – Andry Sep 18 '19 at 23:13
  • This has more pitfalls than one can think of. Another one is that the constructors and initializers of the original class will often refuse to build an instance of another class or pull in the original one in the process. – Bachsau May 26 '20 at 23:54
  • I'm using Python 3.8 and this is failing due to super "obj must be an instance or subtype of type" – Gordon Wrigley Jul 27 '20 at 07:37
  • If you want an exact copy, it's good to apply `functools.update_wrapper` to match special attributes such as `__module__`: `functools.update_wrapper(wrapped=C, wrapper=CopyOfC`, or `functools.wraps(C)(type(C.__name__, C.__bases__, C.__dict__)`. – Anakhand Jul 08 '21 at 10:00
  • Though you don't get a copy that fully behaves like the original. What I observe is that instances of the copy behave differently from `B`. If you type `dir(cob)`, you get the error ` TypeError: descriptor '__dict__' for 'B' objects doesn't apply to a 'CopyOfB' object`. – pthibault Oct 03 '22 at 19:09
28

The right way to "copy" a class, is, as you surmise, inheritance:

class B(A):
    pass
kindall
  • 178,883
  • 35
  • 278
  • 309
David Robinson
  • 77,383
  • 16
  • 167
  • 187
  • 17
    You can also do it using `type()`, e.g. `B = type("B", (A,), {})` – kindall Mar 03 '12 at 00:04
  • 15
    Inheritance does not do what is required (indempendence of ARG values) unless a value is assigned also to B.ARG. – hynekcer Nov 22 '12 at 21:17
  • @hynecker: could you explain what you mean by that? I think this does answer the OP's question – David Robinson Nov 22 '12 at 21:28
  • 11
    @DavidRobinson With this answer, setting A.ARG will also change B.ARG. This is because ARG doesn't actually appear in B.__dict__, so it gets resolved to A's value at time of access rather than when the copy happens. – Quantum7 Mar 08 '17 at 08:24
26

You could use a factory function:

def get_A():
    class A(object):
        ARG = 1
    return A

A = get_A()
B = get_A()
James
  • 3,191
  • 1
  • 23
  • 39
  • 1
    I think this is the most elegant solution and in particular I think this is the only way to do it while preserving type information for mypy. – royce3 Oct 04 '19 at 14:47
  • 1
    `B.mro() == [__main__.get..A, object]`, which not everybody wants – rindeal Nov 05 '21 at 12:11
8

As Florian Brucker pointed out, there is a problem with mutable class attributes. You also can't just deepcopy(cls.__dict__) on new style objects. I've done the following to solve this problem for what I'm doing. I'm certain someone determined enough could break this. But, it will work in more cases.

from copy import deepcopy
from typing import TypeVar

Cls = TypeVar('Cls')


# This type hint is a dirty lie to make autocomplete and static
# analyzers give more useful results. Crazy the stuff you can do
# with python...
def copy_class(cls: Cls) -> Cls:
    copy_cls = type(f'{cls.__name__}Copy', cls.__bases__, dict(cls.__dict__))
    for name, attr in cls.__dict__.items():
        try:
            hash(attr)
        except TypeError:
            # Assume lack of __hash__ implies mutability. This is NOT
            # a bullet proof assumption but good in many cases.
            setattr(copy_cls, name, deepcopy(attr))
    return copy_cls


def test_copy_class():
    class A(object):
        mutable_class_var = []

    ACopy = copy_class(A)

    a = A()
    acopy = ACopy()

    acopy.mutable_class_var.append(1)
    assert a.mutable_class_var == []
    assert A.mutable_class_var == []
    assert ACopy.mutable_class_var == [1]
    assert acopy.mutable_class_var == [1]
willtalmadge
  • 406
  • 3
  • 7
  • Not that a custom `__setattr__` on a metaclass will break this. Probably better to populate the new class's dict directly – DylanYoung Jun 23 '20 at 17:01
2

I think you misunderstand the meaning of static variable here. Every where you declare a variable outside a method and not in the shape of self.some_thing, the variable will be considered as class's static variable ( like your ARG variable here). Thus, every object ( instance ) of the Class that changes a static variable will cause change of all other objects in the same Class. The deepcopy really does the job here.

vutran
  • 2,145
  • 21
  • 34
2

To copy a class with __slots__ attribute, this function will help :-)

def copy_class(c,name=None):
    if not name: name = 'CopyOf'+c.__name__
    if hasattr(c,'__slots__'):
        slots = c.__slots__ if type(c.__slots__) != str else (c.__slots__,)
        dict_ = dict()
        sloted_members = dict()
        for k,v in c.__dict__.items():
            if k not in slots:
                dict_[k] = v
            elif type(v) != types.MemberDescriptorType:
                sloted_members[k] = v
        CopyOfc = type(name, c.__bases__, dict_)
        for k,v in sloted_members.items():
            setattr(CopyOfc,k,v)
        return CopyOfc
    else:
        dict_ = dict(c.__dict__)
        return type(name, c.__bases__, dict_)
nkpro
  • 21
  • 1
  • 1
0

A simple approach is to put the class in a module and reload it every time you want a new copy. I think this deals with the mutables, because the reload recreates everything.

M Juckes
  • 299
  • 1
  • 6
0

This is a solution for copying at all levels:

#for windows use dill teh same way

import pickle
copy = lambda obj: pickle.loads(pickle.dumps(obj))

Problem was:

class A: a = 1
x = A()
y = x
x.a = 5
print(y.a) #return's 5

With copy:

class A:a = 1
x = A()
y = copy(x)
x.a = 5
print(y.a) #return 1

You wan copy anything you want, not only class instances or Classes

Henry
  • 577
  • 4
  • 9
  • 1
    This does not do class deepcopy, pickle just re-creates the class (type) object based on its `__module__` and `__name__`. – eudoxos Aug 08 '21 at 05:14
-5

If you want to create just another instance of class then just make it:

 >>> class A(object):
...    ARG=1
... 
 >>> a = A()
 >>> A().ARG
 1
 >>> b = A()
 >>> b.ARG
 1
 >>> a.ARG=2
 >>> b.ARG
 1
Pavel Shvedov
  • 1,284
  • 11
  • 8