The easiest way to explore the differences between Java and Python is to look at how we would implement a data structure in Python. In this example, we will build a simple linked list, though do note that we would never actually use a list like this in Python!
# This line is automatically added by the PyCharm IDE. It simply
# sets the author meta-variable for this module. If you are running
# your Python programs via the command line like a true wizard, it
# is not required.
__author__ = 'Mr. Davis'
# We define a class in Python with the class keyword. If we were to
# define a superclass for our Node, we would include a list of
# superclasses in parentheses following the class name:
# class Node(SuperClass1, SuperClass2):
class Node:
# By giving the argument 'next' a default parameter, it will
# automatically be set to None if there is nothing passed as
# a parameter. Therefore, the following two statements will
# be equivalent:
# x = Node(2, None)
# x = Node(2)
# We can, however, pass 'next' a parameter if we so choose:
# x = Node(2, other_node)
def __init__(self, data, next = None):
self.data = data
self.next = next
# __str__() returns a human-readable string representation
# of the class. If for some reason you needed a machine-
# readable representation, you should define a __repr__()
# method. However, we won't need to do this in this course.
def __str__(self):
# This is an example of setting a variable based on a
# condition. If our next Node is None, then we will
# append an empty string. Otherwise, we will append
# an arrow indicating that we have a Node following
# us.
suffix = '' if self.next is None else ' -> '
return "(" + str(self.data) + ")" + suffix
# __cmp__() is a 'magic method' that will allow us to use
# the comparison operators with our Nodes. In addition, there
# are six other magic comparison methods, corresponding to
# each comparison operator:
# __eq__() -> ==
# __ne__() -> !=
# __lt__() -> <
# __gt__() -> >
# __le__() -> <=
# __ge__() -> >=
# Other magic methods exist for other operators and useful
# builtins in Python. In Python 3 (which you may see in
# university), you must define all six methods, as __cmp__()
# has been removed:
# def __eq__(self, other):
#
# def __ne__(self, other):
#
# def __lt__(self, other):
#
# def __gt__(self, other):
#
# def __le__(self, other):
#
# def __ge__(self, other):
def __cmp__(self, other):
if isinstance(other, Node):
if self.data < other.data:
return -1
elif self.data > other.data:
return 1
return 0
else:
# This is equivalent to raising an exception in Java.
# TypeErrors should be used when the error is due to
# some argument or variable being the incorrect type,
# while ValueErrors should be used for errors due
# to obtaining an incorrect or error value.
raise TypeError("Operand of invalid type")
class List:
# Our List class will only store the location of the first
# node. We could of course keep track of the tail as well,
# but for this implementation we won't. In order to easily
# create empty lists, we give the head argument a default
# parameter of None.
def __init__(self, head = None):
self.head = head
# Defining __add__() will allow us to append items to our
# list like so:
# x = List()
# x + 2 # (2)
# x + Node(3) # (2) -> (3)
# We will be able to add integers or Nodes.
def __add__(self, other):
# Like Scala, functions are first-class values in Python,
# so we can have inner functions to make our code neater,
# and to practice the DRY principle (Don't Repeat Yourself)
def add_helper(other):
# None is a special kind of object called a singleton,
# meaning there is only ever one copy of it in memory
# when Python is running. The safest way of comparing to
# a singleton is using the 'is' keyword, and not '=='.
if self.head is None:
self.head = other
else:
tmp = self.head
while tmp.next:
tmp = tmp.next
tmp.next = other
# isinstance() allows us to check the type of a variable.
if isinstance(other, Node):
add_helper(other)
elif isinstance(other, int):
add_helper(Node(other))
else:
raise TypeError("Operand of invalid type")
# __radd__() will allow us to prepend items to our list like so,
# assuming 'x' is the same list:
# 1 + x # (1) -> (2) -> (3)
# Node(0) + x # (0) -> (1) -> (2) -> (3)
# Normally, those statements would try to call the __add__()
# method on an integer, or Node, which either don't exist or
# don't handle our custom class. However, we can define this
# method to give us that ability. So, given the following
# pattern of statements:
# operand1 op operand2
# Python will first try:
# operand1.__op__(operand2)
# If that fails, it will try:
# operand2.__rop__(operand1) # Notice the added r
# If that also fails, the program will crash.
def __radd__(self, other):
def radd_helper(other):
if self.head is None:
self.head = other
else:
other.next = self.head
self.head = other
if isinstance(other, Node):
radd_helper(other)
elif isinstance(other, int):
radd_helper(Node(other))
else:
raise TypeError("Operand of invalid type")
# __sub__() will allow us to remove items from our list
# by searching for the integer or Node, then removing it.
# Only statements like the following make sense algebraically,
# so we only define __sub__(), not __rsub__():
# x - 2 # (0) -> (1) -> (3)
def __sub__(self, other):
def sub_helper(other):
if self.head is None:
# Having an empty list means we have essentially
# an error value, so we raise a ValueError
raise ValueError("Empty list")
elif self.search(other) is None:
# Having a list without the node we're deleting
# is also a problem...
raise ValueError("Operand not found in list")
elif self.head == other:
self.head = self.head.next
else:
val = self.search(other)
tmp = self.head
while tmp.next != val:
tmp = tmp.next
tmp.next = val.next
if isinstance(other, Node):
sub_helper(other)
elif isinstance(other, int):
sub_helper(Node(other))
else:
raise TypeError("Operand of invalid type")
# __str__() is implemented in a functional style, to show
# that Python is multi-paradigm just like Scala. We build
# our string by recursively building our string and passing
# it back to the recursive call until we reach the end of
# the list.
def __str__(self):
if self.head is None: return "Empty list"
else:
def str_helper(list, list_str):
if list.head is None: return list_str
else:
tmp = list.head
return str_helper(List(tmp.next), list_str + str(tmp))
return str_helper(self, "")
# search() is implemented in a functional style, to show
# that Python is multi-paradigm just like Scala. We search
# the list by recursively checking the head of smaller
# sub-lists.
def search(self, val):
def search_helper(list, val):
# We cannot combine our boolean expressions here,
# since Python would be unable to tell if list.head
# is supposed to be a Node or None. If you don't
# understand, try this if-statement instead and
# explore the error you receive:
#
# if list.head == val or list.head is None:
# return list.head
# else:
# return search_helper(List(list.head.next), val)
if list.head is None:
return None
elif list.head == val:
return list.head
else:
return search_helper(List(list.head.next), val)
if isinstance(val, Node):
return search_helper(self, val)
elif isinstance(val, int):
return search_helper(self, Node(val))
else:
raise TypeError("Searching for incorrect type")
if __name__ == "__main__":
q = List()
print q
q + 2
print q
q + Node(3)
print q
1 + q
print q
q - 2
print q