You could use a factory function with functools.cache:
import functools
@functools.cache
def make_myclass(*args, **kwargs):
return MyClass(*args, **kwargs)
EDIT: Apparently you can decorate your class directly to get the same effect:
@functools.cache
class Foo:
def __init__(self, a):
print("Creating new instance")
self.a = a
>>> Foo(1)
Creating new instance
<__main__.Foo object at 0x0000021D7D61FFA0>
>>> Foo(1)
<__main__.Foo object at 0x0000021D7D61FFA0>
>>> Foo(2)
Creating new instance
<__main__.Foo object at 0x0000021D7D61F250>
Note the same memory address both times Foo(1)
is called.
Edit 2: After some playing around, you can get your default-respecting instance cache behavior if you override __new__
and do all of your caching and instantiation there:
class Foo:
_cached = {}
def __new__(cls, a, b=3):
attrs = a, b
if attrs in cls._cached:
return cls._cached[attrs]
print(f"Creating new instance Foo({a}, {b})")
new_foo = super().__new__(cls)
new_foo.a = a
new_foo.b = b
cls._cached[attrs] = new_foo
return new_foo
a = Foo(1)
b = Foo(1, 3)
c = Foo(b=3, a=1)
d = Foo(4)
print(a is b)
print(b is c)
print(c is d)
output:
Creating new instance Foo(1, 3)
Creating new instance Foo(4, 3)
True
True
False
The __init__
will still be called after __new__
, so you will want to do your expensive initialization (or all of it) in __new__
after the cache check.