Welcome, guest | Sign In | My Account | Store | Cart

Another pattern for creating singleton instances of classes in Python.

Python, 107 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
from __future__ import print_function

class SomeImplicitBase(object):
    """
    A special base class that is an implicit base of all singletons
    created with this pattern.  Not necessary, but included to show
    how this sort of thing would be done if needed.
    """
    pass

class SingletonMeta(type):
    """
    Needed to override the mro() to include implicit base classes
    at the end of the method resolution order, but before any of
    the bases of these implicit base classes that may appear in
    the mro of the explicit bases
    """
    def __new__(mcs, name, all_bases, cls_dict):
        return type.__new__(mcs, name, all_bases, cls_dict)
    def mro(cls):
        # Get what the __mro__ would be without special bases
        new_mro_bases = list(type("___unused_class_name_1",
            cls.__dict__['__explicit_bases__'], dict(cls.__dict__)).__mro__)
        # Omit __unused_class_name_1
        new_mro_bases = new_mro_bases[1:]
        # start with the mro that excludes the implicit bases
        for ibase in cls.__dict__["__implicit_bases__"]:
            # Iterate until we find an entry in the MRO that is a base
            # of ibase, and insert just before that.  MRO on a list like
            # this should be well-defined if the MRO is well-defined on
            # the explicit base list.
            spot = len(new_mro_bases)
            for i, bcls in enumerate(new_mro_bases):
                if bcls in ibase.__mro__:
                    spot = i
                    break
            new_mro_bases.insert(spot, ibase)
        # Now call type.mro() with this new, combined base list
        #   again omitting ___unused_class_name_2
        return tuple(
            [cls] + list(
                type("___unused_class_name_2",
                    tuple(new_mro_bases), dict(cls.__dict__)).__mro__[1:]
            )
        )
def as_singleton_instance(cls):
    """
    This is where the magic happens.  This defines a
    class decorator that returns an *instance* of a class
    with the name "_<original_name>__class" that has
    the same member functions, etc as the cls argument.
    The type of the returned value is not accessible to the
    user (except via the __class__ attribute of the returned
    instance), and therefore no other instances of the class
    can "accidentally" be created.
    """
    new_name = "_{0}__class".format(cls.__name__)
    new_dict = dict(cls.__dict__)
    implicit_bases = (SomeImplicitBase,)
    new_dict['__explicit_bases__'] = cls.__bases__
    new_dict['__implicit_bases__'] = implicit_bases
    all_bases = list(cls.__bases__) + list(implicit_bases)
    rv_type = SingletonMeta(
        new_name,
        tuple(all_bases),
        new_dict
    )
    rv = rv_type()
    return rv

class SomeOtherBaseClass(object):
    _some_property = 25

@as_singleton_instance
class this_is_an_instance(SomeOtherBaseClass):
    """
    this_is_an_instance will be an *instance* of a class
    named _this_is_an_instance__class, which will have the
    base classes SomeOtherBaseClass and SomeImplicitBase.
    All of the methods below will work as expected.
    """
    def __call__(self, *args):
        print("In instance __call__()")
    def some_function(self, *args):
        print("called some_function({0})".format(
            ", ".join(repr(a) for a in args)
        ))
    @property
    def some_property(self):
        print("getting some_property")
        return self._some_property
    @some_property.setter
    def some_property(self, new_value):
        print("setting some_property to {0}".format(new_value))
        self._some_property = new_value


if __name__ == "__main__":
    this_is_an_instance() # => prints "In instance __call__()"

    this_is_an_instance.some_function(
        "hello", "world"
    ) # => prints "called some_function("hello", "world")

    print(this_is_an_instance.some_property) # => prints "getting some_property", then "25"
    this_is_an_instance.some_property = 55 # => prints "setting some_property to 55"
    print(this_is_an_instance.some_property) # => prints "getting some_property", then "55"
Created by David Hollman on Fri, 31 Jul 2015 (MIT)
Python recipes (4591)
David Hollman's recipes (1)

Required Modules

  • (none specified)

Other Information and Tasks