From a pure Python standpoint, a list is already a stack if you narrow its methods down to only pop
and append
.
l.append(x)
will put x
at the end of the list, and l.pop()
will remove the last element of the list and return it.
So generally speaking, when you need a stack in Python, you'll just make a list and use append
and pop
.
The same goes for queues, but you'd use l.pop(0)
instead of l.pop()
.
Considering this, I would say it's just plain unpythonic to implement a stack structure; instead, just use a list
, that really is a dequeue and thus can be used as both a stack and a queue.
That said, I understand that append
is not the common naming for the stack structure, and that push
makes the intent of the method more clear.
Therefore, I would implement a stack as a direct subclass of list
, and just "rename" the append
method:
class Stack(list):
def push(self, x):
super().append(x)
If I wanted to make it more fool-proof, I'd also override pop
so that it cannot take a parameter, unlike list.pop
class Stack(list):
...
def pop(self):
return super().pop()
Now the operation performed by the peek
method can usually be done by stack[-1]
(where stack
is an instance of Stack
), but it could be implemented just as follows:
class Stack(list):
...
def peek(self):
return self[-1]
Addendum
Some comments advising against subclassing list
came out, notably mentioning the "composition over inheritance" principle.
That was an enriching discussion, and I'd like to amend my answer accordingly.
I'm no expert in design patterns, but as I understand it, this principle advocates the use of composition instead of inheritance to provide more flexibility.
One benefit of composition here is clearly to provide a cleaner API, with only the common stack methods and not insert
, remove
and so on that come from list
and have no sense for stacks.
On the other hand, the inheritance I suggested happens to violate Liskov's substitution principle.
Although there's no problem with adding push
and peek
methods with respect to this principle, my implementation changes the signature of the pop
method, which is not allowed by this principle.
From this perspective, I think that not only composition is valid here and adds in the benefit of a clean API, but also that inheritance is plain incorrect here.
Therefore, if I were forced to implement a stack class in Python, I'd go with composition and do as follows:
class Stack:
def __init__(self):
self._data = []
def push(self, x):
self._data.append(x)
def pop(self):
return self._data.pop()
def peek(self)
return self._data[-1]
def __len__(self):
return len(self._data)
But again, as I said, I would never do that in real life because a list can already serve as a stack, and stack = []
would perfectly convey how stack
would be intended to be used.
References:
On the Liskov substitution principle:
On composition over inheritance: