from typing import Any # abstract class class Stack: # abstact method def is_empty(self) -> bool: raise NotImplementedError def push(self, item: Any) -> None: raise NotImplementedError def pop(self) -> Any: raise NotImplementedError # polymorphic code using inheritance def push_and_pop(stack: Stack, item: Any) -> None: stack.push(item) stack.pop() class Stack1(Stack): """A last-in-first-out (LIFO) stack of items. Stores data in a last-in, first-out order. When removing an item from the stack, the most recently-added item is the one that is removed. >>> s = Stack1() >>> s.is_empty() True >>> s.push('hello') >>> s.is_empty() False >>> s.push('goodbye') >>> s.pop() 'goodbye' """ # Private Instance Attributes: # - items: a list containing the items in the stack. The end of the list # represents the top of the stack. _items: list def __init__(self) -> None: """Initialize a new empty stack.""" self._items = [] def is_empty(self) -> bool: """Return whether this stack contains no items. """ return self._items == [] def push(self, item: Any) -> None: """Add a new element to the top of this stack.""" self._items.append(item) def pop(self) -> Any: """Remove and return the element at the top of this stack. Raise an EmptyStackError if this stack is empty. """ if self.is_empty(): raise EmptyStackError # Could also raise a built-in error, like ValueError/IndexError else: return self._items.pop() def __str__(self): return 'Test' class EmptyStackError(Exception): """Exception raised when calling pop on an empty stack.""" # def __str__(self) -> str: # """Return a string representation of this error.""" # return 'pop may not be called on an empty stack' class Stack2: # Duplicated code from Stack1 omitted. Only push and pop are different. def push(self, item: Any) -> None: """Add a new element to the top of this stack. """ self._items.insert(0, item) def pop(self) -> Any: """Remove and return the element at the top of this stack. Preconditions: - not self.is_empty() """ return self._items.pop(0)