Xavier Olive research teaching python blog til cli

Python private constructors

29 June 2016

There is no such thing as Python private constructors.

However, as I worked on wrapping some C-based code with Cython, I ended up with the following pattern. Some (boring) technical issues do not allow me to just have the make_foo processing in the constructor.

cdef class Foo(object):
    """Documentation for class Foo.

    Use `make_foo` to create such object.
    """

    cdef long value

    def __cinit__(self, value):
        self.value = value

    def __repr__(self):
        return "Foo: #{}".format(self.get_id())

    def get_id(self):
        return foo_id(self.value)

def make_foo(idx):
    """Build a Foo object from an id.

    >>> make_foo(12)
    Foo: #12
    """
    value = build_foo(idx)
    return Foo(value)

So, I expect the user to only call make_foo and I would love that the constructor raises a ValueError when it is called by the user and processes silently when it is called by authorised functions in the module.

Here is a suggestion to reach this goal.

Because of its double-underscore prefix, __SECRET__ is not available from outside the module, and as a basic object(), it is not possible to imitate it and try to fool the constructor of Foo. However, the make_foo function being part of the module knows the __SECRET__ and can safely pass it to the constructor.

__SECRET__ = object()

cdef class Foo(object):
    """Documentation for class Foo.

    The constructor raises a ValueError if called directly.
    >>> Foo(12)
    Traceback (most recent call last):
        ...
    ValueError: Use `make_foo` instead.
    """

    cdef long value

    def __cinit__(self, value, secret):
        if (secret != __SECRET__):
            raise ValueError("Use `make_foo` instead.")
        self.value = value

    def __repr__(self):
        return "Foo: #{}".format(self.get_id())

    def get_id(self):
        return foo_id(self.value)

def make_foo(idx):
    """Build a Foo object from an id.

    >>> make_foo(12)
    Foo: #12
    """
    value = build_foo(idx)
    return Foo(value, __SECRET__)