One of the risks in a class like this is that students leave with the wrong impression. Let's be clear: OOP is NOT specific to the Java programming language. OOP is a general design model that you will find relevant and important to all kinds of challenges that you are trying to solve, and which programming language you are using. Today we'll look at just one other language, Python, and show how Python designers integrated various aspects of OOP principles.
We don't have time to teach you Python in one class. However, there are a couple things you should know so that we can understand how it uses OOP design.
hello.py
| Output
Hello world This is a number: 9 |
if( x > 3 ):
print("x is greater!")
x = x + 1
else:
print("x will be penalized!")
x = x - 1
Semicolons don't end statements either. Actually, semicolons are allowed in Python, but most Python coders will make fun of you.
Java
| Python
|
x = 3
x = "hello"
This typing freedom goes hand-in-hand with (1) above in that there is no compiler to type check your code. It is up to you as the programmer to prevent type conflicts. This is a big deal and requires you to think a little differently while you code. If you want your variable to be assigned an object with a particular method (e.g., x.max()), it is up to you to make sure the x variable actually has that correct object type when you call the method, otherwise your program will crash when run. In contrast, Java's type checking obviously checks this for you at compile time before you even try running it.
example.py
| Output
Give me a word: cat Now a number: 5 I'll repeat cat 5 times. cat cat cat cat cat |
The first requirement for any OOP language is to have objects, and as you will see here, Python uses classes just like Java. The syntax is more bare-bones and quicker to define:
Python
| Java
|
Pretty simple, right? This is a new class "Foo" with a single member method "myfunction()". We'll get back to the self parameter soon. Now when you want to create an instance of Foo, you just do it:
Python
| Java
|
Encapsulation is the idea that methods should be associated with their objects. Everything you learned for Java applies to Python.
You already saw that the Python class can have its own member methods just like Java. One difference with Python is that these methods need to explicitly include the variable that points back to the object itself. In Java, we have the this variable which sort of comes for free whenever you're inside a class's member method. The designers of Python thought this odd and instead require that the programmer explicitly include this variable as a method parameter, and instead of this, it's simply called self. The reality as a Python programmer is that you'll include self at the start of every method's parameter list if it is a class method. Here is a short example with a couple methods:
class Cat:
def meow(self):
print("meow meow meow")
def jump(self, height):
for x in range(height):
print("jump")
def eat(self, type, amount):
self.meow()
if type == 'tuna':
return amount-5 # return the amount remaining after eating
else:
return amount-3 # return the amount remaining after eating
Look at the eat method. It calls self.meow() using self because meow() is in the same object! Java was different here because it performs inference to find meow() automatically so you don't have to type this.meow() if it's unambiguous. Python requires a bit more specificity because Python dislikes ambiguity (that's a good thing!). The one potential confusion is that methods with self have more parameters than you'll actually give when calling it. For instance, you'll call x.jump(10) even though the method has two parameters with self as its first. You get self for free.
What about data hiding? Well Python class instances can have member variables too, but this is where it gets a little different. Instance variables don't appear at the top of a class, but instead must be created in a special initialization method that is called when an instance of the class is created. Think of it as a Java constructor!
class Cat:
def __init__(self, heal, weigh):
self.health = heal # these are now your class variables!
self.weight = weigh # they aren't 'declared' at the top of the class
def meow(self):
print("meow meow meow")
def eat(self, type, amount):
self.meow()
if type == 'tuna':
self.health += 5
return amount-5 # return the amount remaining after eating
else:
self.health += 3
return amount-3 # return the amount remaining after eating
x = Cat(50, 20)
x.eat('tuna', 10)
print("Cat's health is now:", x.health)
Private vs Public?? Ok so classes have instance variables, but what about data hiding? Do we make getter/setter methods? Can variables be private? Can methods be private? No. Python does not have the concept of private and public! This is our first example of a language not adopting all OOP concepts. You can access any method or variable you'd like in an object at any time.
There is a loose exception to what we just wrote above. While you can access anything you want when coding, there is a Python convention that you can use to encourage you and other coders to pretend something is private. Put an underscore in front of your variable or method name. That means you shouldn't access this directly. Let's extend our above example:
class Cat:
def __init__(self, heal, weigh):
self._health = heal # these are now your class variables!
self.weight = weigh # they aren't 'declared' at the top of the class
def meow(self):
print("meow meow meow")
def eat(self, type, amount):
self.meow()
if type == 'tuna':
self._health += 5
return amount-5 # return the amount remaining after eating
else:
self._health += 3
return amount-3 # return the amount remaining after eating
def getHealth(self):
return self._health
x = Cat(50, 20)
x.eat('tuna', 10)
print("Cat's health is now:", x.getHealth())
Now we have a variable _health which by virtue of its name should encourage you to not access it directly. You should feel dirty if you type x._health and thus will instead type x.getHealth().
This is a slight detour because it isn't about data hiding, but you may wonder if python has static variables for classes. Yes! You can make a static variable by including it outside your class methods. Remember that normal object-specific variables must be set in the __init__() method. If you include a variable outside that method, it is static and there will only be one such variable for all instances of your class.
class Cat:
numcats = 0 # static, one variable for all Cat instances
def __init__(self, heal, weigh):
self._health = heal # these are now your class variables!
self.weight = weigh # they aren't 'declared' at the top of the class
Cat.numcats += 1
And there you have it. Look at the last line. Every time you create a cat object, it will increment the numcats static variable and you can access that using any of the cat objects you've created.
We finally arrive at the all important inheritance topic. Here is the rundown:
1. Python allows class inheritance.
2. Python supports multiple inheritance.
3. Python does not have interfaces.
4. Python does have abstract methods. (we won't cover this today)
Imagine that we want to create a new type of cat called Tabby which extends our Cat class above. Java uses the extends keyword, but Python just uses parentheses:
class Tabby(Cat):
def printColor(self):
print("I'm all colors of course!")
def meow(self):
print("MEOW!")
Pretty great, right? It's just like your OOP in Java rules! We extended a parent class, added a new method (printColor) and we over-rode the parent's meow() method to do it our own Tabby way! There is nothing new here except some syntax, so all the good stuff you've learned about inheritance and proper OOP design also applies to Python. Here is how you might then call this class:
y = Tabby(50,20)
y.printColor()
y.eat('tuna', 20)
print("Tabby's health is now:", y.health)
And the output:
I'm all colors of course! MEOW! Tabby's health is now: 55
Notice how we must give two parameters to the Tabby's constructor. Since we did not define a new one, it uses the parent's __init__ method which requires two of them. After that, you can see we inherited the eat() method.
Ok to summarize a bit more: inheritance from a single parent is the same, you inherit any methods and member variables (defined in the parent's __init__ method). You can also override methods by redefining them in the child class.
The big difference from Java is that the Python developers went all-in and allowed multiple inheritance. It's actually very easy to use, you just include more parent classes in your parenthetical:
class Tabby(Cat, Mammal):
...
As you might imagine, when you include multiple parents, you inherit all of their methods combined! That's a very useful thing to do, but as you should know from this class, it introduces ambiguities and some confusion. What if both Cat and Mammal have a method with the same name? Which one is called? The short answer to that is that the first parent class listed gets dibs on the method name. If the second class has the same method name, it won't be called by the Tabby class (in our example). But what if we actually want to call both the Cat's and the Mammal's method? Let's craft a silly example:
class Mammal:
def meow(self):
print("I don't typically meow.")
class Cat:
def meow(self):
print("meow meow meow!")
class Tabby(Cat, Mammal):
...
x = Tabby()
x.meow()
We have 2 options, but since Cat is listed first in the Tabby's parents, it is the Cat's method that is called ("meow meow meow!"). Ok, but what if we want the Tabby's method to actually call its parents' methods too?? Certainly that is possible, right? Yes indeed.
class Mammal:
def __init__(self):
self.health = 100
def meow(self):
print("I don't typically meow.")
class Cat:
def meow(self):
print("meow meow meow!")
class Tabby(Cat, Mammal):
def __init__(self, color):
super().__init__()
self.color = color
def meow(self):
print("MEOW!")
Cat.meow(self) # calling a specific parent
Mammal.meow(self)
x = Tabby('orange')
x.meow()
Remember in Java, we had the super variable which always gave us a hook into our parent. Python actually has a super() method which gives you a hook into the parent, but this dives a little deeper than we have time for in today's lecture. As you can imagine, with more than one parent, now the super() method becomes more ambiguous. It actually has to search through the parents in a specific order. This means Tabby(Cat,Mammal) is not the same as Tabby(Mammal,Cat). Ok now we're in the weeds. We just want to give you a taste into how a language that allows multiple inheritance can handle these things. The example above shows you one way of doing so when there are method naming conflicts.
Hopefully by now you realize that Python is not going to treat polymorphism like Java because it is not a strongly typed language. If the variables don't have required types, then the programming language is not going to have an enforcement mechanism for things like polymorphism. In the same way that Python doesn't have interfaces, what Python depends on is basically a set of loose conventions that programmers agree to. If you want a variable to always have a certain method available (like a list of GUI objects which all have paint()), then it is up to the programmers to ensure that they never assign values to that variable which aren't things that have a paint() method. Python doesn't check your code like Java does during compilation, which is where Java shines when it comes to polymorphism. It is more difficult in Python to write code that you can prove will work when run (at least from a typing standpoint, your code may still crash because of other reasons). That's just one tradeoff when you use Python, and obviously there are benefits to Python which may outweigh this negative. These are the typical debates that computer scientists and software developers like to have. The important takeaway for this class is that you can look at any new programming language you want, and understand its OOP mechanisms that are available to you.