104

How do I create a private constructor which should be called only by the static function of the class and not from else where?

Anders
  • 8,307
  • 9
  • 56
  • 88
user1057793
  • 1,041
  • 2
  • 7
  • 3
  • 10
    There is no notion of private vs. public methods in Python. – Fred Foo Nov 21 '11 at 12:50
  • 11
    [Python is not Java.](http://dirtsimple.org/2004/12/python-is-not-java.html) – Sven Marnach Nov 21 '11 at 12:51
  • 8
    @Tadeck, please stop wrongly correcting people. Mangling a name does not make it private. – Daniel Roseman Nov 21 '11 at 13:15
  • 11
    @DanielRoseman: larsmans said " _there is no notion of private vs. public methods in Python_ ". I say he is wrong. [Documentation](http://docs.python.org/tutorial/classes.html#private-variables) says: " _Since there is a valid use-case for class-private members (...), there is limited support for such a mechanism, called name mangling._ ". Please stop arguing without proving you are right. – Tadeck Nov 21 '11 at 13:21
  • 2
    @Tadeck, repeating my answer: a method marked with `__` can still be called as `obj._ClassName__Method()`. Now would you please explain, how this is private? – Zaur Nasibov Nov 21 '11 at 13:32
  • 4
    @BasicWolf: Please read my comment fully first. Name mangling gives you a (partial) notion of private classes. Please do not say calling `obj._ClassName__Method()` outside of `ClassName` is correct behaviour. – Tadeck Nov 21 '11 at 13:38

13 Answers13

72

The _ and __ prefixes don't offer a solution to restricting instantiation of an object to a specific 'factory', however Python is a powerful toolbox and the desired behaviour can be achieved in more than one way (as @Jesse W at Z has demonstrated). Here is a possible solution that keeps the class publicly visible (allowing isinstance etc.) but ensures construction is only possible by class-methods:

class OnlyCreatable(object):

    __create_key = object()

    @classmethod
    def create(cls, value):
        return OnlyCreatable(cls.__create_key, value)

    def __init__(self, create_key, value):
        assert(create_key == OnlyCreatable.__create_key), \
            "OnlyCreatable objects must be created using OnlyCreatable.create"
        self.value = value

Constructing an object with the create class-method:

>>> OnlyCreatable.create("I'm a test") 
<__main__.OnlyCreatable object at 0x1023a6f60>

When attempting to construct an object without using the create class-method creation fails due to the assertion:

>>> OnlyCreatable(0, "I'm a test")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in __init__
AssertionError: OnlyCreatable objects can only be created using OnlyCreatable.create

If attempting to create an object by mimicking the create class-method creation fails due to compiler mangling of OnlyCreatable.__createKey.

>>> OnlyCreatable(OnlyCreatable.__createKey, "I'm a test")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'OnlyCreatable' has no attribute '__createKey'

The only way to construct OnlyCreatable outside of a class-method is to know the value of OnlyCreatable.__create_key. Since this class-attribute's value is generated at runtime and it's name is prefixed with __ marking it as inaccessible it is effectively 'impossible' to obtain this value and/or construct the object.

Eivind Eklund
  • 208
  • 1
  • 4
  • 5
    It's not impossible to access, it's `OnlyCreatable._OnlyCreatable__create_key`. Or you can run Python with assertions disabled using the `-O` flag or `PYTHONOPTIMIZE` env var. – jonrsharpe Apr 22 '19 at 07:15
  • 1
    This has nothing to do with python having a "powerful toolbox". Any language can implement this. The reason it would be considered a horrible practice in any true OOP is there is no way to identify that instantiating a class with throw an exception until you actually run the code. The principle of least astonishment is therefore horribly broken in this use case. – Tarynn Jan 17 '22 at 21:20
  • In `__init__`, make it an if-statement instead of an assertion -- assertions can easily be disabled and in some environments they are. Also, you may replace `==` test for equality with `is`, to make programmer's intention clear if not for anything else. – amka66 Oct 03 '22 at 23:18
  • @Tarynn I wonder which languages you consider to be "truly OOP". I've used many languages and I'm surprised how far they are in the most basic object-oriented aspects compared to Python. In Python I truly see that "everything is an object". In Java, C#, C++ that statement is laughably wrong. Functions are not objects, classes are not objects, methods are not objects. Etc. – Ark-kun May 05 '23 at 01:53
  • To extend @amka66's comment, you can hack this with a key `class Hack: def __eq__(self, other): return True`. Use `is` if you want this to work even for evil objects – joel May 27 '23 at 19:18
39

How do I create a private constructor?

In essence, it's impossible both because python does not use constructors the way you may think it does if you come from other OOP languages and because python does not enforce privacy, it just has a specific syntax to suggest that a given method/property should be considered as private. Let me elaborate...

First: the closest to a constructor that you can find in python is the __new__ method but this is very very seldom used (you normally use __init__, which modify the just created object (in fact it already has self as first parameter).

Regardless, python is based on the assumption everybody is a consenting adult, thus private/public is not enforced as some other language do.

As mentioned by some other responder, methods that are meant to be "private" are normally prepended by either one or two underscores: _private or __private. The difference between the two is that the latter will scramble the name of the method, so you will be unable to call it from outside the object instantiation, while the former doesn't.

So for example if your class A defines both _private(self) and __private(self):

>>> a = A()
>>> a._private()   # will work
>>> a.__private()  # will raise an exception

You normally want to use the single underscore, as - especially for unit testing - having double underscores can make things very tricky....

HTH!

Ryan Li
  • 9,020
  • 7
  • 33
  • 62
mac
  • 42,153
  • 26
  • 121
  • 131
  • 2
    +1 That is the correct answer! :) Actually you can call private method from outside the class, but you should not do it, so your answer is ok. Thanks. – Tadeck Nov 21 '11 at 13:24
  • 14
    The question was about private constructor, not private member functions. You're just explaining how to have private functions. – Joshua Chia Oct 27 '14 at 03:14
  • 1
    @Syncopated - As pointed out by all answers and comments to the question, **there is no such a thing as "private constructors" in Python**. – mac Oct 27 '14 at 11:15
  • 4
    @mac If that's the case, then your answer should be "It's impossible.", not what you wrote, since the question was clearly about constructors, not functions. Or, if you can think of a way to have the desired effect, that would be the answer. – Joshua Chia Oct 28 '14 at 05:20
  • @Syncopated - Suggestion taken, have a look at the revised answer. :) – mac Oct 28 '14 at 08:36
  • I'm quite sure that I have already used liberies which throw an exception when you are calling some constructors from your code. So, at least, it seems not to be impossible. However, it might still be discouraged. I'm also wondering why some persons dislike the term "constructor" for Python. Constructors in Java and C++ are about initialization, just as in Python. The constructor does not handle allocation. C++ does even have an allocator similar to `__new__`, the [new operator](https://en.cppreference.com/w/cpp/memory/new/operator_new). However, it is not as powerfull as `__new__` in Python. – JojOatXGME Aug 27 '18 at 20:40
  • 2
    The whole "consenting adult" meme needs to die. The reason private modifiers exist is because objects are intended to be a kind of state machine. In OOP methods and members are tightly coupled. The reason for making a private member is because changing it outside of the public interface may have non obvious side effects resulting in hard to track down bugs. – Tarynn Jan 17 '22 at 21:21
  • "Python is based on the assumption everybody is a consenting adult" Python is based on many bizarre, weak or just dated assumptions. I want the language to help me express my intent and also enforce the intent. Python fails at it on features that I take for granted in other languages. – async Jul 09 '22 at 18:07
17

You can have considerable control over what names are visible in what scopes -- and there are lots of scopes available. Here are two three other ways to limit construction of a class to a factory method:

#Define the class within the factory method
def factory():
  class Foo:
    pass
  return Foo()

OR

#Assign the class as an attribute of the factory method
def factory():
  return factory.Foo()
class Foo:
  pass
factory.Foo = Foo
del Foo

(Note: This still allows the class to be referred to from outside (for isinstance checks, for example), but it makes it pretty obvious that you aren't supposed to instantiate it directly.)

OR

#Assign the class to a local variable of an outer function
class Foo:
  pass
def factory_maker():
  inner_Foo=Foo
  def factory():
    return inner_Foo()
  return factory
factory = factory_maker()
del Foo
del factory_maker

This makes it impossible (at least, without using at least one magic (double underscore) property) to access the Foo class, but still allows multiple functions to use it (by defining them before deleting the global Foo name.

joel
  • 6,359
  • 2
  • 30
  • 55
  • 1
    +1. I'd like to add that in this case, `isinstance` checks will be impossible because the class is not accessible (or at least, not supposed to be accessed). That can be solved by letting the class inherit from a visible abstract base class. – Fred Foo Sep 24 '14 at 21:52
  • 2
    The 2nd option allows isinstance checks, by referring to the class as factory.Foo – Jesse W at Z - Given up on SE Sep 24 '14 at 22:34
  • 1
    True, but I assumed client code wasn't permitted to see the class from the outside? – Fred Foo Sep 25 '14 at 08:14
  • 1
    I was intending it to make it _easier_ to call the factory than to instantiate the class directly, rather to make it **impossible** (well, mostly) to instantiate directly (for that, use the first form). – Jesse W at Z - Given up on SE Sep 25 '14 at 16:51
17

Though strictly private attributes do not exist in Python, you can use a metaclass to prevent the use of the MyClass() syntax to create a MyClass object.

Here is an example adapted from the Trio project:

from typing import Type, Any, TypeVar


T = TypeVar("T")


class NoPublicConstructor(type):
    """Metaclass that ensures a private constructor

    If a class uses this metaclass like this:

        class SomeClass(metaclass=NoPublicConstructor):
            pass

    If you try to instantiate your class (`SomeClass()`),
    a `TypeError` will be thrown.
    """

    def __call__(cls, *args, **kwargs):
        raise TypeError(
            f"{cls.__module__}.{cls.__qualname__} has no public constructor"
        )

    def _create(cls: Type[T], *args: Any, **kwargs: Any) -> T:
        return super().__call__(*args, **kwargs)  # type: ignore

Here is an example of use:

from math import cos, sin


class Point(metaclass=NoPublicConstructor):
     def __init__(self, x, y):
         self.x = x
         self.y = y

     @classmethod
     def from_cartesian(cls, x, y):
         return cls._create(x, y)
     
     @classmethod
     def from_polar(cls, rho, phi):
         return cls._create(rho * cos(phi), rho * sin(phi))

Point(1, 2) # raises a type error
Point.from_cartesian(1, 2) # OK
Point.from_polar(1, 2) # OK
Noé Rubinstein
  • 786
  • 6
  • 17
5

Quoting the Python style guide (PEP 8):

In addition, the following special forms using leading or trailing underscores are recognized (these can generally be combined with any case convention):

  • _single_leading_underscore: weak "internal use" indicator. E.g. "from M import *" does not import objects whose name starts with an underscore.

  • single_trailing_underscore_: used by convention to avoid conflicts with Python keyword, e.g. Tkinter.Toplevel(master, class_='ClassName')

  • __double_leading_underscore: when naming a class attribute, invokes name mangling (inside class FooBar, __boo becomes _FooBar__boo; see below).

  • __double_leading_and_trailing_underscore__: "magic" objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
gimel
  • 83,368
  • 10
  • 76
  • 104
4

If a module-level function is OK instead of a static method ...

Make the whole class private, expose the API as an abstract class, and use a function to instantiate your private class

class Foo(ABC):
    @abstractmethod
    def bar(self) -> int:
        ...


class _SpecificFoo(Foo):
    def __init__(self, bar: int):
        self._bar = bar

    def bar(self) -> int:
        return self._bar


def specific_foo(bar: int) -> Foo:
    return _SpecificFoo(bar)

Note

  • _SpecificFoo.__init__ and specific_foo can have different signatures
  • specific_foo returns a Foo. The API makes no mention of _SpecificFoo

This is very similar to Jesse's first option, but the class isn't redefined on each call to specific_foo, and the class interface is statically typed.

joel
  • 6,359
  • 2
  • 30
  • 55
  • 1
    This is a great solution from the perspective of type annotation. We can still use `Foo` in type annotations, but the protected status of `_SpecificFoo` will strongly encourage us to instatiate new instances using `specific_foo` instead. This is also more pythonic than solutions that actually throw an error if we try to use the constructor. It's better to just discourage it using an underscore. I'm surprised this doesn't have more up votes. – croncroncron Aug 16 '21 at 16:03
2

If a module-level function is OK instead of a static method, see my other answer

You can achieve something to this effect with an abstract class. Any instance attributes that need defining in the "private constructor" can be abstract properties. Your factory class method then builds its own concrete class by populating these abstract attributes, as well as doing any other initialisation work such as data validation.

from abc import ABC, abstractmethod

class Foo(ABC):
    @property
    @abstractmethod
    def _a(self) -> int:
        pass

    def bar(self) -> int:
        return self._a + 1

    @classmethod
    def from_values(cls, a: int) -> 'Foo':
        class _Foo(cls):
            def __init__(self, __a):
                self.__a = __a

            @property
            def _a(self):
                return self.__a
        
        return _Foo(a)

Foo()  # TypeError: Can't instantiate abstract class ...
Foo.from_values(1).bar()  # 1

If you find you need no abstract attributes on Foo, you won't get the TypeError when calling Foo(). In that case you can either rely on the inheritance from ABC as documentation, or define a dummy attribute for safety.

Optional tweaks

  • Need mutable instance attributes? Add setters.
  • Don't care about the difference between class and instance attributes? Simplify with
    class _Foo(cls):
        _a = a
    
    return _Foo()
    
joel
  • 6,359
  • 2
  • 30
  • 55
2

If you just want a simple way to signal that a constructor isn't supposed to be used directly, you can follow the following pattern to make the default usage of the constructor raise an exception:

class Foo:
    def __init__(self, arg, __private=False):
        assert __private, "Constructor shouldn't be called directly"
        self.arg = arg

    @classmethod
    def from_bar(cls, bar: Bar):
        return cls(bar.arg, __private=True)

    @classmethod
    def from_baz(cls, baz: Baz):
         return cls(baz.arg, __private=True)

Of course, anyone can still do Foo('abc', __private=True) but it would send a clear signal that such behavior isn't supported which follows the general design of Python.

Matthew D. Scholefield
  • 2,977
  • 3
  • 31
  • 42
  • 3
    Note that of course in certain configurations (ie. `PYTHONOPTIMIZE=TRUE`) Python will skip checking asserts. If this is a case where you still want this check, you may consider changing `assert` to `if not __private: raise RuntimeError("...")`. – Matthew D. Scholefield Dec 21 '21 at 14:51
  • `__private` can be made even more obscure(but possibly less portable) by using unicode characters. – Dmytro Aug 26 '22 at 11:26
2

Fist of all, the term "constructor" does not apply to Python, because, although __init__() method plays a role of one, it is just a method which is called when an object has already been created and requires initialization.

Every method of a class in Python is public. Generally programmers mark "private" methods with _ or __ in the name of a method, e.g.:

# inheriting from object is relevant for Python 2.x only
class MyClass(object): 
    # kinda "constructor"
    def __init__(self):
        pass

    # here is a "private" method
    def _some_method(self):
        pass

    # ... and a public one
    def another_method(self):
        pass
Zaur Nasibov
  • 22,280
  • 12
  • 56
  • 83
  • 4
    @Tadeck, this is not true, and BasicWolf is correct. Methods prefixed by double-underscores are not private - they are name-mangled, but it is still possible to access them from outside the class. – Daniel Roseman Nov 21 '11 at 13:14
  • 1
    @Tadeck, try this: create a class `MyClass` with `__my()` method. Consider `mc_obj = MyClass()`. You'll be able to access `__my()` via `mc_obj._MyClass__my()`. – Zaur Nasibov Nov 21 '11 at 13:17
  • 1
    @BasicWolf: I know you can do it, but it does not mean you **should** do it, if you want to follow Python's rules. If you do not care, you can also become a fan of `eval()` or of using assignments such as `vars()['a'] = 10` instead of `a = 10`. – Tadeck Nov 21 '11 at 13:32
  • @BasicWolf: You said " _marking a method with single underscore means absolutely the same_ ", I totally disagree. Please read gimel's answer and check it yourself so you do know the difference between methods beginning with single and double underscores. – Tadeck Nov 21 '11 at 13:47
  • @BasicWolf: Please do not say this is exactly the same or prove it is. Your last comment is off-topic. I use `__` methods when I want the functionality they offer. What I wanted to point out is that beginning method's name with `_` is not the same as `__` and if you did not notice it already, see @gimel's or @mac's answers, please. – Tadeck Nov 21 '11 at 14:09
1

Other answers have mentioned that there is no such thing as "private" constructors (or access modifiers in general) in Python. While this is true, you can still take advantage of the __ prefixing behavior to accomplish the same end effect by "hiding" the actual implementation in a nested class with a double-underscore prefix:

class MyPublicClassWithPrivateConstructor:

    class __Implementation:
        def __init__(self, *args, **kwargs):
            # do the things
            self.args = args
            self.kwargs = kwargs

    @classmethod
    def make_my_thing(cls, *args, **kwargs)
        return cls.__Implementation(*args, **kwargs)

    def __new__(cls, *args, **kwargs):
        raise Exception("MyPublicClassWithPrivateConstructor cannot be initialized directly, use MyPublicClassWithPrivateConstructor.make_my_thing")

# elsewhere
from whateva import MyPublicClassWithPrivateConstructor

MyPublicClassWithPrivateConstructor.__Implementation  # type object 'MyPublicClassWithPrivateConstructor' has no attribute '__Implementation'
MyPublicClassWithPrivateConstructor.make_my_thing  # <bound method MyPublicClassWithPrivateConstructor.make_my_thing of <class 'whateva. MyPublicClassWithPrivateConstructor'>>
           
Daniel Schaffer
  • 56,753
  • 31
  • 116
  • 165
1

I know, I'm late but we can do something like this to create private constructor.

class MyClass:
    __instance = None
    __private = False

    @staticmethod
    def get_instance():

        MyClass.__private = True
        __instance = MyClass()
        MyClass.__private = False
        return MyClass.__instance
     
    def __init__(self):

        if MyClass.__private == False:
             raise Exception("Its a private method. Please use get_instance method")
        
  • 2
    That's great for singletons. How about for the rest of use cases? – Tarynn Jan 17 '22 at 21:08
  • 1
    @Tarynn for normal use cases just remove `if not MyClass.__instance:` condition and put everything outside of condition, and that will work. Let me edit it for general use cases. – Piyush Srivastava Jan 23 '22 at 08:09
  • i don't think this is thread-safe. I can call `MyClass()` while another thread has set `__private` to `True` – joel May 09 '22 at 14:33
  • @joel in python there is no multithreading exists. i.e. only one thread excuted at once even if you have used multithread, so this would never happen. – Piyush Srivastava May 28 '22 at 07:37
  • I really don't think that's true. See [this section](https://realpython.com/intro-to-python-threading/#race-conditions) of an article on multithreading in python. In particular, they say "The operating system can swap which thread is running _at any time_. A thread can be swapped out after any of these small instructions. This means that a thread can be put to sleep to let another thread run in the _middle_ of a Python statement. – joel May 28 '22 at 12:50
  • But where is the parallerism while talking about multithreading ? – Piyush Srivastava Jul 16 '22 at 11:11
1

Another solution, is to let __init__ ask for a kind of private secret that only factory methods will know.

See example below:

from __future__ import annotations

class Foo:
    name: str

    def __init__(self, check_private_constructor: str):
        if check_private_constructor != "from_within":
            raise RuntimeError("Please use named constructors!")

    @staticmethod
    def from_name(name: str) -> Foo:
        r = Foo("from_within")
        r.name = name
        return r

    @staticmethod
    def from_index(index: int) -> Foo:
        r = Foo("from_within")
        r.name = str(index)
        return r
        
    def __str__(self):
        return "Foo.name=" + self.name


# a = Foo("eee")    # will raise an exception
a = Foo.from_name("Bob")
print(a)
b = Foo.from_index(3)
print(b)
Pascal T.
  • 3,866
  • 4
  • 33
  • 36
-2
class MongoConn(object):
    @classmethod
    def instance(cls):
        if not hasattr(cls, '_instance'):
            cls._instance = cls()
        return cls._instance

    def __init__(self):
        assert not hasattr(self.__class__, '_instance'), 'Do not call constructor directly!'

If you want a single instance.

BaiJiFeiLong
  • 3,716
  • 1
  • 30
  • 28