0

I would like to write my own class Singleton that represents a set of size 1. It should

  1. subclass frozenset so that all the usual set operations work seamlessly between Singleton, set, frozenset, and
  2. add an assertion to __init__ of frozenset that checks that the underlying set is constructed on an iterable of length 1.

What is a clean way to do this? Subclassing? Decorators? Thank you for your advice.

hwong557
  • 1,309
  • 1
  • 10
  • 15
  • Please update your question with the code you have tried. – quamrana Aug 17 '22 at 13:17
  • 2
    If you only fix it in the constructor there would be nothing preventing you from adding more elements later – mousetail Aug 17 '22 at 13:18
  • An immutable "set" of length 1 could be represented by a simple tuple…? – deceze Aug 17 '22 at 13:19
  • 1
    @mousetail Good point, I'll use a `frozenset` instead. – hwong557 Aug 17 '22 at 13:20
  • 1
    @deceze Yes, but I would like all the usual set operations to work. e.g.`(1,) | {2, 3, 4} ` fails. – hwong557 Aug 17 '22 at 13:21
  • 1
    Presumably you want `Singleton | Set` to yield a Set, not a Singleton or an error? I'd subclass `frozenset`, and override `frozenset.__init__()` to add the assertion and call super's (i.e. frozenset's) `__init__()`. Have you tried that? What problems did you encounter? It seems like your answer is in your question, but maybe I missed something? – Sarah Messer Aug 17 '22 at 13:28
  • @SarahMesser Yes that was my first thought, and no you didn't miss anything. I didn't encounter a problem, but I was looking for advice from someone experienced. – hwong557 Aug 17 '22 at 13:30
  • 2
    Subclassing built-in / base classes to get a specific behavior's well-defined and robust. General rules of thumb: don't change more than you have to; make sure the doc string describes purpose and behavior; add unit tests to cover the common desired & error conditions, as well as a few edge cases. https://towardsdatascience.com/python-tricks-inheriting-from-built-in-data-types-f6cbeb8d88a5 has some examples. One difficulty is that you can't see the source code for built-in types, so `__or__()` might return either a new `Set` or `self.__class__`. Testing will clarify what you need. – Sarah Messer Aug 17 '22 at 13:42

1 Answers1

2

I'd just subclass frozenset and assert its length is 1.

class Singleton(frozenset):
    def __new__(cls, data):
        obj = super(Singleton, cls).__new__(cls, data)
        assert len(obj) == 1, "Must be of length 1"
        return obj

But that is literally a just going to be a frozenset of length 1.

x = Singleton([1])
print(x)
# Singleton({1})

print(Singleton([1]) | {2, 3})
# frozenset({1, 2, 3})

print(Singleton([1]) | Singleton([2]))
# frozenset({1, 2})

y = Singleton([1, 2])
# AssertionError: Must be of length 1

Not sure how useful the object would be, as most operations with Singleton operands are not going to be Singleton.

norok2
  • 25,683
  • 4
  • 73
  • 99
  • Thank you for your suggestion. May I ask why you use `__new__` instead of `__init__`? – hwong557 Aug 17 '22 at 13:44
  • Because `frozenset()` is an immutable type and it can only be initialized upon creation. See also this [question](https://stackoverflow.com/questions/4859129/python-and-python-c-api-new-versus-init), asking the difference between `__new__` and `__init__`. – norok2 Aug 17 '22 at 13:47