Chapter 6. Classes and Objects

So far, you have been introduced to most of the building blocks of programming. You have used data; you have referenced that data to names (the names are more commonly called variables when programmers talk); and you have used that data in loops and functions. The use of these three elements is the foundation of programming and problem-solving with computers. Named variables enable you to store values, reference them, and manipulate them. Repeating loops enable you to evaluate every possible element in a list, or every other element, or every third element, and so on. Finally, functions enable you to combine bunches of code into a name that you can invoke whenever and wherever you need it.

In this chapter you learn:

  • How Python combines functions and data so that they are accessed using a single object's name.

  • How and why classes and objects are used and how they make programs easier to write and use in a variety of circumstances.

Thinking About Programming

At this point, you've only been given a rudimentary introduction to Python. To create a description of an object in Python right now, you have just enough knowledge to achieve two views. One is of the data, which comes and goes as needed, except for parts that live in the top level, or global scope. The other view is of functions, which have no persistent data of their own. They interact only with data that you give them.

What is an Object?

In Python, every piece of data you see or come into contact with is represented by an object. Each of these objects has three components: an identity, a type, and a value. The identity represents the location of the object being held in your memory (and therefore is unchangeable), while its type tells us what types of data and values it can have. The value, meanwhile, can be changed in an object, but only if it is set as a mutable type; if it is set as immutable, then it may not change.

A simpler explanation might be to consider some of the things we have already seen (and will see soon). Integers, strings, lists, and so forth are all nothing more than objects. Now, it is all well and good to have all of these floating around within your program, but wouldn't it make more sense to have the ones that work closely together in one spot? That is where classes come in. A class allows you to define and encapsulate a group of objects into one convenient space.

Objects You Already Know

The next tool you learn will enable you to think of entire objects that contain both data and functions. You've already seen these when you used strings. A string is not just the text that it contains. As you've learned, methods are associated with strings, which enable them to be more than just the text, offering such features as allowing you to make the entire string uppercase or lowercase. To recap what you've already learned, a string is mainly the text that you've input:

>>> omelet_type = "Cheese"

In addition to the data that you've worked with the most, the text "Cheese", the string is an object that has methods, or behaviors that are well known. Examples of methods that every string has are lower, which will return the string it contains as all lowercase, and upper, which will return the string as an entirely uppercase string:

>>> omelet_type.lower()
'cheese'
>>> omelet_type.upper()
'CHEESE'

Also available are methods built into tuple, list, and dictionary objects, like the keys method of dictionaries, which you've already used:

>>> fridge = {"cheese":1, "tomato":2, "milk":4}
>>> for x in fridge.keys():
        print(x)
 ['tomato', 'cheese', 'milk']

When you want to find out more about what is available in an object, Python exposes everything that exists in an object when you use the dir function:

dir(fridge)
['__class__', '__contains__', '__delattr__', '__delitem__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
'__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__',
'__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__'
'__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem',
'setdefault', 'update', 'values']

Every bit of data, every method, and, in short, every name in a string or any other object in Python can be exposed with the dir function. dir lists all of the available names in the object it is examining in alphabetical order, which tends to group those names beginning with underscores first. By convention, these names refer to items considered to be internal pieces of the object and should be treated as though they are invisible. In other words, you shouldn't use them, but Python leaves that decision up to you — there's no reason not to look at these items interactively to learn from them:

>>> type(omelet_type.__len__)
<class 'method-wrapper'

This is interesting. Because this is a method, it can be invoked to see what it does:

>>> omelet_type.__len__()
6

This returns the same value as the len built-in function. When a function is built into an object, it's called a method of that object.

In fact, the method __len__ is how the len function works: It asks an object how long it is by asking this built-in method. This enables the designer of an object to define how the length is determined and to have the built-in function len behave correctly for any object that defines a __len__ method.

The other names beginning with an underscore also have special meanings. You can explore these in the Python shell. The Python shell will help you explore the normal methods of a string object, or any other method, by displaying possible names within the object that you are trying to call on, but it will not display internal names that begin with an underscore. You can determine those with the dir function yourself if you decide to do this.

Looking Ahead: How You Want to Use Objects

When you have an object, you want to be able to use it naturally. For instance, once you've defined it, the Omelet class could produce objects that behave in a way that would feel natural when you read the source code. You're going to try to make something that can do this (you see how to do this in the next section):

>>> o1 = Omelet()
>>> o1.show_kind()
'cheese'

You'd also want to have a refrigerator that can be used as an object instead of just as a dictionary. It may be nice for you to do things like be able to think of using it like a real fridge, whereby you can add food, remove food, check for foods, add or remove more than one thing at a time, and so on.

In other words, when you create an object that models something from the real world, you can form your program's objects and classes so they help the pieces of the program work in a way that someone familiar with the real-life object will recognize and be able to understand.

Defining a Class

When you are considering how even small programs of a few hundred lines of Python code is working, you will often realize that the programs are keeping track of data in groups — when one thing is accessed, it affects other things that need to go along with it. Almost as often, you'll realize that you have whole lists of this interdependent data — lists in which the first element in list1 is matched to the first element in list2 and list3, and so on. Sometimes this can and should be solved by combining the lists creatively. Python employs the concept of creating an entire class of code that acts as a placeholder. When a class is invoked, it creates an object bound to a name.

How Code Can Be Made into an Object

After you have an object bound to a name, using that name provides you with access to all of the data and functions you've defined. When you are writing code for a class, you start by declaring that class. You do this with the class keyword.

You've now written a completely usable class for a refrigerator. Remember that there are many directions in which you can take this. Although you may be making omelets that use the Fridge class now, you can also use it for other projects — to model the product flow of a business, for example, such as a deli that has ten refrigerators with different products in each one.

When you do find an opportunity to repurpose a class that you've written (or a class that you've used), you can take advantage of the opportunity that is presented by adding features to support new needs without sacrificing what it already does.

For instance, an application that needs to take into account several refrigerators may result in a need for each Fridge object to have extra attributes, such as a name for it (like "dairy fridge"), its position in the store, its preferred temperature setting, and its dimensions. You can add these to the class, along with methods to get and set these values, while still keeping it completely usable for the omelet examples in this book. This is how interfaces help you. As long as the interfaces to the Fridge class you've already written here aren't changed, or at least as long as they behave the same, you can otherwise modify anything. This capability to keep interfaces behaving the same is called their stability.

Objects and Their Scope

As you saw in Chapter 5, functions create their own space, a scope, for the names that they use. While the function is being invoked, the name and value are present, and any changes made to the name persist for as long as the function is in use. However, after the function has finished running and is invoked again, any work that was done in any prior invocations is lost, and the function has to start again.

With objects, the values inside of them can be stored and attached to self on the inside of the object (self in this case is a name that refers to the object itself, and it's also the same as what is referenced by a name on the outside of the object, such as f). As long as the object is referenced by a name that is still active, all of the values contained in it will be available as well. If an object is created in a function and isn't returned by that function to be referenced to a name in a longer-lived scope, it will be available for as long as the single invocation of the function in which it was called, in the same way as any other data in the function.

Multiple objects are often created in tandem so that they can be used together. For instance, now that you've implemented all of the features you need to have a workable Fridge in your program, you need to have an Omelet object that works with it.

Take a moment to compare this to how you'd do the same thing using the functions from Chapter 5, and you'll realize why so much programming is done in this style — and why this kind of programming, called object-oriented programming, is used to make larger systems.

As long as the Fridge has the ingredients needed, making different kinds of omelets is very, very easy now — it involves only invoking the class to create a new object and then just calling three methods for each Omelet object. Of course, you could reduce it to one. That will be an exercise question.

Summary

In this chapter, you've been introduced to how Python provides you with the tools to program with classes and objects. These are the basic concepts behind what is called object-oriented programming.

When they are used inside a class, functions are referred to as methods because now every one has a special name called self that, when that method is invoked as part of an object, contains all of the data and methods of the object.

A class is invoked to create an object by using the class's name followed by parentheses, (). Initial parameters can be given at this time and whether or not parameters are given, the newly created object will invoke the method __init__. Like normal functions, methods in classes (including __init__) can accept parameters, including optional and default parameters.

The process of creating a class includes deciding what methods should be created to provide all of the functionality that you want in your class. Two general kinds of methods were described: public interfaces that should be invoked on the outside of the objects and private methods that should be called only by methods inside of the object. The interfaces should be made to change as little as possible, whereas the internal methods may change without affecting how the class can be used. This is especially important to remember when using a class written by someone else. Python expects any name within the scope of an object beginning with two underscores to be private, so this convention should be used by you as well. Other names are generally considered public.

The key points to take away from this chapter are:

  • To specify how you expect the class to be used you should create a docstring for the class by entering a string on the first line after the class's definition. In that docstring, it is best to always provide the names of the methods that you expect to be used, and their purpose. It's not a bad idea to include an explanation of the class as a whole, too.

  • All of the names that are defined in a class (both data and methods) are distinct in each object that is created. When a method is invoked in one object and that changes data in that object, other types of the same object are not affected. Examples of this that are built into Python are strings, which are objects that include special methods that help with common tasks when you are using text.

  • To make objects easier to use, it's common to provide multiple interfaces that behave similarly. This can save you a lot of work by finding ways for these interfaces to call a single internal method that is more complex or accepts more parameters than the interfaces. This gives you two distinct advantages. First, it makes the code that calls on these methods easier to read because the names of the parameters don't need to be remembered by the programmer — the name of the method provides needed information to the programmer. Second, if you need to change the internal method that its related interfaces call on, you can change how all of them behave by just changing the internal method. This is especially useful when fixing problems because a single fix will correct how all of the interfaces work as well. In addition, the method that provides this support to other methods can itself be a public interface. There's no strict rule about whether or not a hard-working method like this should be private and internal. It's really up to you.

  • One goal of writing objects is to duplicate as little code as possible, while providing as many features as possible. Creating a class that can use objects can save a lot of code writing because they are usually manipulated more conveniently than when functions and data are kept separated, because methods within the same class can count on the methods and data that they use being present. Groups of classes can be written so that they have interdependent behaviors, enabling you to model groups of things that work together. You learn how to structure these interdependent and cooperative classes in Chapter 7.

  • Last, you've seen how codeEditor's Python shell helps you explore your objects by showing you all of the interface names once you type a period. This is much easier than typing dir to get the same information because of the more convenient and easier-to-use manner in which codeEditor displays the information.

Exercises

Each of the following exercises builds on the exercises that preceded it:

  1. Add an option to the Omelet class's mix method to turn off the creation messages by adding a parameter that defaults to True, indicating that the "mixing ..." messages should be printed.

  2. Create a method in class Omelet that uses the new mix method from Exercise 1. Called quick_cook, it should take three parameters: the kind of omelet, the quantity wanted, and the Fridge that they'll come from. The quick_cook method should do everything required instead of requiring three method calls, but it should use all of the existing methods to accomplish this, including the modified mix method with the mix messages turned off.

  3. For each of the methods in the Omelet class that do not have a docstring, create one. In each docstring, make sure you include the name of the method, the parameters that the method takes, what the method does, what value or values it returns upon success, and what it returns when it encounters an error (or what exceptions it raises, if any).

  4. View the docstrings that you've created by creating an Omelet object.

  5. Create a Recipe class that can be called by the Omelet class to get ingredients. The Recipe class should have the ingredient lists of the same omelets that are already included in the Omelet class. You can include other foods if you like. The Recipe class should include methods to retrieve a recipe, get(recipe_name), a method to add a recipe as well as name it, and create (recipe_name, ingredients), where the ingredients are a dictionary with the same format as the one already used in the Fridge and Omelet classes.

  6. Alter the __init__ method of Omelet so that it accepts a Recipe class. To do this, you can do the following:

    1. Create a name, self.recipe, that each Omelet object will have.

    2. The only part of the Omelet class that stores recipes is the internal method __known_kinds. Alter __known_kinds to use the recipes by calling self.recipe.get() with the kind of omelet that's desired.

    3. Alter the set_new_kind method so that it places the new recipe into self.recipe and then calls set_kind to set the current omelet to the kind just added to the recipe.

    4. In addition, modify __known_kinds to use the recipe method's get method to find out the ingredients of an omelet.

  7. Try using all of the new classes and methods to determine whether you understand them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.106.233