1

I have a class like so:

class Foo:
    def spam(self) -> None:
        pass

I want to copy the class to new name CopyFoo, and notably have the copy's underlying methods be totally different objects.

How can I copy Foo, and have the unbound method spam be a different unbound method?

Note: I need to do this without instantiating Foo, having bound methods be different objects doesn't help me.

Research

From How to copy a python class?, I have tried copy.deepcopy, pairing pickle.loads + pickle.dumps, and using type, to no avail.

>>> Foo.spam
<function Foo.spam at 0x111a6a0d0>
>>> deepcopy(Foo).spam
<function Foo.spam at 0x111a6a0d0>
>>> pickle.loads(pickle.dumps(Foo)).spam
<function Foo.spam at 0x111a6a0d0>
>>> type('CopyFoo', Foo.__bases__, dict(Foo.__dict__)).spam
<function Foo.spam at 0x111a6a0d0>
Intrastellar Explorer
  • 3,005
  • 9
  • 52
  • 119
  • The problem is that you're copying the class itself as opposed to an instance of the class. If you create a Foo object and you call copy.deepcopy(obj), it will properly create a deepcopy. – Matthew Kligerman Jul 13 '21 at 02:06
  • That said, I did find a previous answer to your question, as it is an interesting one: https://stackoverflow.com/questions/9541025/how-to-copy-a-python-class – Matthew Kligerman Jul 13 '21 at 02:07
  • Thank you @MatthewKligerman, yes I had already tried the solutions in that question. I updated my question to add more details, so far no solution yet – Intrastellar Explorer Jul 13 '21 at 02:15
  • 1
    You probably have to iterate through the class attributes, detect methods and create copies of them. As they are ordinary functions you can use the answer of Aaron Hall on https://stackoverflow.com/questions/6527633/how-can-i-make-a-deepcopy-of-a-function-in-python – Michael Butscher Jul 13 '21 at 02:15
  • 1
    why not inherit Foo.`class CopyFoo(Foo):` – nay Jul 13 '21 at 02:24
  • 1
    @nay Then `spam` method will be inherited too. – Selcuk Jul 13 '21 at 02:29
  • This sounds like an X-Y problem. Why would it ever matter? – juanpa.arrivillaga Jul 13 '21 at 04:07

1 Answers1

1

This is totally a hack and should not be used in production but posting it anyway for future reference:

import inspect

class Foo:
    def spam(self) -> None:
        pass


foo_source = inspect.getsource(Foo)
copy_foo_source = foo_source.replace("Foo", "CopyFoo")

exec(copy_foo_source)

print(f"{id(Foo)=}")
print(f"{id(CopyFoo)=}")
print(f"{id(Foo.spam)=}")
print(f"{id(CopyFoo.spam)=}")

the output will be similar to this:

id(Foo)=140343301414016
id(CopyFoo)=140343299415168
id(Foo.spam)=4420954432
id(CopyFoo.spam)=4421268528
Selcuk
  • 57,004
  • 12
  • 102
  • 110