How do I create a private constructor which should be called only by the static function of the class and not from else where?
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!
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