Private Constructor in Python

36,892

Solution 1

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!

Solution 2

As no-one has mentioned this yet -- 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.

Solution 3

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

Solution 4

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.

Solution 5

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
Share:
36,892
Admin
Author by

Admin

Updated on January 23, 2022

Comments

  • Admin
    Admin 5 months

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