(Sponsors) Get started learning Python with free . Learn Data Science by completing interactive coding challenges and watching videos by expert instructors.
Updated on Jan 07, 2020
Creating object and classes
Python is an object-oriented language. In python everything is object i.e int, str, bool even modules, functions are also objects.
Object oriented programming use objects to create programs, and these objects stores data and behaviours.
Defining class
Class name in python is preceded with class keyword followed by a colon (:). Classes commonly contains data field to store the data and methods for defining behaviors. Also every class in python contains a special method called initializer (also commonly known as constructors), which get invoked automatically every time new object is created.
Let's see an example.
Here we have created a class called Person which contains one data field called name and method whoami().
What is self?
All methods in python including some special methods like initializer have first parameter self. This parameter refers to the object which invokes the method. When you create new object the self parameter in the __init__ method is automatically set to reference the object you have just created.
Creating object from class
Expected Output:
note:
When you call a method you don't need to pass anything to self parameter, python automatically does that for you behind the scenes.
You can also change the name data field.
Expected Output:
Although it is a bad practice to give access to your data fields outside the class. We will discuss how to prevent this next.
Hiding data fields
To hide data fields you need to define private data fields. In python you can create private data field using two leading underscores. You can also define a private method using two leading underscores.
Let's see an example
Expected Output:
Let's try to access __balance data field outside of class.
Expected Output:
AttributeError: 'BankAccount' object has no attribute '__balance'
As you can see, now the __balance field is not accessible outside the class.
In next chapter we will learn about .
Other Tutorials (Sponsors)
This site generously supported by . DataCamp offers online interactive for Data Science. Join over a million other learners and get started learning Python for data science today!
Python Objects & Classes
Creating object and classes #
Python is an object-oriented language. In python everything is object i.e int, str, bool even modules, functions are also objects.
Object oriented programming use objects to create programs, and these objects stores data and behaviours.
Classes and objects
Classes and Objects
Python is an object oriented programming language. Everything in Python is an object, with its properties and methods. A number, string, list, dictionary, tuple, set etc. used in a program is an object of a corresponding built-in class. We create class to create an object. A class is like an object constructor, or a "blueprint" for creating objects. We instantiate a class to create an object. The class defines attributes and the behavior of the object, while the object, on the other hand, represents the class.
Inheritance
8. Inheritance
In this chapter we look at a larger example using object oriented programming and learn about the very useful OOP feature of .
Classes
Python Classes and Interfaces
As an object-oriented programming language, Python supports a full range of features, such as inheritance, polymorphism, and encapsulation. Getting things done in Python often requires writing new classes and defining how they interact through their interfaces and hierarchies.
Python's classes and inheritance make it easy to express a program's intended behaviors with objects. They allow you to improve and expand functionality over time. They provide flexibility in an environment of changing requirements. Knowing how to use them well enables you to write maintainable code.
Defining class #
Class name in python is preceded with class keyword followed by a colon (:). Classes commonly contains data field to store the data and methods for defining behaviors. Also every class in python contains a special method called initializer (also commonly known as constructors), which get invoked automatically every time new object is created.
Let's see an example.
Here we have created a class called Person which contains one data field called name and method whoami().
What is self? #
All methods in python including some special methods like initializer have first parameter self. This parameter refers to the object which invokes the method. When you create new object the self parameter in the __init__ method is automatically set to reference the object you have just created.
Creating object from class #
Expected Output:
note:
When you call a method you don't need to pass anything to self parameter, python automatically does that for you behind the scenes.
You can also change the name data field.
Expected Output:
Although it is a bad practice to give access to your data fields outside the class. We will discuss how to prevent this next.
Hiding data fields #
To hide data fields you need to define private data fields. In python you can create private data field using two leading underscores. You can also define a private method using two leading underscores.
Let's see an example
Expected Output:
Let's try to access __balance data field outside of class.
Expected Output:
AttributeError: 'BankAccount' object has no attribute '__balance'
As you can see, now the __balance field is not accessible outside the class.
This site generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join over a million other learners and get started learning Python for data science today!
Item 37: Compose Classes Instead of Nesting Many Levels of Built-in Types
Python's built-in dictionary type is wonderful for maintaining dynamic internal state over the lifetime of an object. By dynamic, I mean situations in which you need to do bookkeeping for an unexpected set of identifiers. For example, say that I want to record the grades of a set of students whose names aren't known in advance. I can define a class to store the names in a dictionary instead of using a predefined attribute for each student:
Dictionaries and their related built-in types are so easy to use that there's a danger of overextending them to write brittle code. For example, say that I want to extend the SimpleGradebook class to keep a list of grades by subject, not just overall. I can do this by changing the _grades dictionary to map student names (its keys) to yet another dictionary (its values). The innermost dictionary will map subjects (its keys) to a list of grades (its values). Here, I do this by using a defaultdict instance for the inner dictionary to handle missing subjects (see Item 17: "Prefer defaultdict Over setdefault to Handle Miss ing Items in Internal State" for background):
We have been working with classes and objects right from the beginning of this challenge unknowingly. Every element in a Python program is an object of a class. Let us check if everything in python is a class:
Creating a Class
To create a class we need the key word class followed by the name and colon. Class name should be CamelCase.
Example:
Creating an Object
We can create an object by calling the class.
Class Constructor
In the examples above, we have created an object from the Person class. However, a class without a constructor is not really useful in real applications. Let us use constructor function to make our class more useful. Like the constructor function in Java or JavaScript, Python has also a built-in init() constructor function. The init constructor function has self parameter which is a reference to the current instance of the class Examples:
Let us add more parameters to the constructor function.
Object Methods
Objects can have methods. The methods are functions which belong to the object.
Example:
Object Default Methods
Sometimes, you may want to have a default values for your object methods. If we give default values for the parameters in the constructor, we can avoid errors when we call or instantiate our class without parameters. Let's see how it looks:
Example:
Method to Modify Class Default Values
In the example below, the person class, all the constructor parameters have default values. In addition to that, we have skills parameter, which we can access using a method. Let us create add_skill method to add skills to the skills list.
Inheritance
Using inheritance we can reuse parent class code. Inheritance allows us to define a class that inherits all the methods and properties from parent class. The parent class or super or base class is the class which gives all the methods and properties. Child class is the class that inherits from another or parent class. Let us create a student class by inheriting from person class.
We did not call the init() constructor in the child class. If we didn't call it then we can still access all the properties from the parent. But if we do call the constructor we can access the parent properties by calling super.
We can add a new method to the child or we can override the parent class methods by creating the same method name in the child class. When we add the init() function, the child class will no longer inherit the parent's init() function.
Overriding parent method
We can use super() built-in function or the parent name Person to automatically inherit the methods and properties from its parent. In the example above we override the parent method. The child method has a different feature, it can identify, if the gender is male or female and assign the proper pronoun(He/She).
🌕 Now, you are fully charged with a super power of programming. Now do some exercises for your brain and muscles.
💻 Exercises: Day 21
Exercises: Level 1
Python has the module called statistics and we can use this module to do all the statistical calculations. However, to learn how to make function and reuse function let us try to develop a program, which calculates the measure of central tendency of a sample (mean, median, mode) and measure of variability (range, variance, standard deviation). In addition to those measures, find the min, max, count, percentile, and frequency distribution of the sample. You can create a class called Statistics and create all the functions that do statistical calculations as methods for the Statistics class. Check the output below.
7. Classes and objects
7.1. Object-oriented programming
Python is an object-oriented programming language, which means that it provides features that support object-oriented programming ( OOP).
Object-oriented programming has its roots in the 1960s, but it wasn’t until the mid 1980s that it became the main programming paradigm used in the creation of new software. It was developed as a way to handle the rapidly increasing size and complexity of software systems, and to make it easier to modify these large and complex systems over time.
Up to now we have been writing programs using a procedural programming paradigm. In procedural programming the focus is on writing functions or procedures which operate on data. In object-oriented programming the focus is on the creation of objects which contain both data and functionality together.
7.2. User-defined compound types
We will now introduce a new Python keyword, class, which in essence defines a new data type. We have been using several of Python’s built-in types throughout this book, we are now ready to create our own user-defined type: the Point.
Consider the concept of a mathematical point. In two dimensions, a point is two numbers (coordinates) that are treated collectively as a single object. In mathematical notation, points are often written in parentheses with a comma separating the coordinates. For example, (0, 0) represents the origin, and (x, y) represents the point x units to the right and y units up from the origin.
A natural way to represent a point in Python is with two numeric values. The question, then, is how to group these two values into a compound object. The quick and dirty solution is to use a list or tuple, and for some applications that might be the best choice.
An alternative is to define a new user-defined compound type, called a class. This approach involves a bit more effort, but it has advantages that will be apparent soon.
A class definition looks like this:
Class definitions can appear anywhere in a program, but they are usually near the beginning (after the import statements). The syntax rules for a class definition are the same as for other compound statements. There is a header which begins with the keyword, class, followed by the name of the class, and ending with a colon.
This definition creates a new class called Point. The pass statement has no effect; it is only necessary because a compound statement must have something in its body. A docstring could serve the same purpose:
By creating the Point class, we created a new type, also called Point. The members of this type are called instances of the type or objects. Creating a new instance is called instantiation, and is accomplished by calling the class. Classes, like functions, are callable, and we instantiate a Point object by calling the Point class:
The variable p is assigned a reference to a new Point object.
It may be helpful to think of a class as a factory for making objects, so our Point class is a factory for making points. The class itself isn’t an instance of a point, but it contains the machinary to make point instances.
7.3. Attributes
Like real world objects, object instances have both form and function. The form consists of data elements contained within the instance.
We can add new data elements to an instance using dot notation:
This syntax is similar to the syntax for selecting a variable from a module, such as math.pi or string.uppercase. Both modules and instances create their own namespaces, and the syntax for accessing names contained in each, called attributes, is the same. In this case the attribute we are selecting is a data item from an instance.
The following state diagram shows the result of these assignments:
The variable p refers to a Point object, which contains two attributes. Each attribute refers to a number.
We can read the value of an attribute using the same syntax:
The expression p.x means, “Go to the object p refers to and get the value of x”. In this case, we assign that value to a variable named x. There is no conflict between the variable x and the attribute x. The purpose of dot notation is to identify which variable you are referring to unambiguously.
You can use dot notation as part of any expression, so the following statements are legal:
The first line outputs (3, 4); the second line calculates the value 25.
7.4. The initialization method and self
Since our Point class is intended to represent two dimensional mathematical points, all point instances ought to have x and y attributes, but that is not yet so with our Point objects.
To solve this problem we add an initialization method to our class.
A method behaves like a function but it is part of an object. Like a data attribute it is accessed using dot notation.
The initialization method is a special method that is invoked automatically when an object is created by calling the class. The name of this method is __init__ (two underscore characters, followed by init, and then two more underscores). This name must be used to make a method an initialization method in Python.
There is no conflict between the attribute self.x and the parameter x. Dot notation specifies which variable we are referring to.
Let’s add another method, distance_from_origin, to see better how methods work:
Let’s create a few point instances, look at their attributes, and call our new method on them:
When defining a method, the first parameter refers to the instance being created. It is customary to name this parameter self. In the example session above, the self parameter refers to the instances p, q, and r respectively.
7.5. Instances as parameters
You can pass an instance as a parameter to a function in the usual way. For example:
print_point takes a point as an argument and displays it in the standard format. If you call print_point(p) with point p as defined previously, the output is (3, 4).
To convert print_point to a method, do the following:
Indent the function definition so that it is inside the class definition.
Rename the parameter to self.
We can now invoke the method using dot notation.
The object on which the method is invoked is assigned to the first parameter, so in this case p is assigned to the parameter self. By convention, the first parameter of a method is called self. The reason for this is a little convoluted, but it is based on a useful metaphor.
The syntax for a function call, print_point(p), suggests that the function is the active agent. It says something like, Hey print_point! Here’s an object for you to print.
In object-oriented programming, the objects are the active agents. An invocation like p.print_point() says Hey p! Please print yourself!
This change in perspective might be more polite, but it is not obvious that it is useful. In the examples we have seen so far, it may not be. But sometimes shifting responsibility from the functions onto the objects makes it possible to write more versatile functions, and makes it easier to maintain and reuse code.
7.6. Object-oriented features
It is not easy to define object-oriented programming, but we have already seen some of its characteristics:
Programs are made up of class definitions which contain attributes that can be data (instance variables) or behaviors (methods).
Each object definition corresponds to some object or concept in the real world, and the functions that operate on that object correspond to the ways real-world objects interact.
Most of the computation is expressed in terms of operations on objects.
For example, the Point class corresponds to the mathematical concept of a point.
7.7. Time
As another example of a user-defined type, we’ll define a class called Time that records the time of day. Since times will need hours, minutes, and second attributes, we’ll start with an initialization method similar to the one we created for Points.
The class definition looks like this:
When we call the Time class, the arguments we provide are passed along to init:
Here is a print_time method for our Time objects that uses string formating to display minutes and seconds with two digits.
To save space, we will leave out the initialization method, but you should include it:
which we can now invoke on time instances in the usual way:
7.8. Optional arguments
We have seen built-in functions that take a variable number of arguments. For example, string.find can take two, three, or four arguments.
It is possible to write user-defined functions with optional argument lists. For example, we can upgrade our own version of find to do the same thing as string.find.
This is the original version:
This is the new and improved version:
The third parameter, start, is optional because a default value, 0, is provided. If we invoke find with only two arguments, we use the default value and start from the beginning of the string:
If we provide a third parameter, it overrides the default:
We can rewrite our initialization method for the Time class so that hours, minutes, and seconds are each optional arguments.
When we instantiate a Time object, we can pass in values for the three parameters, as we did with
Because the parameters are now optional, however, we can omit them:
Or provide only the first parameter:
Or the first two parameters:
Finally, we can provide a subset of the parameters by naming them explicitly:
7.9. Another method
Let’s add a method increment, which increments a time instance by a given number of seconds. To save space, we will continue to leave out previously defined methods, but you should always keep them in your version:
Now we can invoke increment on a time instance.
Again, the object on which the method is invoked gets assigned to the first parameter, self. The second parameter, seconds gets the value 125.
7.10. An example with two Times
Let’s add a boolen method, after, that takes two time instances and returns True when the first one is chronologically after the second.
We can only convert one of the parameters to self; the other we will call other, and it will have to be a parameter of the method.
We invoke this method on one object and pass the other as an argument:
You can almost read the invocation like English: If time1 is after time2, then…
7.10.1. Pure functions and modifiers (again)
In the next few sections, we’ll write two versions of a function called add_time, which calculates the sum of two Times. They will demonstrate two kinds of functions: pure functions and modifiers, which we first encountered in the Functions chapter.
The following is a rough version of add_time:
The function creates a new Time object, initializes its attributes, and returns a reference to the new object. This is called a pure function because it does not modify any of the objects passed to it as parameters and it has no side effects, such as displaying a value or getting user input.
Here is an example of how to use this function. We’ll create two Time objects: current_time, which contains the current time; and bread_time, which contains the amount of time it takes for a breadmaker to make bread. Then we’ll use add_time to figure out when the bread will be done. If you haven’t finished writing print_time yet, take a look ahead to Section before you try this:
The output of this program is 12:49:30, which is correct. On the other hand, there are cases where the result is not correct. Can you think of one?
The problem is that this function does not deal with cases where the number of seconds or minutes adds up to more than sixty. When that happens, we have to carry the extra seconds into the minutes column or the extra minutes into the hours column.
Here’s a second corrected version of the function:
Although this function is correct, it is starting to get big. Later we will suggest an alternative approach that yields shorter code.
7.10.2. Modifiers
There are times when it is useful for a function to modify one or more of the objects it gets as parameters. Usually, the caller keeps a reference to the objects it passes, so any changes the function makes are visible to the caller. Functions that work this way are called modifiers.
increment, which adds a given number of seconds to a Time object, would be written most naturally as a modifier. A rough draft of the function looks like this:
The first line performs the basic operation; the remainder deals with the special cases we saw before.
Is this function correct? What happens if the parameter seconds is much greater than sixty? In that case, it is not enough to carry once; we have to keep doing it until seconds is less than sixty. One solution is to replace the if statements with while statements:
This function is now correct, but it is not the most efficient solution.
7.11. Prototype development versus planning
So far in this chapter, we’ve used an approach to program development that we’ll call prototype development. We wrote a rough draft (or prototype) that performed the basic calculation and then tested it on a few cases, correcting flaws as we found them.
Although this approach can be effective, it can lead to code that is unnecessarily complicated – since it deals with many special cases – and unreliable – since it is hard to know if we’ve found all the errors.
An alternative is planned development, in which high-level insight into the problem can make the programming much easier. In this case, the insight is that a Time object is really a three-digit number in base 60! The second component is the ones column, the minute component is the sixties column, and the hour component is the thirty-six hundreds column.
When we wrote add_time and increment, we were effectively doing addition in base 60, which is why we had to carry from one column to the next.
This observation suggests another approach to the whole problem – we can convert a Time object into a single number and take advantage of the fact that the computer knows how to do arithmetic with numbers. The following function converts a Time object into an integer:
Now, all we need is a way to convert from an integer to a Time object:
You might have to think a bit to convince yourself that this technique to convert from one base to another is correct. Assuming you are convinced, you can use these functions to rewrite add_time:
This version is much shorter than the original, and it is much easier to demonstrate that it is correct (assuming, as usual, that the functions it calls are correct).
7.12. Generalization
In some ways, converting from base 60 to base 10 and back is harder than just dealing with times. Base conversion is more abstract; our intuition for dealing with times is better.
But if we have the insight to treat times as base 60 numbers and make the investment of writing the conversion functions (convert_to_seconds and make_time), we get a program that is shorter, easier to read and debug, and more reliable.
It is also easier to add features later. For example, imagine subtracting two Times to find the duration between them. The naive approach would be to implement subtraction with borrowing. Using the conversion functions would be easier and more likely to be correct.
Ironically, sometimes making a problem harder (or more general) makes it easier (because there are fewer special cases and fewer opportunities for error).
7.13. Algorithms
When you write a general solution for a class of problems, as opposed to a specific solution to a single problem, you have written an algorithm. We mentioned this word before but did not define it carefully. It is not easy to define, so we will try a couple of approaches.
First, consider something that is not an algorithm. When you learned to multiply single-digit numbers, you probably memorized the multiplication table. In effect, you memorized 100 specific solutions. That kind of knowledge is not algorithmic.
But if you were lazy, you probably cheated by learning a few tricks. For example, to find the product of n and 9, you can write n-1 as the first digit and 10-n as the second digit. This trick is a general solution for multiplying any single-digit number by 9. That’s an algorithm!
Similarly, the techniques you learned for addition with carrying, subtraction with borrowing, and long division are all algorithms. One of the characteristics of algorithms is that they do not require any intelligence to carry out. They are mechanical processes in which each step follows from the last according to a simple set of rules.
In my opinion, it is embarrassing that humans spend so much time in school learning to execute algorithms that, quite literally, require no intelligence.
On the other hand, the process of designing algorithms is interesting, intellectually challenging, and a central part of what we call programming.
Some of the things that people do naturally, without difficulty or conscious thought, are the hardest to express algorithmically. Understanding natural language is a good example. We all do it, but so far no one has been able to explain how we do it, at least not in the form of an algorithm.
7.14. Points revisited
Let’s rewrite the Point class in a more object- oriented style:
The next method, __str__, returns a string representation of a Point object. If a class provides a method named __str__, it overrides the default behavior of the Python built-in str function.
Printing a Point object implicitly invokes __str__ on the object, so defining __str__ also changes the behavior of print:
When we write a new class, we almost always start by writing __init__, which makes it easier to instantiate objects, and __str__, which is almost always useful for debugging.
7.15. Operator overloading
Some languages make it possible to change the definition of the built-in operators when they are applied to user-defined types. This feature is called operator overloading. It is especially useful when defining new mathematical types.
For example, to override the addition operator +, we provide a method named __add__:
As usual, the first parameter is the object on which the method is invoked. The second parameter is conveniently named other to distinguish it from self. To add two Points, we create and return a new Point that contains the sum of the x coordinates and the sum of the y coordinates.
Now, when we apply the + operator to Point objects, Python invokes __add__:
The expression p1 + p2 is equivalent to p1.__add__(p2), but obviously more elegant. As an exercise, add a method __sub__(self, other) that overloads the subtraction operator, and try it out. There are several ways to override the behavior of the multiplication operator: by defining a method named __mul__, or __rmul__, or both.
If the left operand of * is a Point, Python invokes __mul__, which assumes that the other operand is also a Point. It computes the dot product of the two points, defined according to the rules of linear algebra:
If the left operand of * is a primitive type and the right operand is a Point, Python invokes __rmul__, which performs scalar multiplication:
The result is a new Point whose coordinates are a multiple of the original coordinates. If other is a type that cannot be multiplied by a floating-point number, then __rmul__ will yield an error.
This example demonstrates both kinds of multiplication:
What happens if we try to evaluate p2 * 2? Since the first parameter is a Point, Python invokes __mul__ with 2 as the second argument. Inside __mul__, the program tries to access the x coordinate of other, which fails because an integer has no attributes:
Unfortunately, the error message is a bit opaque. This example demonstrates some of the difficulties of object-oriented programming. Sometimes it is hard enough just to figure out what code is running.
For a more complete example of operator overloading, see Appendix (reference overloading).
7.16. Polymorphism
Most of the methods we have written only work for a specific type. When you create a new object, you write methods that operate on that type.
But there are certain operations that you will want to apply to many types, such as the arithmetic operations in the previous sections. If many types support the same set of operations, you can write functions that work on any of those types.
For example, the multadd operation (which is common in linear algebra) takes three parameters; it multiplies the first two and then adds the third. We can write it in Python like this:
This method will work for any values of x and y that can be multiplied and for any value of z that can be added to the product.
We can invoke it with numeric values:
Or with Points:
In the first case, the Point is multiplied by a scalar and then added to another Point. In the second case, the dot product yields a numeric value, so the third parameter also has to be a numeric value.
A function like this that can take parameters with different types is called polymorphic.
As another example, consider the method front_and_back, which prints a list twice, forward and backward:
Because the reverse method is a modifier, we make a copy of the list before reversing it. That way, this method doesn’t modify the list it gets as a parameter.
Here’s an example that applies front_and_back to a list:
Of course, we intended to apply this function to lists, so it is not surprising that it works. What would be surprising is if we could apply it to a Point.
To determine whether a function can be applied to a new type, we apply the fundamental rule of polymorphism: If all of the operations inside the function can be applied to the type, the function can be applied to the type. The operations in the method include copy, reverse, and print.
copy works on any object, and we have already written a __str__ method for Points, so all we need is a reverse method in the Point class:
Then we can pass Points to front_and_back:
The best kind of polymorphism is the unintentional kind, where you discover that a function you have already written can be applied to a type for which you never planned.
7.17. Glossary
class
A user-defined compound type. A class can also be thought of as a template for the objects that are instances of it.instantiate
To create an instance of a class.instance
An object that belongs to a class.object
A compound data type that is often used to model a thing or concept in the real world.attribute
One of the named data items that makes up an instance.pure function
A function that does not modify any of the objects it receives as parameters. Most pure functions are fruitful.modifier
A function that changes one or more of the objects it receives as parameters. Most modifiers are void.functional programming style
A style of program design in which the majority of functions are pure.prototype development
A way of developing programs starting with a prototype and gradually testing and improving it.planned development
A way of developing programs that involves high-level insight into the problem and more planning than incremental development or prototype development.object-oriented language
A language that provides features, such as user-defined classes and inheritance, that facilitate object-oriented programming.object-oriented programming
A style of programming in which data and the operations that manipulate it are organized into classes and methods.method
A function that is defined inside a class definition and is invoked on instances of that class. :override:: To replace a default. Examples include replacing a default parameter with a particular argument and replacing a default method by providing a new method with the same name.initialization method
A special method that is invoked automatically when a new object is created and that initializes the object’s attributes.operator overloading
Extending built-in operators ( +, -, *, >, <, etc.) so that they work with user-defined types.dot product
An operation defined in linear algebra that multiplies two Points and yields a numeric value.scalar multiplication
An operation defined in linear algebra that multiplies each of the coordinates of a Point by a numeric value.polymorphic
A function that can operate on more than one type. If all the operations in a function can be applied to a type, then the function can be applied to a type.
8.1. Composition
By now, you have seen several examples of composition. One of the first examples was using a method invocation as part of an expression. Another example is the nested structure of statements; you can put an if statement within a while loop, within another if statement, and so on.
Having seen this pattern, and having learned about lists and objects, you should not be surprised to learn that you can create lists of objects. You can also create objects that contain lists (as attributes); you can create lists that contain lists; you can create objects that contain objects; and so on.
In this chapter we will look at some examples of these combinations, using Card objects as an example.
8.2. Card objects
If you are not familiar with common playing cards, now would be a good time to get a deck, or else this chapter might not make much sense. There are fifty-two cards in a deck, each of which belongs to one of four suits and one of thirteen ranks. The suits are Spades, Hearts, Diamonds, and Clubs (in descending order in bridge). The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, and King. Depending on the game that you are playing, the rank of Ace may be higher than King or lower than 2.
If we want to define a new object to represent a playing card, it is obvious what the attributes should be: rank and suit. It is not as obvious what type the attributes should be. One possibility is to use strings containing words like "Spade" for suits and "Queen" for ranks. One problem with this implementation is that it would not be easy to compare cards to see which had a higher rank or suit.
An alternative is to use integers to encode the ranks and suits. By encode, we do not mean what some people think, which is to encrypt or translate into a secret code. What a computer scientist means by encode is to define a mapping between a sequence of numbers and the items I want to represent. For example:
An obvious feature of this mapping is that the suits map to integers in order, so we can compare suits by comparing integers. The mapping for ranks is fairly obvious; each of the numerical ranks maps to the corresponding integer, and for face cards:
The reason we are using mathematical notation for these mappings is that they are not part of the Python program. They are part of the program design, but they never appear explicitly in the code. The class definition for the Card type looks like this:
As usual, we provide an initialization method that takes an optional parameter for each attribute.
To create an object that represents the 3 of Clubs, use this command:
The first argument, 0, represents the suit Clubs.
8.3. Class attributes and the __str__ method
In order to print Card objects in a way that people can easily read, we want to map the integer codes onto words. A natural way to do that is with lists of strings. We assign these lists to class attributes at the top of the class definition:
Class attributes like Card.SUITS and Card.RANKS are defined outside of any method, and can be accessed from any of the methods in the class.
Inside __str__, we can use SUITS and RANKS to map the numerical values of suit and rank to strings. For example, the expression Card.SUITS[self.suit] means use the attribute suit from the object self as an index into the class attribute named SUITS, and select the appropriate string.
The reason for the "narf" in the first element in ranks is to act as a place keeper for the zero-eth element of the list, which will never be used. The only valid ranks are 1 to 13. This wasted item is not entirely necessary. We could have started at 0, as usual, but it is less confusing to encode 2 as 2, 3 as 3, and so on.
We have a doctest in the __str__ method to confirm that Card(2, 11) will display as “Queen of Hearts”.
8.4. Comparing cards
For primitive types, there are conditional operators ( <, >, ==, etc.) that compare values and determine when one is greater than, less than, or equal to another. For user-defined types, we can override the behavior of the built-in operators by providing a method named __cmp__. By convention, __cmp__ takes two parameters, self and other, and returns 1 if the first object is greater, -1 if the second object is greater, and 0 if they are equal to each other.
Some types are completely ordered, which means that you can compare any two elements and tell which is bigger. For example, the integers and the floating-point numbers are completely ordered. Some sets are unordered, which means that there is no meaningful way to say that one element is bigger than another. For example, the fruits are unordered, which is why you cannot compare apples and oranges.
The set of playing cards is partially ordered, which means that sometimes you can compare cards and sometimes not. For example, you know that the 3 of Clubs is higher than the 2 of Clubs, and the 3 of Diamonds is higher than the 3 of Clubs. But which is better, the 3 of Clubs or the 2 of Diamonds? One has a higher rank, but the other has a higher suit.
In order to make cards comparable, you have to decide which is more important, rank or suit. To be honest, the choice is arbitrary. For the sake of choosing, we will say that suit is more important, because a new deck of cards comes sorted with all the Clubs together, followed by all the Diamonds, and so on.
With that decided, we can write __cmp__:
In this ordering, Aces appear lower than Deuces (2s).
8.5. Decks
Now that we have objects to represent Cards, the next logical step is to define a class to represent a Deck. Of course, a deck is made up of cards, so each Deck object will contain a list of cards as an attribute.
The following is a class definition for the Deck class. The initialization method creates the attribute cards and generates the standard set of fifty-two cards:
The easiest way to populate the deck is with a nested loop. The outer loop enumerates the suits from 0 to 3. The inner loop enumerates the ranks from 1 to 13. Since the outer loop iterates four times, and the inner loop iterates thirteen times, the total number of times the body is executed is fifty-two (thirteen times four). Each iteration creates a new instance of Card with the current suit and rank, and appends that card to the cards list.
The append method works on lists but not, of course, tuples.
8.6. Printing the deck
As usual, when we define a new type of object we want a method that prints the contents of an object. To print a Deck, we traverse the list and print each Card:
Here, and from now on, the ellipsis ( ...) indicates that we have omitted the other methods in the class.
As an alternative to print_deck, we could write a __str__ method for the Deck class. The advantage of __str__ is that it is more flexible. Rather than just printing the contents of the object, it generates a string representation that other parts of the program can manipulate before printing, or store for later use.
Here is a version of __str__ that returns a string representation of a Deck. To add a bit of pizzazz, it arranges the cards in a cascade where each card is indented one space more than the previous card:
This example demonstrates several features. First, instead of traversing self.cards and assigning each card to a variable, we are using i as a loop variable and an index into the list of cards.
Second, we are using the string multiplication operator to indent each card by one more space than the last. The expression " " * i yields a number of spaces equal to the current value of i.
Third, instead of using the print function to print the cards, we use the str function. Passing an object as an argument to str is equivalent to invoking the __str__ method on the object.
Finally, we are using the variable s as an accumulator. Initially, s is the empty string. Each time through the loop, a new string is generated and concatenated with the old value of s to get the new value. When the loop ends, s contains the complete string representation of the Deck, which looks like this:
And so on. Even though the result appears on 52 lines, it is one long string that contains newlines.
8.7. Shuffling the deck
If a deck is perfectly shuffled, then any card is equally likely to appear anywhere in the deck, and any location in the deck is equally likely to contain any card.
To shuffle the deck, we will use the randrange function from the random module. With two integer arguments, a and b, randrange chooses a random integer in the range a <= x < b. Since the upper bound is strictly less than b, we can use the length of a list as the second parameter, and we are guaranteed to get a legal index. For example, this expression chooses the index of a random card in a deck:
An easy way to shuffle the deck is by traversing the cards and swapping each card with a randomly chosen one. It is possible that the card will be swapped with itself, but that is fine. In fact, if we precluded that possibility, the order of the cards would be less than entirely random:
Rather than assume that there are fifty-two cards in the deck, we get the actual length of the list and store it in num_cards.
For each card in the deck, we choose a random card from among the cards that haven’t been shuffled yet. Then we swap the current card ( i) with the selected card ( j). To swap the cards we use a tuple assignment:
8.8. Removing and dealing cards
Another method that would be useful for the Deck class is remove, which takes a card as a parameter, removes it, and returns True if the card was in the deck and False otherwise:
The in operator returns True if the first operand is in the second, which must be a list or a tuple. If the first operand is an object, Python uses the object’s __cmp__ method to determine equality with items in the list. Since the __cmp__ in the Card class checks for deep equality, the remove method checks for deep equality.
To deal cards, we want to remove and return the top card. The list method pop provides a convenient way to do that:
Actually, pop removes the last card in the list, so we are in effect dealing from the bottom of the deck.
One more operation that we are likely to want is the boolean function is_empty, which returns true if the deck contains no cards:
8.9. Inheritance
The language feature most often associated with object-oriented programming is inheritance. Inheritance is the ability to define a new class that is a modified version of an existing class.
The primary advantage of this feature is that you can add new methods to a class without modifying the existing class. It is called inheritance because the new class inherits all of the methods of the existing class. Extending this metaphor, the existing class is sometimes called the parent class. The new class may be called the child class or sometimes subclass.
Inheritance is a powerful feature. Some programs that would be complicated without inheritance can be written concisely and simply with it. Also, inheritance can facilitate code reuse, since you can customize the behavior of parent classes without having to modify them. In some cases, the inheritance structure reflects the natural structure of the problem, which makes the program easier to understand.
On the other hand, inheritance can make programs difficult to read. When a method is invoked, it is sometimes not clear where to find its definition. The relevant code may be scattered among several modules. Also, many of the things that can be done using inheritance can be done as elegantly (or more so) without it. If the natural structure of the problem does not lend itself to inheritance, this style of programming can do more harm than good.
In this chapter we will demonstrate the use of inheritance as part of a program that plays the card game Old Maid. One of our goals is to write code that could be reused to implement other card games.
8.10. A hand of cards
For almost any card game, we need to represent a hand of cards. A hand is similar to a deck, of course. Both are made up of a set of cards, and both require operations like adding and removing cards. Also, we might like the ability to shuffle both decks and hands.
A hand is also different from a deck. Depending on the game being played, we might want to perform some operations on hands that don’t make sense for a deck. For example, in poker we might classify a hand (straight, flush, etc.) or compare it with another hand. In bridge, we might want to compute a score for a hand in order to make a bid.
This situation suggests the use of inheritance. If Hand is a subclass of Deck, it will have all the methods of Deck, and new methods can be added.
In the class definition, the name of the parent class appears in parentheses:
This statement indicates that the new Hand class inherits from the existing Deck class.
The Hand constructor initializes the attributes for the hand, which are name and cards. The string name identifies this hand, probably by the name of the player that holds it. The name is an optional parameter with the empty string as a default value. cards is the list of cards in the hand, initialized to the empty list:
For just about any card game, it is necessary to add and remove cards from the deck. Removing cards is already taken care of, since Hand inherits remove from Deck. But we have to write add:
Again, the ellipsis indicates that we have omitted other methods. The list append method adds the new card to the end of the list of cards.
8.11. Dealing cards
Now that we have a Hand class, we want to deal cards from the Deck into hands. It is not immediately obvious whether this method should go in the Hand class or in the Deck class, but since it operates on a single deck and (possibly) several hands, it is more natural to put it in Deck.
deal should be fairly general, since different games will have different requirements. We may want to deal out the entire deck at once or add one card to each hand.
deal takes two parameters, a list (or tuple) of hands and the total number of cards to deal. If there are not enough cards in the deck, the method deals out all of the cards and stops:
The second parameter, num_cards, is optional; the default is a large number, which effectively means that all of the cards in the deck will get dealt.
The loop variable i goes from 0 to nCards-1. Each time through the loop, a card is removed from the deck using the list method pop, which removes and returns the last item in the list.
The modulus operator ( %) allows us to deal cards in a round robin (one card at a time to each hand). When i is equal to the number of hands in the list, the expression i % nHands wraps around to the beginning of the list (index 0).
8.12. Printing a Hand
To print the contents of a hand, we can take advantage of the printDeck and __str__ methods inherited from Deck. For example:
It’s not a great hand, but it has the makings of a straight flush.
Although it is convenient to inherit the existing methods, there is additional information in a Hand object we might want to include when we print one. To do that, we can provide a __str__ method in the Hand class that overrides the one in the Deck class:
Initially, s is a string that identifies the hand. If the hand is empty, the program appends the words is empty and returns s.
Otherwise, the program appends the word contains and the string representation of the Deck, computed by invoking the __str__ method in the Deck class on self.
It may seem odd to send self, which refers to the current Hand, to a Deck method, until you remember that a Hand is a kind of Deck. Hand objects can do everything Deck objects can, so it is legal to send a Hand to a Deck method.
In general, it is always legal to use an instance of a subclass in place of an instance of a parent class.
8.13. The CardGame class
The CardGame class takes care of some basic chores common to all games, such as creating the deck and shuffling it:
This is the first case we have seen where the initialization method performs a significant computation, beyond initializing attributes.
To implement specific games, we can inherit from CardGame and add features for the new game. As an example, we’ll write a simulation of Old Maid.
The object of Old Maid is to get rid of cards in your hand. You do this by matching cards by rank and color. For example, the 4 of Clubs matches the 4 of Spades since both suits are black. The Jack of Hearts matches the Jack of Diamonds since both are red.
To begin the game, the Queen of Clubs is removed from the deck so that the Queen of Spades has no match. The fifty-one remaining cards are dealt to the players in a round robin. After the deal, all players match and discard as many cards as possible.
When no more matches can be made, play begins. In turn, each player picks a card (without looking) from the closest neighbor to the left who still has cards. If the chosen card matches a card in the player’s hand, the pair is removed. Otherwise, the card is added to the player’s hand. Eventually all possible matches are made, leaving only the Queen of Spades in the loser’s hand.
In our computer simulation of the game, the computer plays all hands. Unfortunately, some nuances of the real game are lost. In a real game, the player with the Old Maid goes to some effort to get their neighbor to pick that card, by displaying it a little more prominently, or perhaps failing to display it more prominently, or even failing to fail to display that card more prominently. The computer simply picks a neighbor’s card at random.
8.14. OldMaidHand class
A hand for playing Old Maid requires some abilities beyond the general abilities of a Hand. We will define a new class, OldMaidHand, that inherits from Hand and provides an additional method called remove_matches:
We start by making a copy of the list of cards, so that we can traverse the copy while removing cards from the original. Since self.cards is modified in the loop, we don’t want to use it to control the traversal. Python can get quite confused if it is traversing a list that is changing!
For each card in the hand, we figure out what the matching card is and go looking for it. The match card has the same rank and the other suit of the same color. The expression 3 - card.suit turns a Club (suit 0) into a Spade (suit 3) and a Diamond (suit 1) into a Heart (suit 2). You should satisfy yourself that the opposite operations also work. If the match card is also in the hand, both cards are removed.
The following example demonstrates how to use remove_matches:
Notice that there is no __init__ method for the OldMaidHand class. We inherit it from Hand.
8.15. OldMaidGame class
Now we can turn our attention to the game itself. OldMaidGame is a subclass of CardGame with a new method called play that takes a list of players as a parameter.
Since __init__ is inherited from CardGame, a new OldMaidGame object contains a new shuffled deck:
The writing of printHands() is left as an exercise.
Some of the steps of the game have been separated into methods. remove_all_matches traverses the list of hands and invokes remove_matches on each:
count is an accumulator that adds up the number of matches in each hand and returns the total.
When the total number of matches reaches twenty-five, fifty cards have been removed from the hands, which means that only one card is left and the game is over.
The variable turn keeps track of which player’s turn it is. It starts at 0 and increases by one each time; when it reaches numHands, the modulus operator wraps it back around to 0.
The method playOneTurn takes a parameter that indicates whose turn it is. The return value is the number of matches made during this turn:
If a player’s hand is empty, that player is out of the game, so he or she does nothing and returns 0.
Otherwise, a turn consists of finding the first player on the left that has cards, taking one card from the neighbor, and checking for matches. Before returning, the cards in the hand are shuffled so that the next player’s choice is random.
The method find_neighbor starts with the player to the immediate left and continues around the circle until it finds a player that still has cards:
If find_neighbor ever went all the way around the circle without finding cards, it would return None and cause an error elsewhere in the program. Fortunately, we can prove that that will never happen (as long as the end of the game is detected correctly).
We have omitted the print_hands method. You can write that one yourself.
The following output is from a truncated form of the game where only the top fifteen cards (tens and higher) were dealt to three players. With this small deck, play stops after seven matches instead of twenty-five.
So Jeff loses.
8.16. Glossary
encode
To represent one set of values using another set of values by constructing a mapping between them.class attribute
A variable that is defined inside a class definition but outside any method. Class attributes are accessible from any method in the class and are shared by all instances of the class.accumulator
A variable used in a loop to accumulate a series of values, such as by concatenating them onto a string or adding them to a running sum.inheritance
The ability to define a new class that is a modified version of a previously defined class.parent class
The class from which a child class inherits.child class
A new class created by inheriting from an existing class; also called a subclass.
"""
Abstract class is an extension of a basic class. Like a basic class, an
abstract class has methods and state. Unlike a basic class, it inherits
the `ABC` class and has at least one `abstractmethod`. That means we
cannot create an instance directly from its constructor. In this module,
we will create an abstract class and two concrete classes.
For more about abstract classes, click the link below:
https://www.python.org/dev/peps/pep-3119/
"""
from abc import ABC, abstractmethod
class Employee(ABC):
"""Abstract definition of an employee.
Any employee can work and relax. The way that one type of employee
can work and relax is different from another type of employee.
"""
def __init__(self, name, title):
self.name = name
self.title = title
def __str__(self):
return self.name
@abstractmethod
def do_work(self):
"""Do something for work."""
raise NotImplementedError
@abstractmethod
def do_relax(self):
"""Do something to relax."""
raise NotImplementedError
class Engineer(Employee):
"""Concrete definition of an engineer.
The Engineer class is concrete because it implements every
`abstractmethod` that was not implemented above.
Notice that we leverage the parent's constructor when creating
this object. We also define `do_refactor` for an engineer, which
is something that a manager prefers not to do.
"""
def __init__(self, name, title, skill):
super().__init__(name, title)
self.skill = skill
def do_work(self):
return f"{self} is coding in {self.skill}"
def do_relax(self):
return f"{self} is watching YouTube"
def do_refactor(self):
"""Do the hard work of refactoring code, unlike managers."""
return f"{self} is refactoring code"
class Manager(Employee):
"""Concrete definition of a manager.
The Manager class is concrete for the same reasons as the Engineer
class is concrete. Notice that a manager has direct reports and
has the responsibility of hiring people on the team, unlike an
engineer.
"""
def __init__(self, name, title, direct_reports):
super().__init__(name, title)
self.direct_reports = direct_reports
def do_work(self):
return f"{self} is meeting up with {len(self.direct_reports)} reports"
def do_relax(self):
return f"{self} is taking a trip to the Bahamas"
def do_hire(self):
"""Do the hard work of hiring employees, unlike engineers."""
return f"{self} is hiring employees"
def main():
# Declare two engineers
engineer_john = Engineer("John Doe", "Software Engineer", "Android")
engineer_jane = Engineer("Jane Doe", "Software Engineer", "iOS")
engineers = [engineer_john, engineer_jane]
# These engineers are employees but not managers
assert all(isinstance(engineer, Employee) for engineer in engineers)
assert all(not isinstance(engineer, Manager) for engineer in engineers)
# Engineers can work, relax and refactor
assert engineer_john.do_work() == "John Doe is coding in Android"
assert engineer_john.do_relax() == "John Doe is watching YouTube"
assert engineer_john.do_refactor() == "John Doe is refactoring code"
# Declare manager with engineers as direct reports
manager_max = Manager("Max Doe", "Engineering Manager", engineers)
# Managers are employees but not engineers
assert isinstance(manager_max, Employee)
assert not isinstance(manager_max, Engineer)
# Managers can work, relax and hire
assert manager_max.do_work() == "Max Doe is meeting up with 2 reports"
assert manager_max.do_relax() == "Max Doe is taking a trip to the Bahamas"
assert manager_max.do_hire() == "Max Doe is hiring employees"
if __name__ == "__main__":
main()
"""
A class is made up of methods and state. This allows code and data to be
combined as one logical entity. This module defines a basic car class,
creates a car instance and uses it for demonstration purposes.
"""
from inspect import isfunction, ismethod, signature
class Car:
"""Basic definition of a car.
We begin with a simple mental model of what a car is. That way, we
can start exploring the core concepts that are associated with a
class definition.
"""
def __init__(self, make, model, year, miles):
"""Constructor logic."""
self.make = make
self.model = model
self.year = year
self.miles = miles
def __repr__(self):
"""Formal representation for developers."""
return f"<Car make={self.make} model={self.model} year={self.year}>"
def __str__(self):
"""Informal representation for users."""
return f"{self.make} {self.model} ({self.year})"
def drive(self, rate_in_mph):
"""Drive car at a certain rate in MPH."""
return f"{self} is driving at {rate_in_mph} MPH"
def main():
# Create a car with the provided class constructor
car = Car("Bumble", "Bee", 2000, 200000.0)
# Formal representation is good for debugging issues
assert repr(car) == "<Car make=Bumble model=Bee year=2000>"
# Informal representation is good for user output
assert str(car) == "Bumble Bee (2000)"
# Call a method on the class constructor
assert car.drive(75) == "Bumble Bee (2000) is driving at 75 MPH"
# As a reminder: everything in Python is an object! And that applies
# to classes in the most interesting way - because they're not only
# subclasses of object - they are also instances of object. This
# means that we can modify the `Car` class at runtime, just like any
# other piece of data we define in Python
assert issubclass(Car, object) and isinstance(Car, object)
# To emphasize the idea that everything is an object, let's look at
# the `drive` method in more detail
driving = getattr(car, "drive")
# The variable method is the same as the instance method
assert driving == car.drive
# The variable method is bound to the instance
assert driving.__self__ == car
# That is why `driving` is considered a method and not a function
assert ismethod(driving) and not isfunction(driving)
# And there is only one parameter for `driving` because `__self__`
# binding is implicit
driving_params = signature(driving).parameters
assert len(driving_params) == 1
assert "rate_in_mph" in driving_params
if __name__ == "__main__":
main()
asabeneh@Asabeneh:~$ python
Python 3.9.6 (default, Jun 28 2021, 15:26:21)
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> num = 10
>>> type(num)
<class 'int'>
>>> string = 'string'
>>> type(string)
<class 'str'>
>>> boolean = True
>>> type(boolean)
<class 'bool'>
>>> lst = []
>>> type(lst)
<class 'list'>
>>> tpl = ()
>>> type(tpl)
<class 'tuple'>
>>> set1 = set()
>>> type(set1)
<class 'set'>
>>> dct = {}
>>> type(dct)
<class 'dict'>
# syntax
class ClassName:
code goes here
class Person:
pass
print(Person)
<__main__.Person object at 0x10804e510>
p = Person()
print(p)
class Person:
def __init__ (self, name):
# self allows to attach parameter to the class
self.name =name
p = Person('Asabeneh')
print(p.name)
print(p)
# output
Asabeneh
<__main__.Person object at 0x2abf46907e80>
class Person:
def __init__(self, firstname, lastname, age, country, city):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.country = country
self.city = city
p = Person('Asabeneh', 'Yetayeh', 250, 'Finland', 'Helsinki')
print(p.firstname)
print(p.lastname)
print(p.age)
print(p.country)
print(p.city)
# output
Asabeneh
Yetayeh
250
Finland
Helsinki
class Person:
def __init__(self, firstname, lastname, age, country, city):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.country = country
self.city = city
def person_info(self):
return f'{self.firstname} {self.lastname} is {self.age} years old. He lives in {self.city}, {self.country}'
p = Person('Asabeneh', 'Yetayeh', 250, 'Finland', 'Helsinki')
print(p.person_info())
# output
Asabeneh Yetayeh is 250 years old. He lives in Helsinki, Finland
class Person:
def __init__(self, firstname='Asabeneh', lastname='Yetayeh', age=250, country='Finland', city='Helsinki'):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.country = country
self.city = city
def person_info(self):
return f'{self.firstname} {self.lastname} is {self.age} years old. He lives in {self.city}, {self.country}.'
p1 = Person()
print(p1.person_info())
p2 = Person('John', 'Doe', 30, 'Nomanland', 'Noman city')
print(p2.person_info())
# output
Asabeneh Yetayeh is 250 years old. He lives in Helsinki, Finland.
John Doe is 30 years old. He lives in Noman city, Nomanland.
class Person:
def __init__(self, firstname='Asabeneh', lastname='Yetayeh', age=250, country='Finland', city='Helsinki'):
self.firstname = firstname
self.lastname = lastname
self.age = age
self.country = country
self.city = city
self.skills = []
def person_info(self):
return f'{self.firstname} {self.lastname} is {self.age} years old. He lives in {self.city}, {self.country}.'
def add_skill(self, skill):
self.skills.append(skill)
p1 = Person()
print(p1.person_info())
p1.add_skill('HTML')
p1.add_skill('CSS')
p1.add_skill('JavaScript')
p2 = Person('John', 'Doe', 30, 'Nomanland', 'Noman city')
print(p2.person_info())
print(p1.skills)
print(p2.skills)
# output
Asabeneh Yetayeh is 250 years old. He lives in Helsinki, Finland.
John Doe is 30 years old. He lives in Noman city, Nomanland.
['HTML', 'CSS', 'JavaScript']
[]
output
Eyob Yetayeh is 30 years old. He lives in Helsinki, Finland.
['JavaScript', 'React', 'Python']
Lidiya Teklemariam is 28 years old. He lives in Espoo, Finland.
['Organizing', 'Marketing', 'Digital Marketing']
Eyob Yetayeh is 30 years old. He lives in Helsinki, Finland.
['JavaScript', 'React', 'Python']
Lidiya Teklemariam is 28 years old. She lives in Espoo, Finland.
['Organizing', 'Marketing', 'Digital Marketing']
def __cmp__(self, other):
# check the suits
if self.suit > other.suit: return 1
if self.suit < other.suit: return -1
# suits are the same... check ranks
if self.rank > other.rank: return 1
if self.rank < other.rank: return -1
# ranks are the same... it's a tie
return 0
class Deck:
def __init__(self):
self.cards = []
for suit in range(4):
for rank in range(1, 14):
self.cards.append(Card(suit, rank))
class Deck:
...
def print_deck(self):
for card in self.cards:
print(card)
class Deck:
...
def __str__(self):
s = ""
for i in range(len(self.cards)):
s += " " * i + str(self.cards[i]) + "\n"
return s
>>> deck = Deck()
>>> print(deck)
Ace of Clubs
2 of Clubs
3 of Clubs
4 of Clubs
5 of Clubs
6 of Clubs
7 of Clubs
8 of Clubs
9 of Clubs
10 of Clubs
Jack of Clubs
Queen of Clubs
King of Clubs
Ace of Diamonds
random.randrange(0, len(self.cards))
class Deck:
...
def shuffle(self):
import random
num_cards = len(self.cards)
for i in range(num_cards):
j = random.randrange(i, num_cards)
self.cards[i], self.cards[j] = self.cards[j], self.cards[i]
class Deck:
...
def remove(self, card):
if card in self.cards:
self.cards.remove(card)
return True
else:
return False
class Deck:
...
def pop(self):
return self.cards.pop()
class Deck:
...
def is_empty(self):
return (len(self.cards) == 0)
class Hand(Deck):
pass
class Hand(Deck):
def __init__(self, name=""):
self.cards = []
self.name = name
class Hand(Deck):
...
def add(self,card):
self.cards.append(card)
class Deck :
...
def deal(self, hands, num_cards=999):
num_hands = len(hands)
for i in range(num_cards):
if self.is_empty(): break # break if out of cards
card = self.pop() # take the top card
hand = hands[i % num_hands] # whose turn is next?
hand.add(card) # add the card to the hand
>>> deck = Deck()
>>> deck.shuffle()
>>> hand = Hand("frank")
>>> deck.deal([hand], 5)
>>> print(hand)
Hand frank contains
2 of Spades
3 of Spades
4 of Spades
Ace of Hearts
9 of Clubs
class Hand(Deck)
...
def __str__(self):
s = "Hand " + self.name
if self.is_empty():
s = s + " is empty\n"
else:
s = s + " contains\n"
return s + Deck.__str__(self)
class CardGame:
def __init__(self):
self.deck = Deck()
self.deck.shuffle()
class OldMaidHand(Hand):
def remove_matches(self):
count = 0
original_cards = self.cards[:]
for card in original_cards:
match = Card(3 - card.suit, card.rank)
if match in self.cards:
self.cards.remove(card)
self.cards.remove(match)
print("Hand {0}: {1} matches {2}".format(self.name, card, match)
count = count + 1
return count
>>> game = CardGame()
>>> hand = OldMaidHand("frank")
>>> game.deck.deal([hand], 13)
>>> print(hand)
Hand frank contains
Ace of Spades
2 of Diamonds
7 of Spades
8 of Clubs
6 of Hearts
8 of Spades
7 of Clubs
Queen of Clubs
7 of Diamonds
5 of Clubs
Jack of Diamonds
10 of Diamonds
10 of Hearts
>>> hand.remove_matches()
Hand frank: 7 of Spades matches 7 of Clubs
Hand frank: 8 of Spades matches 8 of Clubs
Hand frank: 10 of Diamonds matches 10 of Hearts
>>> print(hand)
Hand frank contains
Ace of Spades
2 of Diamonds
6 of Hearts
Queen of Clubs
7 of Diamonds
5 of Clubs
Jack of Diamonds
class OldMaidGame(CardGame):
def play(self, names):
# remove Queen of Clubs
self.deck.remove(Card(0,12))
# make a hand for each player
self.hands = []
for name in names:
self.hands.append(OldMaidHand(name))
# deal the cards
self.deck.deal(self.hands)
print("---------- Cards have been dealt")
self.printHands()
# remove initial matches
matches = self.removeAllMatches()
print("---------- Matches discarded, play begins")
self.printHands()
# play until all 50 cards are matched
turn = 0
numHands = len(self.hands)
while matches < 25:
matches = matches + self.playOneTurn(turn)
turn = (turn + 1) % numHands
print("---------- Game is Over")
self.printHands()
class OldMaidGame(CardGame):
...
def remove_all_matches(self):
count = 0
for hand in self.hands:
count = count + hand.remove_matches()
return count
class OldMaidGame(CardGame):
...
def find_neighbor(self, i):
numHands = len(self.hands)
for next in range(1,numHands):
neighbor = (i + next) % numHands
if not self.hands[neighbor].is_empty():
return neighbor
>>> import cards
>>> game = cards.OldMaidGame()
>>> game.play(["Allen","Jeff","Chris"])
---------- Cards have been dealt
Hand Allen contains
King of Hearts
Jack of Clubs
Queen of Spades
King of Spades
10 of Diamonds
Hand Jeff contains
Queen of Hearts
Jack of Spades
Jack of Hearts
King of Diamonds
Queen of Diamonds
Hand Chris contains
Jack of Diamonds
King of Clubs
10 of Spades
10 of Hearts
10 of Clubs
Hand Jeff: Queen of Hearts matches Queen of Diamonds
Hand Chris: 10 of Spades matches 10 of Clubs
---------- Matches discarded, play begins
Hand Allen contains
King of Hearts
Jack of Clubs
Queen of Spades
King of Spades
10 of Diamonds
Hand Jeff contains
Jack of Spades
Jack of Hearts
King of Diamonds
Hand Chris contains
Jack of Diamonds
King of Clubs
10 of Hearts
Hand Allen picked King of Diamonds
Hand Allen: King of Hearts matches King of Diamonds
Hand Jeff picked 10 of Hearts
Hand Chris picked Jack of Clubs
Hand Allen picked Jack of Hearts
Hand Jeff picked Jack of Diamonds
Hand Chris picked Queen of Spades
Hand Allen picked Jack of Diamonds
Hand Allen: Jack of Hearts matches Jack of Diamonds
Hand Jeff picked King of Clubs
Hand Chris picked King of Spades
Hand Allen picked 10 of Hearts
Hand Allen: 10 of Diamonds matches 10 of Hearts
Hand Jeff picked Queen of Spades
Hand Chris picked Jack of Spades
Hand Chris: Jack of Clubs matches Jack of Spades
Hand Jeff picked King of Spades
Hand Jeff: King of Clubs matches King of Spades
---------- Game is Over
Hand Allen is empty
Hand Jeff contains
Queen of Spades
Hand Chris is empty