Jace Browning
Jace Browning

Reputation: 12662

How can I make __init__ take a new value or an existing instance?

I have a pattern that looks similar to the following:

class Foobar(object):  # instances of this class will be referenced by others
    def __init__(self, value):
        self.value = value

class Foo(object):
    def __init__(self, value, foobar)
        self.value = value
        if isinstance(foobar, Foobar):
            self.foobar = foobar
        else:
            self.foobar = Foobar(foobar)

class Bar(object):
    def __init__(self, value, foobar)
        self.value = value
        if isinstance(foobar, Foobar):
            self.foobar = foobar
        else:
            self.foobar = Foobar(foobar)

This allows Foo and Bar to take either a new value (to create a Foobar) or an existing instance of Foobar as their foobar argument.


I would like to get rid of this redundant code:

# ...
        if isinstance(foobar, Foobar):
            self.foobar = foobar
        else:
            self.foobar = Foobar(foobar)

I considered the following, but it doesn't work due to infinite recursion in Foobar.__new__():

class Foobar(object):
    def __new__(cls, value):
        if isinstance(value, cls):
           return value
        else:
           return Foobar(value)
    def __init__(self, value):
        self.value = value

class Foo(object):
    def __init__(self, value, foobar)
        self.value = value
        self.foobar = Foobar(foobar)

class Bar(object):
    def __init__(self, value, foobar)
        self.value = value
        self.foobar = Foobar(foobar)

What is the best way to allow classes to create new instances or use existing instances depending on the values passed to __init__?

Upvotes: 4

Views: 122

Answers (2)

Aya
Aya

Reputation: 41950

Another option would be to factor out the duplicated code with a mixin class...

class Foobar(object):
    def __init__(self, value):
        self.value = value

class FoobarMixin(object):
    def __init__(self, **kwargs):
        foobar = kwargs['foobar']
        if isinstance(foobar, Foobar):
            self.foobar = foobar
        else:
            self.foobar = Foobar(foobar)

class Foo(FoobarMixin):
    def __init__(self, value, **kwargs):
        super(Foo, self).__init__(**kwargs)
        self.value = value
        print self.value, self.foobar

class Bar(FoobarMixin):
    def __init__(self, value, **kwargs):
        super(Bar, self).__init__(**kwargs)
        self.value = value
        print self.value, self.foobar

foo = Foo('foo', foobar='foobar')
bar = Bar('bar', foobar=Foobar('foobar'))

...which prints...

foo <__main__.Foobar object at 0x7fa0fedf6050>
bar <__main__.Foobar object at 0x7fa0fedeaf10>

Upvotes: 1

Sven Marnach
Sven Marnach

Reputation: 601559

You can get rid of the recursion by calling the base class __new__():

class Foobar(object):
    def __new__(cls, value):
        if isinstance(value, cls):
           return value
        else:
           return object.__new__(cls, value)
    def __init__(self, value):
        self.value = value

Note that the first parameter to __new__() is a class, not self.

That said, I'm not convinced that this is a useful pattern. In general, I'd recommend to accept instances in the constructor and leave the object construction to the calling code. While magic that does the Right Thing often seems convenient, it usually causes more problems down the road than it is worth.

Upvotes: 4

Related Questions