4

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.

1
  • You can just provide a default action for a = A(), like always call from_method_1(). Commented Mar 13, 2014 at 12:46

1 Answer 1

5

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>
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.