Jul-26-2023, 08:45 PM
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
On the other hand, for the sake of creating library functions such as
I came up with a wrapper function
None of the solutions that I've found so far please me as much as this one, but I'm biased
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!'>