User-Defined Classes
In an object-oriented language like Python, the concept of the fruit, Orange, corresponds to a class. A particular orange, say the one sitting on the table in front of us, is an object belonging to that class; it has the traits and capabilities we expect of an Orange. Now let’s see how to define our own Orange class, and how to instantiate objects of that class.
We’ll start by considering what we’d want in a class called Orange. We imagine that an object in this class should have two attributes to describe it, color and shape, and two methods that do something to it, eat and squeeze. Here’s how we translate the verbal description of the Orange class to the Python programming language:
All the class instance-methods (i.e., methods which operate on instances of the class, which generally means all methods defined inside the class) have self as the first parameter in their definitions. However, function calls from outside the class do not include the self argument. C++ or C# programmers may consider it to be similar to the role of “this” in those languages.
Next we’d like to define an instance that is associated with this class. In the following, we declare florida_orng (a Florida Orange!) as an instance of the Orange class, which means florida_orng possesses all the attributes the Orange class and all Orange class methods work on it as expected. But we also give the object a new, special attribute, origin = 'Florida'.
As we can see, florida_orng not only possesses all the attributes of the Orange class, it has its own attribute, origin, which is 'Florida' in this case. While it is not something you would ordinarily want to do, you should be aware that it is possible for an object to possess attributes beyond those normally defined for its class.
Help on a user-defined class
If we want to examine what’s inside of a user-defined class, we can use help() on it just as we would for any other class. For example, let’s look at help(Orange), for the class we just defined:
To view the whole list of attributes and methods attached to a particular object, you can use the dir() function, for example, dir(florida_orng).
Constructor
Unless otherwise specified as described below, a default method called the constructor is called every time we create an object of a given class. As we have seen, the default constructor is invoked simply by calling the name of the class with an empty argument list. A new object with default attributes is then returned.
It is possible to customize the class so the constructor returns objects in a specified initial state. This is done by defining the special method __init__()
, or the initializer for the constructor. Here is an example of its syntax:
Note, the “__” decoration consists of two underscores, not one “_”. Next, let’s look at how we might customize the initializer for the Orange class so the constructor takes an argument that is used in initializing the new instance. For example, what if we want to be able to instantiate an Orange object so that its shape is already 'squished'?
With this class definition, we can pass in an argument for the shape of the orange:
When flat_orng is instantiated, the constructor for the redefined Orange class recognizes that the shape attribute should be initialized to 'squished'. If no argument were given, then shape would be set to the default value, 'round'. Notice too that when an argument is present, the if-statement checks to see whether it is one of the recognized values of shape; if it isn’t, the default value is assigned instead.
Inheritance
What if we want to create a new type of object that is comprised of multiple Oranges? One possible approach is to define a MultiOrange class and have it inherit from the Orange class as defined above:
We can now construct a MultiOrange with the desired number of Oranges:
Here, when pair_orngs is instantiated, the constructor initializes the number attribute to 2; the default value of 1 is assumed if no argument is given. The initializer must also make sure that the initializer from Orange is called. (Python provides another means of achieving this called super.) When everything has been done properly, MultiOrange truly inherits from Orange: it has all the attributes and methods of the Orange class, though, possibly with modifications that are unique to MultiOrange:
We see that the MultiOrange class introduces two alterations to Orange: it extends the Orange class with a new attribute, number; and it overrides the eat() method, so that the number attribute and the screen output are dealt with appropriately. If you enter help(MultiOrange), you’ll see these relationships pretty clearly.
It is easy to think of other innovations that we might want to build into MultiOrange. For example, perhaps the eat() method should accept an integer argument, num_to_eat, to allow some of the oranges to be eaten while reducing the number accordingly. (Try it! But be careful: what happens if num_to_eat > number?)
Inheritance is a major feature of object-oriented languages, so we will have more to say about it in the next section.