Jayanth Koushik
Jayanth Koushik

Reputation: 9874

Python: Multiple ways to initialize a class

I have a class A which can be 'initialized' in two different ways. So, I provide a 'factory-like' interface for it based on the second answer in this post.

class A(object):

    @staticmethod
    def from_method_1(<method_1_parameters>):
        a = A()
        # set parameters of 'a' using <method_1_parameters>
        return a

    @staticmethod
    def from_method_2(<method_2_parameters>):
        a = A()
        # set parameters of 'a' using <method_2_parameters>
        return a

The two methods are different enough that I can't just plug their parameters into the class's __init__. So, class A should be initialized using:

a = A.from_method_1(<method_1_parameters>)

or

a = A.from_method_2(<method_2_parameters>)

However, it is still possible to call the 'default init' for A:

a = A() # just an empty 'A' object

Is there any way to prevent this? I can't just raise NotImplementedError from __init__ because the two 'factory methods' use it too.

Or do I need to use a completely different approach altogether.

Upvotes: 4

Views: 5561

Answers (1)

Aurora Wang
Aurora Wang

Reputation: 1940

Has been a very long time since this question was asked but I think it's interesting enough to be revived.

When I first saw your problem the private constructor concept just popped out my mind. It's a concept important in other OOP languages, but as Python doesn't enforces privacy I didn't really thought about it since Python became my main language.

Therefore, I became curious and I found this "Private Constructor in Python" question. It covers pretty much all about this topic and I think the second answer can be helpful in here.

Basically it uses name mangling to declare a pseudo-private class attribute (there isn't such thing as private variables in Python) and assign the class object to it. Therefore you'll have an as-private-as-Python allows variable to use to check if your initialization was made from an class method or from an outside call. I made the following example based on this mechanism:

class A(object):
    __obj = object()

    def __init__(self, obj=None):
        assert(obj == A.__obj), \
            'A object must be created using A.from_method_1 or A.from_method_2'

    @classmethod
    def from_method_1(cls):
        a = A(cls.__obj)
        print('Created from method 1!')
        return a

    @classmethod
    def from_method_2(cls):
        a = A(cls.__obj)
        print('Created from method 2!')
        return a

Tests:

>>> A()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "t.py", line 6, in __init__
    'A object must be created using A.from_method_1 or A.from_method_2'
AssertionError: A object must be created using A.from_method_1 or A.from_method_2
>>> A.from_method_1()
Created from method 1!
<t.A object at 0x7f3f7f2ca450>
>>> A.from_method_2()
Created from method 2!
<t.A object at 0x7f3f7f2ca350>

However, as this solution is a workaround with name mangling, it does have one flaw if you know how to look for it:

>>> A(A._A__obj)
<t.A object at 0x7f3f7f2ca450>

Upvotes: 5

Related Questions