I have a question about a particular case I'm working on. I'm studying OOP. To ask the question, I'm going to have to introduce you my context here, so you'll need to bear with me. If you'd like to see the question right away, go to the section ``My difficulty in encapsulating a union''.
(*) Introduction I wrote the classes class Empty: ... class Pair: ... With them, I can (sort of) make a union of them and build a Lisp-like list by defining that my Lisp-like list is either Empty or a Pair. For instance, here's a Lisp-like sequence that represents the string "abc". >>> Pair.fromIterable("abc") Pair('a', Pair('b', Pair('c', Empty()))) So far so good. (``Full'' code at the end of this message, if you'd like to more carefully look into my reasoning there.) (*) How to build a stack? These Lisp-like sequences are clearly a stack. You pop an element from it by grabbing the first element. You push an element on to it by just pairing the new element with the current stack. For instance, let's pop the 1-string "a" off of the stack. >>> ls = Pair.fromIterable("abc") >>> ls.first 'a' >>> ls = ls.rest >>> ls Pair('b', Pair('c', Empty())) Done. Let's push it back on. >>> ls = Pair("a", ls) >>> ls Pair('a', Pair('b', Pair('c', Empty()))) So far so good, but when it comes to building a better user interface for it I have no idea how to do it. I think one of the purposes of OOP is to organize code hierarchically so that we can reuse what we wrote. So I tried to make a Stack by inheriting Pair. class Stack(Pair): pass >>> Stack(1, Empty()) Stack(1, Empty()) Then I wrote pop and push. >>> Stack(1, Empty()).pop() 1 >>> Stack(1, Empty()).push(2) Stack(2, Stack(1, Empty())) So far so good. Now let me show you what I can't do. (*) The difficulty of encapsulating a union The Lisp-like sequences we're building here are union-like data structures. A /sequence/ is either Empty() or Pair(..., /sequence/). I have not found a way to represent this either-or datastructure with a class. For example, there is no way right now to build an empty Stack by invoking the Stack constructor. >>> Stack() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Pair.__init__() missing 2 required positional arguments: 'first' and 'rest' As I designed, an empty Stack is represented by Empty(), which is a whole other object. Users will have to know this. I wish I did not have to burden my users with such knowledge. For instance, I wish the answer was "Stack()" to the question of -- ``how can I build an empty stack?'' My desire seems to imply that I need a union-like data structure. If Stack is invoked with no arguments, it should produce Empty(), otherwise it produces Pair() as it does today. How can I achieve that? (*) Code class Pair: def __init__(self, first, rest): if not isinstance(rest, Pair) and not isinstance(rest, Empty): raise ValueError("rest must be Empty or Pair") self.first = first self.rest = rest def fromIterable(it): if len(it) == 0: return Empty() else: return Pair(it[0], Pair.fromIterable(it[1:])) def __str__(self): return "{}({!r}, {})".format(self.__class__.__name__, self.first, str(self.rest)) def __repr__(self): return str(self) def __len__(self): return 1 + self.rest.__len__() class Empty: def __len__(self): return 0 def __str__(self): return "Empty()" def __repr__(self): return self.__str__() def __new__(clss): if not hasattr(clss, "saved"): clss.saved = super().__new__(clss) return clss.saved class Stack(Pair): def pop(self): return self.first def push(self, x): return Stack(x, self) -- https://mail.python.org/mailman/listinfo/python-list