Object orientation is a central concept in Python, as well as many other languages. Understanding the concept, and applying it well, will enable you to build much more elegant and manageable software.
What is Object Oriented Programming in Python?
You’ll often hear it said that, in Python, everything’s an object. It’s pretty much true, with a few exceptions. An expression is not an object, but it always expresses one. Because the Python interpreter will evaluate any expression on contact, as soon as it reads one, an expression is essentially the object it evaluates to. A name is not an object either, but it always references an object. Names are also evaluated on contact, evaluating to whichever object they reference at that point in time. So, ultimately, everything is pretty much an object.
Python’s Built-In Objects vs Custom Objects
Each object is of a particular type. There’s a range of different types of objects built into Python, they’re predefined, and you can also define others yourself as needed. The built-in types include String, Float, List, Function, Module and so on. You can do a lot with these built-in types, but you can do a lot more if you learn to combine them to create custom types of object, tailored to your application. You might define a
SpaceInvader type, an
Employee type, an
Account type; it can be anything. Once you have defined, for example, an
Account type, you can then create as many instances of that type as you need. In the same way that you can have as many strings as you need, you can have as many accounts as you need.
Data vs Logic
Classically, there was a conceptual division between data and logic. Stuff like numbers and strings and so on were data, stuff like if branches and while loops were logic, and the two things were to be kept apart. Many modern languages have a different approach, combining data and logic to create objects. An object is a singular thing, with attributes, which store data, and methods, which define logic. Pretty much any thing can be thought of as an object and defined in terms of its attributes and methods.
An object’s attributes are really just values assigned to names and the methods are just like functions, so objects are pretty easy to understand if you know a little Python already.
If you wanted to do some banking, you might want to define some bank account objects. You’d start by defining a new object type, we’ll call it Account. Then, you could give it two attributes, one called fullname that’d have a string assigned to it, the person’s name, and one called balance that’d have a number, maybe a float, assigned to it, the current balance. The Account type would also need a method, we’ll call it transact, which changes the value of an Account object’s balance attribute, by adding to it or subtracting from it.
Lego as being Object Oriented
So why would thinking in terms of objects be useful? Think about building with Lego. Lego provides primitive object types, Bricks, Tiles, Plates and so on that we can build almost any construction with. If you only want to build a very simple model, a wall perhaps, you can get away with picking up a single brick and just adding bits until you’ve got what you want. If you want to build something more elaborate, say a city, you need to approach it differently. If you just keep adding bits to an ever expanding lump of Lego, you’ll only ever end up with a huge mess. You’ll instead need to think in terms of objects and interfaces.
Lego elements are objects that have pretty simple interfaces; there’s not much complexity in how bricks interface with other bricks or tiles or whatever. They can clip on the top or bottom, there’s not much to it. Because they have clean, simple interfaces, it’s easy to combine Lego elements to make more complex objects, a wall, a chimney, a fireplace. The important thing is to ensure that you build each of these new objects with simple interfaces too.
Well designed chimney objects have simple interfaces for attaching them to wall objects. A fireplace object should just clip onto the chimney and wall in a simple way. Assuming it’s designed well, you can forget how you made the fireplace, you may have spent a long time getting it looking just right, but it’s no longer relevant, only the complexity of its interface is an issue. So long as your fireplace clips easily into place, it doesn’t make any difference how complicated it was to build. That complexity is encapsulated inside the fireplace object, leaving only the complexity of its interface exposed.
If you continue combining these new objects to form an even more elaborate object, say a house, you can very easily add that house object to your city object, again assuming simple interfaces. And so on. Every object should be constructed from more primitive objects, all with simple interfaces, all the way down.
Objects for Object Oriented Programming in Python
In Python, if you defined an
Account type as discussed above, you’d realise early on that only having the account holder’s name and the current balance is not realistically going to be enough, so you may decide to include other data like the account holder’s home address, date of birth, that type of stuff, as well as things like the date the account was opened. Then, you might want methods to close the account, modify the account holder’s name, maybe they get married, it can all become a bit too much fairly quickly. However, if you think in terms of objects, it’s obvious that you’d want to separate it out a bit.
We can always define a new
Customer type that has the attributes and methods a customer would have, then define an Account type that has the attributes and methods of a bank account. The
Account type would have an attribute called
account_holder that would be assigned one of your
Customer type objects, instead of just a string or something.
Object definitions are like blueprints. You first define a new type of object, an object definition, you then create instances of that type, these instances are the objects themselves. Every object is one instance of a particular type of object. It will have the same attribute names as all other objects of the same type. For example, all Account type objects will have an account_holder attribute, but, as all instances of Account are objects in their own right, each can have something different assigned to its account_holder attribute.
Likewise, each instance of an object has the same methods as all other instances of the same type, but calling one instance’s method is specific to that instance. Every string has an upper method because it’s a string and the string type is defined with an upper method. You can call upper on any string, but it only affects that particular instance of String.
Remember, everything’s an object. The arguments you pass to a function are objects, so you can write a function that takes Customer objects as arguments and references their attributes and calls their methods. You can have a list of Account objects and iterate over the list as normal. Anything you can do with a built-in type of object, you can do with one of your own types.
This will all be made much more concrete in articles that cover actual Python syntax. The aim here is to try and introduce the Object Oriented Paradigm in a way that helps you understand what an object actually is, whilst also encouraging you to think about how to use them more powerfully. Python wizardry begins with OOP.