Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class name as idempotent typecast function

I'm writing a custom type Foo and I'd like to achieve the following: when writing

foo = Foo(bar)

then if bar is already an instance of Foo, then Foo(foo) returns that object unmodified, so foo and bar refer to the same object. Otherwise a new object of type Foo should be created, using information from bar in whatever way Foo.__init__ deems appropriate.

How can I do this?

I would assume that Foo.__new__ is the key to this. It should be fairly easy to have Foo.__new__ return the existing object if the instanceof check succeeds, and to call super().__new__ otherwise. But the documentation for __new__ writes this:

If __new__() returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to __new__().

In this case I would be returning an instance of the requested class, albeit not a new one. Can I somehow prevent the call to __init__? Or do I have to add a check inside __init__ to detect whether it is being invoked for a newly created instance or for an already existing one? The latter sounds like code duplication which should be avoidable.

like image 215
MvG Avatar asked Feb 06 '23 05:02

MvG


1 Answers

IMHO, you should directly use __new__ and __init__. The test in init to see whether you should init a new object or already have an existing one is so simple that there is no code duplication and the added complexity is IMHO acceptable

class Foo:
    def __new__(cls, obj):
        if isinstance(obj, cls):
            print("New: same object")
            return obj
        else:
            print("New: create object")
            return super(Foo, cls).__new__(cls)
    def __init__(self, obj):
        if self is obj:
            print("init: same object")
        else:
            print("init: brand new object from ", obj)
            # do the actual initialization

It gives as expected:

>>> foo = Foo("x")
New: create object
init: brand new object from  x
>>> bar = Foo(foo)
New: same object
init: same object
>>> bar is foo
True
like image 52
Serge Ballesta Avatar answered Feb 08 '23 15:02

Serge Ballesta