Book Image

Learning Python Application Development

By : Ninad Sathaye
Book Image

Learning Python Application Development

By: Ninad Sathaye

Overview of this book

Python is one of the most widely used dynamic programming languages, supported by a rich set of libraries and frameworks that enable rapid development. But fast paced development often comes with its own baggage that could bring down the quality, performance, and extensibility of an application. This book will show you ways to handle such problems and write better Python applications. From the basics of simple command-line applications, develop your skills all the way to designing efficient and advanced Python apps. Guided by a light-hearted fantasy learning theme, overcome the real-world problems of complex Python development with practical solutions. Beginning with a focus on robustness, packaging, and releasing application code, you’ll move on to focus on improving application lifetime by making code extensible, reusable, and readable. Get to grips with Python refactoring, design patterns and best practices. Techniques to identify the bottlenecks and improve performance are covered in a series of chapters devoted to performance, before closing with a look at developing Python GUIs.
Table of Contents (18 chapters)
Learning Python Application Development
Credits
Disclaimers
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

Using OOP – Attack of the Orcs v1.0.0


The attack feature that you added in the previous game has made it a lot more interesting. You can see some friends coming back again and again to play the game. The new feature requests have started pouring in.

Here is a partial list of the requested features:

  1. New mission to acquire all the huts and defeat all the enemies. This also means the hut occupants should be revealed right at the beginning of the game.

  2. Ability to get healed in a friendly or unoccupied hut.

  3. Ability to abandon combat (or run away from the enemy). This is a strategic move to run away, get healed in a friendly hut, and resume combat.

  4. Introduce one or more horse riders to assist Sir Foo. They can take turns to acquire huts. Ideally, a user-configurable option.

  5. Ability to configure the maximum hit points for each enemy unit and each of the horse riders.

  6. Configurable total number of huts; for example, increase it to 10.

  7. Each hut can have either some gold or a weapon inside that Sir Foo and his friends can pick up.

  8. Have an elf rider join Sir Foo. His abilities give him a very high chance of winning with fewer attacks.

This is quite a long list. You are preparing a plan. Here is a partial list of things you will need to add to the existing code to implement some of these features:

  • Keeping track of the hit points of multiple enemy units occupying various huts

  • Maintaining the health record of Sir Foo and all accompanying horse riders

  • Monitoring how many huts are acquired by Sir Foo's army

  • Another dictionary or list to keep track of the gold in each hut, and another one for weapons; additionally, what if someone wants to put armor in the hut?

  • Not to forget, yet another list of dictionary for each unit that accepts any of these goodies

  • Ah! So they want an elf rider with its own traits and abilities...nice...thanks for the additional trouble!

That is already a long list. While you could still continue to use the functional programming approach, in such scenarios it will get tougher as the game evolves and new features get added.

Thankfully, object-oriented programming comes to the rescue. How about making Sir Foo an instance of a Knight class? With this, it should be easy to manage parameters relevant to Sir Foo. For example, an attribute, hitpoints, can be used to keep track of Sir Foo's health instead of using the health_meter dictionary in the earlier example. Similarly, the other attributes in the class can keep track of the amount of gold or weapons collected while acquiring the huts (another requested feature).

There is a lot more beyond this bookkeeping. The various methods of the class would enable a specific implementation of behaviors, such as attack, run, heal, and so on. The horse riders accompanying Sir Foo can also be instances of the class Knight. Alternatively, you can create a new class called HorseRider for all these units that accept commands from Sir Foo.

Prioritize the feature requests

For this new version, let's hand pick a few requested features from the earlier list. In fact, Sir Foo should be the one who makes this call:

As you wish, Sir Foo...we will only add the new heal feature in this version.

Problem statement

It is now time to clearly define the targets for this release. You are not just adding new features to your application, but also making some fundamental changes to the code to accommodate future requests.

In this version, the mission is to acquire all of the five huts. Here, you will implement a new heal feature to regain all the hit points for Sir Foo. You will also implement some strategic controls, such as running away from combat, getting healed in a friendly hut, and then returning rejuvenated to defeat the enemy.

Redesigning the code

We already discussed how creating a Knight class will help simplify the handling of data and all other things related to Sir Foo, be it the hit points or the way he attacks enemies.

What other classes can be carved out? How about having the enemy as an object? The enemy could occupy multiple huts. Remember that we need to defeat all the enemies. Imagine the following scenario: Sir Foo injures an enemy in hut number 2, thereby reducing its hit points. Then, he moves on to another hut occupied by another enemy. Now, we need to maintain two separate hit point counters for each of these enemy units.

In a future version, you can expect users to ask for different enemy types with the ability to attack or heal, just like how we have it for Sir Foo. So, at this point, it makes sense to have a separate class, instances of which represent the enemy units. We will name this class OrcRider. It will have similar attributes to the Knight class. However, for simplicity, we will not give the enemy capabilities such as healing, changing huts, and so on.

Sir Foo says he is delighted to read that the enemy has been denied some important capabilities. (But you can't see his happy face behind the helm.)

There is something else we should consider. So far, huts was just a simple Python list object holding information about the occupant types as strings.

Looking at the requested features list, we also need bookkeeping for the amount of gold and armor in the hut and to update its occupant, depending on the result of the fight. In a future version, you may also want to show some statistics, such as a historic record of the occupants, changes in the amount of gold, and so on. For all this and more, we will create a class, Hut.

Painting the big picture

Take a pen and paper and write down the important attributes we need for each class discussed so far. At this point, do not worry about classifying whether it is an instance variable or a class method that encapsulates instructions to perform specific tasks. Just write down what you think belongs to each class.

The following schematic shows a list of potential attributes for the Knight, Hut, and OrcRider classes. The attribute names in strikethrough text indicate the potential attributes that won't be implemented in this illustration. But, it is always good to think ahead and keep it at the back of your mind during the design phase of the application:

This is not a complete specification, but we have a good starting point now. When Sir Foo enters an enemy hut, we have a choice to call the attack method of the Knight class. As before, the attack method will randomly pick who gets injured and deduct the hit points for that character. In the Knight class, it is convenient to have a new attribute, enemy, that will represent the active opponent. In this example, enemy will be an instance of the OrcRider class.

Let's develop this design further. Did you notice that the Knight and OrcRider classes have several things in common? We will use the inheritance principle to create a superclass for these classes, and call it GameUnit. We will move the common code to the superclass, and let the subclasses override the things they want to implement differently. In the next section, we will represent these classes with a Unified Modeling Language (UML)-like diagram.

Pseudo UML representation

The following diagram will help develop a basic understanding of how the various components talk to each other:

The preceding diagram is similar to a UML representation. It helps create a visual representation of a software design. In this book, we will loosely follow the UML representations. Let's call the diagrams used here pseudo UML diagrams (or UML-like diagrams).

Understanding the pseudo UML diagram

An explanation is in order for the UML-like convention used here. We will represent each class in the schematics as a rounded rectangle. It shows the class name followed by its attributes. The plus sign (+) before the attribute indicates that it is public. A protected or private method is generally represented with a negative sign (-). All the attributes shown in this diagram are public attributes. So, optionally, you could add a plus sign next to each attribute. In later chapters, we will follow this convention. For ease of illustration, only a few relevant public attributes will be listed. Observe that we are using different types of connectors in this diagram:

  • The arrowhead with an empty triangle symbol represents inheritance; for example, the Knight class inherits from the GameUnit class

  • The arrowhead with a filled diamond symbol represents object composition, for example, a Hut instance has an object of the GameUnit class (or its subclasses)

  • The arrowhead with an empty diamond symbol represents object aggregation

Now, let's talk about the individual components of the diagram presented earlier.

The Knight and OrcRider classes inherit from GameUnit. The Knight class, in this case, will override default methods, such as attack, heal, and run_away. The OrcRider class will not have such overridden methods, as we will not give these capabilities to the enemy.

The Hut class will have an occupant. The occupant can either be an instance of the Knight or the OrcRider, or the None type if the hut is unoccupied. The filled diamond connector in the diagram indicates composition.

Tip

Object composition

It is an important OOP principle. It implies a has-a relationship. In this case, Hut contains, or is composed of, some other object that is to be used to perform specific tasks. Just say it out loud; a Hut has-a Knight, a Hut has-an OrcRider, and so on.

In addition to the four classes discussed, we will introduce another one to encapsulate the top-level code. Let's call it AttackOfTheOrcs. As there are five huts, a class method in AttackOfTheOrcs creates that number of Hut instances. This is object aggregation, shown by the empty diamond shaped arrow in the preceding diagram.

Have you noticed another has-a relationship in AttackOfTheOrcs? The player attribute in this class is an instance of the Knight class, but in the future, this could change. This relationship is indicated by the filled diamond-head connector joining the Knight and AttackOfTheOrcs boxes.

Reviewing the code

With this high-level understanding, let's begin developing the code. Download the Python source file, ch01_ex03.py. We will review only a few important methods in the code. Refer to this source file for the complete code.

Tip

The code for this example, ch01_ex03.py, is all squished inside a single file. Is it good practice? Certainly not! As we go along, you will learn about best practices. Later in the book, we will discuss some important building blocks of application development, namely refactoring, coding standards, and design patterns. As an exercise, try to split the code into smaller modules and add code documentation.

The main execution code is shown here, along with some details of the AttackOfTheOrcs class. In the __init__ method, we will initialize some instance variables and later update the values they hold. For example, self.player represents the instance of the Knight class when the game begins:

Tip

Just as a refresher, the __init__ method is somewhat similar to a constructor in languages such as C++; however, keep in mind some differences. For example, you cannot overload __init__ as you might do in these languages. Instead, you can easily accomplish this using optional arguments or the classmethod decorator. We will cover some aspects later in the book.

Let's quickly review the play and _occupy_huts methods:

The self.player is an instance of the Knight class. We will call the acquire_hut method of this instance where most of the high-level action happens. After this, the program simply looks for the health parameters of the player and the enemy. It also queries the Hut instance to see if it is acquired.

Moving ahead, in the _occupy_hut method, the objects of Hut are created and appended to the self.huts list. This method is shown in the following figure:

Note

Public, protected, and private in Python

You will notice that some methods of the AttackOfTheOrcs class start with an underscore, for example, _process_user_choice(). That is a way to say that this method is not meant for public use. It is intended to be used from within the class. Languages such as C++ define class access specifiers, namely, private, protected, and public. These are used to put restrictions on the access of class attributes.

There is no such thing in Python. It allows outside access to the attributes with a single underscore as game._process_user_choice(). If the attribute name starts with double underscores, you can't call it directly. For example, you can't directly call game.__process_user_choice(). That being said, there is another way to access such attributes from outside. But let's not talk about it. Although Python allows you to access such attributes, it is is not good practice to do so!

Observe the acquire_hut method of the Knight class:

Let's talk through this method next:

  • First, we need to check whether the hut's occupant is a friend or an enemy. This is determined by the variable is_enemy, as shown in the preceding figure.

  • The hut's occupant can be of the following types: an instance of the Knight class, an instance of the OrcRider class, or set to None.

  • The GameUnit class, and its subclasses Knight and OrcRider, define a unit_type attribute. This is just a string that is set as either 'friend' or 'enemy'.

  • Thus, to determine whether there is an enemy hiding in the hut, we will first check whether the hut.occupant is an instance of the superclass GameUnit. If true, we will know it has a unit_type parameter. So, we will check whether hut.occupant.unit_type is equal to 'enemy'. For the OrcRider class, unit_type is set to 'enemy' by default.

  • The rest of the logic is simple. If the occupant is an enemy, it asks the user what to do next: attack or run away.

  • The Knight.attack method is similar to the one discussed earlier. One change here is that we can access the health_meter attribute of the injured unit and update it.

  • If hut.occupant happens to be 'friend' or None, it calls hut.acquire().

What happens when the Hut.acquire() method is called? Here is the code snippet for the Hut class:

The acquire method simply updates the occupant attribute with the object passed as an argument to this method.

Running Attack of the Orcs v1.0.0

It's play time! We have reviewed the most important methods of the new classes. You can review the rest of the code from the ch01_ex03.py file, or better try to write these methods on your own. Run the application from the command line, like we did earlier. The following screenshot shows the game in action: