Python Forum
Class that cannot be initialized
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Class that cannot be initialized
#1
In the process of using the Bridge pattern for some code, I ended up wanting a class which client code could not instantiate directly because the class is actually a wrapper around some implementation so that the constructor is itself an implementation detail.

Let's call the class Spam. So I want client code to obtain Spam instances through some library functions calls such as make_spam(), but if the client tries to call Spam(...) it raises NotImplementedError.

On the other hand, for the sake of creating library functions such as make_spam() or subclasses of Spam, these library functions and subclasses must have a private way of instantiating and initializing Spam instances.

I came up with a wrapper function privatize_init(klass) which is not exactly a class decorator because it returns a private object usable by library functions for this purpose. Here is the code, tell me what you think of this idea!

None of the solutions that I've found so far please me as much as this one, but I'm biased

#!/usr/bin/env python
# SPDX-FileCopyrightText: 2023 Eric Ringeisen
# SPDX-License-Identifier: MIT
import functools

__version__ = '2023.07.26'

class PrivateInitHandler:
    def __init__(self, klass):
        self.klass = klass
        self._init = klass.__init__

    def init(self, obj, *args, **kwargs):
        """Function to privately initialize an instance of self.klass"""
        self._init(obj, *args, **kwargs)

    def instantiate(self, *args, **kwargs):
        """Function to privately create an instance of self.klass"""
        obj = self.klass.__new__(self.klass, *args, **kwargs)
        self._init(obj, *args, **kwargs)
        return obj

def _wrapper(self, *args, **kwargs):
    raise NotImplementedError('Initialization not permitted')

def privatize_init(klass):
    handler = PrivateInitHandler(klass)
    klass.__init__ = functools.wraps(klass.__init__)(_wrapper)
    return handler

if __name__ == '__main__':

    class Spam:
        def __init__(self, value):
            self.value = value

        def __repr__(self):
            return f'<{type(self).__name__}: value={self.value!r}>'

    # After this call, Spam class cannot be directly called
    _Spam_private = privatize_init(Spam)

    # Client code cannot create directly a Spam instance:
    try:
        s = Spam('ham')
    except NotImplementedError as exc:
        print(exc)

    # However it is possible to implement new Spam constructors
    # that client code can invoke

    def make_spam(x):
        return _Spam_private.instantiate(x.upper())

    s = make_spam('eggs')
    print(s) # --> prints <Spam: value='EGGS'>

    # It is also possible to create subclass of Spam that can
    # be instantiated directly by client code

    class SubSpam(Spam):
        def __init__(self):
            _Spam_private.init(self, 'ham!')

    s = SubSpam()
    print(s) # --> prints <SubSpam: value='ham!'>
Output:
Initialization not permitted <Spam: value='EGGS'> <SubSpam: value='ham!'>
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020