an oop question

Julieta Shem jshem at yaxenu.org
Sun Oct 30 13:30:08 EDT 2022


Julieta Shem <jshem at yaxenu.org> writes:

[...]

>>   . If you should, however, be talking about the new "type hints":
>>   These are static and have "Union", for example, "Union[int, str]"
>>   or "int | str".
>
> I ended up locating such features of the language in the documentation,
> but I actually am not interested in declaring the type to the compiler
> (or to the reader).  
>
> I was looking for a solution like yours --- thank you! ---, although I
> was hoping for handling that situation in the construction of the Stack
> object, which was probably why I did not find a way out.  Right now I'm
> looking into __new__() to see if it can somehow produce one type or
> another type of object depending on how the user has invoked the class
> object.
>
> Terminology.  By ``invoking the class object'' I mean expressions such
> as Class1() or Class2().  ``Class1'' represents the object that
> represents the class 1.  Since the syntax is that of procedure
> invokation, I say ``invoking the class object''.

An experiment.  What's my definition of Stack?  It's either Empty or
Pair, so it's a union.  So let us create two inner classes (that is,
inner to Stack) and make them behave just like Empty and Pair.  Using
__new__(), we can produce a Stack object that is sometimes Empty() and
sometimes Pair(...).

class Stack:
  class StackEmpty(Empty):
    pass
  class StackPair(Pair):
    pass
  def __new__(clss, *args):
    if len(args) == 0:
      return Stack.StackEmpty()
    else:
      return Stack.StackPair(*args)

This does it.  However, we are exposing the different types to the user.

>>> Stack()
Empty()

>>> Stack(1, Stack())
Pair(1, Empty())

Since we have our own copies of Empty and Pair inside Stack, we change
their representation.

class Stack:
  class StackEmpty(Empty):
    def __str__(self):
      return "Stack()"
    def __repr__(self):
      return str(self)
  class StackPair(Pair):
    def __str__(self):
      return "Stack({!r}, {})".format(self.first, str(self.rest))
    def __repr__(self):
      return str(self)
  [...]

>>> Stack()
Stack()

>>> Stack(1, Stack())
Stack(1, Stack())

That's it.  That's what I was looking for.  However, I don't really like
the OOP approach here because of what's going to happen next.

Now we are free to move on with implementing push and pop, say.  But
since Stack is really a union, we need to define push and pop on each of
these inner types that make up Stack.  Let's do it and see what we get.

  class StackEmpty(Empty):
    [...]
    def pop(self):
      raise ValueError("cannot pop from an empty stack")
    def push(self, x):
      return Stack(x, self)

  class StackPair(Pair):
    def pop(self):
      return self.first
    def push(self, x):
      return Stack(x, self)

The job is done.

>>> Stack().push("it")
Stack('it', Stack())

>>> Stack(1, Stack()).push(2).push(3)
Stack(3, Stack(2, Stack(1, Stack())))

We may observe that we had to write the same exact procedure /push/ in
both types.  Sounds funny to say that it seems that we are too modular
here.  I'm pretty sure you'll find a way to grab the procedure of one
class and apply it on the other, so I really think there's a way out of
not wasting keyboard-typing work and keeping things with a ``single
point of control''.

(*) Complete code

class Stack:
  class StackEmpty(Empty):
    def __str__(self):
      return "Stack()"
    def __repr__(self):
      return str(self)
    def pop(self):
      raise ValueError("cannot pop from an empty stack")
    def push(self, x):
      return Stack(x, self)
  class StackPair(Pair):
    def __str__(self):
      return "Stack({!r}, {})".format(self.first, str(self.rest))
    def __repr__(self):
      return str(self)
    def pop(self):
      return self.first
    def push(self, x):
      return Stack(x, self)
  def __new__(clss, *args):
    if len(args) == 0:
      return Stack.StackEmpty()
    else:
      return Stack.StackPair(*args)


More information about the Python-list mailing list