Learning Python Application Development

5 (3 reviews total)
By Ninad Sathaye
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Developing Simple Applications

About 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.

Publication date:
September 2016
Publisher
Packt
Pages
454
ISBN
9781785889196

 

Chapter 1. Developing Simple Applications

Python is one of the most widely used dynamic programming languages. It supports a rich set of packages, GUI libraries, and web frameworks that enable you to build efficient cross-platform applications. It is an ideal language for rapid application development. Such fast-paced development often comes with its own baggage that could bring down the overall quality, performance, and extensibility of the code. This book will show you ways to handle such situations and help you develop better Python applications. The key concepts will be explained with the help of command-line applications, which will be progressively improved in subsequent chapters.

This chapter will be an introductory one. It will serve as a refresher to Python programming. That being said, it is expected you have some knowledge of Python language, as well as object-oriented programming (OOP) concepts.

Here is how this chapter is organized:

  • We will start with installation prerequisites and set up a proper environment for Python development.

  • To set the tone right for the rest of the book, the next section will be a brief introduction to the high fantasy theme of the book.

  • What follows next is our first program. It is a simple text-based fantasy game, presented as a Python script.

  • We will add some complexity to this game and develop an incremental version of the game using simple functions.

  • Moving ahead, we will add more features to the game and redesign the code by applying OOP concepts.

  • The last topic will briefly cover Abstract Base Classes (ABCs) in Python.

The code explanation will be a bit verbose. More experienced readers can breeze past the examples and go to the next chapter, but be sure to understand the theme of the book and review the code in the ch01_ex03.py file. In the next few chapters, you will learn techniques to progressively improve this code.

 

Important housekeeping notes


Before diving into the rest of the chapter, let's get some housekeeping out of the way. If you haven't already, you should read the Preface, which documents most of the following things:

  • Every chapter will have its own set of Python source files. Although we will talk through most of the code, you should keep the relevant files at hand.

  • The source code can be downloaded from the Packt Publishing website. Follow the instructions mentioned in the Preface.

  • The code illustrated in this book is compatible with Python version 3.5.1. The supporting code bundles also provide files compatible with version 2.7.9.

  • As noted before, it is assumed that you are familiar with basics of the Python language and know OOP concepts.

  • The book uses a fun, text-based game theme as a vehicle to explain various application development aspects. However, the book itself is not about developing game applications!

  • The solutions to the exercises (if any) are generally not provided.

  • The book provides several external links (URLs) for further reading. Over time, some of these links might end up being broken. If that ever happens, try searching the web with appropriate search terms.

 

Installation prerequisites


Let's make sure that we have installed the prerequisites. Here is a table that summarizes the basic tools we need for this chapter and beyond; more verbose installation instructions follow in the next section:

Tool

Notes

Python 3.5

The code illustrated in this book is compatible with version 3.5. See the next table for available Python distributions. Supporting code bundles also provide 2.7.9 compatible files.

pip (package manager for Python)

The pip is already available in the official distribution for versions 3.5 and 2.7.9.

IPython

Optional installation. IPython is an enhanced Python interpreter.

Integrated development environment (IDE)

Use the Python editor or any IDE of your choice. Some good IDEs are listed in a table later in this chapter.

In subsequent chapters, we will need to install some additional dependencies. The Python package manager (pip) will makes this a trivial task.

Tip

Have you already set up the required Python environment or know how to do it? Just skip the setup instructions that follow and move on to the The theme of the book section, where the real action begins!

Installing Python

There are two options to install Python. You can either use the official Python version or one of the freely available bundled distributions.

Option 1 – official distribution

For Linux or Mac users, Python is probably already installed on your system. If not, you can install it using the package manager of your operating system. Windows OS users can install Python 3.5 by downloading the Python installer from the official Python website:

During the installation process, just make sure to select the option that adds Python 3.5 to the system environment variable, PATH, as shown in the preceding screenshot. You can also visit the official Python website, https://www.python.org/downloads, to get the platform-specific distribution.

Option 2 – bundled distribution

Alternatively, there are several freely available Python distributions that bundle together useful Python packages, including pip and IPython. The following table summarizes some of the most popular Python distributions, including the official one:

Distribution

Supported platforms

Notes

Official Python distribution

https://www.python.org

Windows, Linux, Mac

  • Freely available

  • Versions 2.7.9 and 3.5 include pip by default

Anaconda

http://continuum.io

Windows, Linux, Mac

  • Freely available

  • Includes pip, IPython and Spyder IDE

  • Bundles packages primarily for science, math, engineering, and data analysis

Enthought Canopy Express

https://www.enthought.com/canopy-express/

Windows, Linux, Mac

  • Freely available

  • Includes pip and IPython

  • Integrates a Python code editor and application development platform

Python(x, y)

https://python-xy.github.io/

Windows

  • Freely available

  • Includes pip, IPython, and Spyder IDE

Python install location

Let's briefly talk about the path where Python is installed, and how to make sure python is available as a command in your terminal window. Of course, things will widely vary, depending on where you install it and which Python distribution you choose.

Tip

The official Python documentation page has comprehensive information on setting up the Python environment on different platforms. Here is a link, in case you need further help beyond what we have covered: https://docs.python.org/3/using/index.html.

Unix-like operating systems

On a Unix-like operating system such as Linux, the default location is typically /usr/bin/python or /usr/local/bin/python.

If you used your operating system's package manager to install Python, the command python or python3 should be available in the terminal window. If it isn't, you need to update the PATH system environment variable to include the directory path to the Python executable. For example, if you have a Bash shell, add the following to the .bashrc file in your user home directory:

export PATH=$PATH:/usr/bin/

Specify the actual path to your Python installation in place of /usr/bin.

Windows OS

On Windows OS, the default Python installation path is typically the following directory: C:\Users\name\AppData\Local\Programs\Python\Python35-32\python.exe. Replace name with your Windows username. Depending on your installer and system, the Python directory can also be Python35-64. As mentioned earlier, at the time of installation, you should select the option Add Python 3.5 to PATH to make sure python or python.exe are automatically recognized as commands. Alternatively, you can rerun the installer with just this option checked.

Verifying Python installation

Open a terminal window (or command prompt on Windows OS) and type the following command to verify the Python version. This command will work if Python is installed and is available as a command in the terminal window. Otherwise, specify the full path to the Python executable. For instance, on Linux you can specify it as /usr/bin/python, if Python is installed in /usr/bin:

$ python -V 

Tip

Note that the $ sign in the previous command line belongs to the terminal window and is not part of the command itself! Put another way, the actual command is just python -V. The $ or % sign in the terminal window is a prompt for a normal user on Linux. For a root (admin) user, the sign is #. Likewise, on Windows OS, the corresponding symbol is >. You will type the actual command after this symbol.

The following is just a sample output, if we run the preceding command:

[[email protected] ~]$ python -V
Python 3.5.1 :: Anaconda 4.0.0 (64-bit)

Installing pip

The pip is a software package manager that makes it trivial to install Python packages from the official third party software repository, PyPI. The pip is already installed for Python-2 version 2.7.9 or higher and Python-3 version 3.4 or higher. If you are using a different Python version, check out https://pip.pypa.io/en/stable/installing for the installation instructions.

On Linux OS, the default location for the pip is same as that of the Python executable. For example, if you have /usr/bin/python, then pip should be available as /usr/bin/pip. On Windows OS, the default pip.exe is typically the following: C:\Users\name\AppData\Local\Programs\Python\Python35-32\Scripts\pip.exe. As mentioned earlier, replace name with your Windows username. Depending on your installer and the system, the Python directory can also be Python35-64.

Installing IPython

This is an optional installation. IPython is an enhanced version of the Python interpreter. If it is not already bundled in your Python distribution, you can install it with:

$ pip install ipython 

After the installation, just type ipython in the terminal to start the IPython interactive shell. Here is a screenshot of the IPython shell using the Anaconda Python 3.5 distribution:

Tip

It is often very convenient to use the Jupyter Notebook to write and share interactive programs. It is a web application that enables an interactive environment for writing Python code alongside rich text, images, plots, and so on. For further details, check out the project homepage at http://jupyter.org/. The Jupyter Notebook can be installed with:

$ pip install "ipython[notebook]"

Choosing an IDE

Using an IDE for development is a matter of personal preference. Simply put, an IDE is a tool intended to accelerate application development. It enables developers to write efficient code quickly by integrating the most common tools they need. The Python installation comes with a program called IDLE. It is a basic IDE for Python, which should get you started. For advanced development, you can choose from a number of freely or commercially available tools. Any good Python IDE has the following minimum features:

  • A source code editor with code completion and syntax highlighting features

  • A code browser to browse through files, projects, functions, and classes

  • A debugger to interactively identify problems

  • A version control system integration such as Git

You can get started by trying out one of the freely available IDEs. Here is a partial list of popular IDEs. If you are just interested in a simple source code editor, you can check out https://wiki.python.org/moin/PythonEditors, for a list of available choices.

Python IDE

Notes

PyCharm Community Edition

https://www.jetbrains.com/pycharm

Has a free community edition. Excellent tool to begin Python development!

Wing IDE 101

http://wingware.com/downloads/wingide-101

Free for non-commercial purposes only. Commercial version available with additional features. Another excellent Python IDE.

Spyder https://pythonhosted.org/spyder

Freely available, open source. Also provided in bundled Python distributions such as Python(x,y) and Anaconda.

Eclipse PyDev

www.pydev.org

Freely available, open source.

Sublime Text 2 or Sublime Text 3 (beta)

http://www.sublimetext.com/2

Free for evaluation purposes only. Highly configurable IDE.

 

The theme of the book


Have you read high fantasy novels, such as The Lord of the Rings or The Hobbit by J. R. R. Tolkien? Or watched the films based on these novels? Well, here is a high fantasy, "Tolkienesque" themed book on Python application development.

Tip

To find out more about J.R.R. Tolkien's work, see https://en.wikipedia.org/wiki/J._R._R._Tolkien. The term high fantasy is often used to represent a fantasy theme set in an alternate fictional world. Check out https://en.wikipedia.org/wiki/High_fantasy for more information.

This book takes you to an imaginary world where you will develop a text game based on the aforementioned theme. Yes, you can continue being a developer even in this imaginary world! During the course of the book, you will be accompanied by many fictional characters. While you learn different aspects of Python development, these characters will talk to you, ask questions, request new features, and even fight with the enemy.

It should be noted that this book is not about developing game applications. It uses a simple text-based game just as a medium to learn various development aspects.

Tip

Off topic, if you are interested in playing a high fantasy theme game, there are quite a few to choose from. Among the open source ones, Battle for Wesnoth is one of the most highly rated, free, turn-based strategy games with a high fantasy theme. Check out https://www.wesnoth.org, for more details.

Meet the characters

Let's meet the imaginary characters who will accompany you in various chapters:

Sir Foo

A human knight who is portrayed as a grand knight guarding the southern plains. He is our main character and will be talking to us throughout the book.

Orc Rider

An Orc is a human-like imaginary creature. Here, it is portrayed as an enemy soldier. The Orc is seen riding a wild boar-like creature. You will see this creature in this chapter.

Elf Rider

An Elf is a supernatural mythical being. The Elf is mounted on an elvish horse. He is portrayed as a friendly. You will meet Mr. Elf in Chapter 6, Design Patterns.

Fairy

An intelligent fairy with an inherent capability for magic. She will use her magic just once while finding her enchanted locket in Chapter 7, Performance Identifying Bottlenecks, (See O(log n)). You will first meet her in Chapter 6, Design Patterns.

Dwarf

A Dwarf is a small human-like mythical being. He is portrayed as "The Great Dwarf" of the Foo mountains. He asks lots of questions. You will see him in the second half of the book, starting with Chapter 6, Design Patterns.

With this fun theme as a vehicle, let's start our journey with a simple command-line application. It will be a text-based game. The complexities added in subsequent chapters will challenge you with interesting problems. The book will show you how to gracefully handle such situations.

 

Simple script – Attack of the Orcs v0.0.1


We have the required tools and the environment set up. It is now time to write our first Python program. It will be a simple game of chance, developed as a command-line application. As we advance further, we will add more complexity to the game and learn new techniques to develop efficient applications. So, get ready for action!

The game – Attack of the Orcs v0.0.1

The war between humans and their arch enemies, the Orcs, was in the offing. A huge army of Orcs was heading toward the human establishments. They were virtually destroying everything in their way. The great kings of the human race joined hands to defeat their worst enemy for the great battle of their time. Men were summoned to join the rest of the army. Sir Foo, one of the brave knights guarding the southern plains, began a long journey toward the east, through an unknown dense forest. For two days and two nights, he moved cautiously through the thick woods. On his way, he spotted a small isolated settlement. Tired and hoping to replenish his food stock, he decided to take a detour. As he approached the village, he saw five huts. There was no one to be seen around. Hesitantly, he decided to enter a hut...

Problem statement

You are designing a simple game in which the player is required to choose a hut for Sir Foo. The huts are randomly occupied either by a friend or an enemy. It is also possible that some huts remain unoccupied. If the chosen one turns out to be an enemy hut, the player loses. In the other two scenarios, the player wins.

Pseudo code – version 0.0.1

Now that the goal is clear, open your favorite editor and note down the main steps. This is sometimes referred to as a pseudo code.

While the user wishes to keep playing the game:

  • Print the game mission

  • Create a huts list

  • Randomly place 'enemy' or 'friend' or 'unoccupied' in 5 huts

  • Prompt the player to select a hut number

  • if enemy: print "you lose"

  • else: print "you win"

As you will notice, the key piece of the code is to randomly occupy the five huts with either enemy or friend and keep the remaining ones unoccupied. How do we do this? Let's quickly work this out using the Python interpreter. If you have installed IPython, start the IPython interpreter. Otherwise, just use the default Python interpreter by typing the command python in a terminal window. First, we need a Python list to hold all the occupant types. Next, we will use the built-in random module and call random.choice to pick one element randomly from this list. This code is shown in the following screen capture:

Now, we just need to write the surrounding code. Let's review it next.

Reviewing the code

Download the source code, ch01_ex01.py, from the supplementary code bundle provided for this chapter. The file extension, .py, indicates that it is a Python file. Open it in a Python editor or an IDE of your choice. It is recommended that you keep this file handy while reading the following discussion. It is often easier to glance at the full code to understand it better. Observe the following code snippet. It is just a small portion of the code inside the if __name__ == '__main__' condition block in the aforementioned file.

Tip

If you have Python 2.7.9 installed, there is a separate Python 2.7.9 compatible source provided in the supporting code bundle.

Let's review the code snippet in the preceding screenshot:

  • The first two lines import two built-in modules to gain access to the functionality provided within these modules. The textwrap module essentially provides features to nicely format the messages printed on the command line.

  • The if condition block, if __name__ == '__main__', is invoked only when the file is run as a standalone script. In other words, the code inside this condition block won't be executed if you import this file in some other file.

  • Now, let's look at the code in this condition block. First, we will initialize a few variables. As demonstrated earlier, the list occupants stores the potential occupant types for the hut.

  • The last few lines are just to format the text printed in the terminal window. The dotted_line is a string that will show a 72-character long line with hyphen symbols.

  • The ASCII escape sequence is used to print the text in bold. The sequence "\033[1m" is to make bold text, and "\033[0m" is to go back to normal printing style.

The next few lines essentially print further information about the game in the console:

Let's have a look at the code from the preceding screenshot:

  • The variable msg is a very long string. This is where the textwrap module is used.

  • The textwrap.fill function wraps the message in such a way that each line is 72 characters long, as specified by the width in our code.

Now, let's review the following while loop.

Tip

For Python 2.7.9, the only change required in the first example is to replace all the calls to the built-in function input with raw_input:

# For Python 2.7 
user_choice = raw_input(msg)
  • This top-level loop gives the player an option to play the game again.

  • Using random.choice, we randomly pick an occupant from the list of occupants and add it to the huts list. This was illustrated earlier.

  • The built-in input function accepts a hut number of the user's choice as an integer. The idx variable stores a number.

Next, it reveals the occupants by printing related information. Finally, it determines the winner by checking the list item corresponding to the hut number. Note that the huts list index starts at 0. Therefore, to retrieve the list element for a given hut number, idx, we need to check the list index at idx-1.

Running Attack of the Orcs v0.0.1

Assuming you already have Python in your system environment variable, PATH (available as either python or python3), run the program from the command line as:

$ python ch01_ex01.py

That's all! Just play the game and try to save Sir Foo by choosing the right hut! The following snapshot of a Linux terminal window shows our game in action:

 

Using functions – Attack of the Orcs v0.0.5


In the last section, you wrote a quick set of instructions to create a nice little command-line game. You asked your friends to try it out and they kind of liked it (perhaps they were just trying to be nice!). You received the first feature request for the game.

"I think this game has good potential to grow. How about including combat in the next version of the game? When Sir Foo encounters an enemy, he should not just give up that easily. Fight with the enemy! Let the combat decide the winner. "-your friend

You liked the idea and decided to add this capability to the code in the next version. Additionally, you also want to make it more interactive.

The script you wrote for the first program was small. However, as we go on adding new features, it will soon become a maintenance headache. As a step further, we will wrap the existing code into small functions so that the code is easier to manage. In functional programming, the focus is typically on function arrangement and their composition. For example, you can build complicated logic using a simple set of reusable functions.

Revisiting the previous version

Before adding any new features, let's revisit the script that you wrote in the previous version (version 0.0.1). We will identify the blocks of code that can be wrapped into functions. Such code chunks are marked in the two code snippets that follow:

We will wrap most of the highlighted code into individual functions, as follows:

1:  show_theme_message 
2:  show_game_mission 
3:  occupy_huts 
4:  process_user_choice 
5:  reveal_occupants
6:  enter_hut

In addition to these six blocks of code, we can also create a few top-level functions to handle all this logic. In Python, the function is created using the def keyword, followed by the function name and arguments in parentheses. For example, the reveal_occupants function requires the information about the huts list. We also need to optionally pass the dotted_line string if we do not want to recreate it in the function. So, we will pass the hut number idx, the huts list, and the dotted_line string as function arguments. This function can be written as follows:

After this initial work, the original script can be rewritten as:

This is much easier to read now. What we just did is also referred to as refactoring; more on various refactoring techniques in a later chapter. It makes it easier to do changes to the individual methods. For example, if you want to customize the mission statement or scenario description, you do not need to open the main function, run_application. Similarly, occupy_huts can be expanded further without any clutter in the main code.

Tip

The initial refactored version of the code is not perfect. There is plenty of room for improvement. Can you reduce the burden of passing the dotted_line parameter or think of some other way to handle the printing of bold text?

Pseudo code with attack feature – Version 0.0.5

In the previous section, we wrapped the game logic into individual functions. This not only improved the code readability, but also made it easier to maintain. Let's move on and include the new attack() function in the game. The following steps show the logic of the game with the attack feature included.

While the user wishes to keep playing the game:

  • Print game mission

  • Create a huts list

  • Randomly place 'enemy', 'friend', or 'unoccupied' in 5 huts

  • Prompt the player to select a hut number

  • if the hut has an enemy, do the following:

    • while the user wishes to continue the attack, use the attack() method on the enemy

      After each attack, update and show the health of Sir Foo, and of the enemy too; if enemy health <= 0: print "You Win".

      But, if Sir Foo health <= 0: print "You Lose".

  • else (hut has a friend or is unoccupied) print "you win"

Initially, Sir Foo and the Orc will have full health. To quantify health, let's assign hit points to each of these characters (or the game units). So, when we say the character has full health, it means it has the maximum possible hit points. Depending on the character, the default number of hit points will vary. The following image shows Sir Foo and the Orc with the default number of hit points, indicated by the Health label:

The bar above the Health label in the image represents a health meter. Essentially, it keeps track of the hit points. In the discussion that follows, we will use the terms hit points and health meter interchangeably. During the combat, either the player or the enemy will get injured. For now, neglect the third possibility where both escape unhurt. An injury will reduce the number of available hit points for the injured unit. In the game, we will assume that in a single attack turn only one of the characters is hit. The following image will help you imagine one such attack turn:

Here, Sir Foo's health meter is shown as the maximum and the Orc has sustained injuries!

Hmm, the Orc thinks he can defeat Sir Foo! This is interesting. Let's develop the game first and then see who has a better chance of winning!

With this understanding of the problem, let's review the code that implements this feature.

Reviewing the code

Download the source file, ch01_ex02.py, from the chapter's code bundle and skim through the code. The key logic will be in the attack() function. We will also need a data structure to keep the health record of Sir Foo and the enemy. Let's start by introducing the following utility functions that take care of some print business:

Now, look at the main function, run_application, and the supporting function, reset_health_meter. In addition to introducing the dictionary health_meter, we have also encapsulated the game logic in play_game:

At the start of a new game, the values of the health_meter dictionary are set back to the initial ones by calling reset_health_meter:

Next, let's review the play_game function. If the hut has the enemy, the player will be asked if the attack should be continued (the start of the while loop). Based on the user input, the code calls the attack function or exits the current game:

The enemy is attacked repetitively using the interactive while loop, which accepts user input. Execution of the attack function may result in injury to Sir Foo, or the enemy, or both. It is also possible that no one gets hurt. For simplicity, we will only consider two possibilities: a single attack that will injure either the enemy or Sir Foo. In the previous section, we used the built-in random number generator to randomly determine the occupants of the huts. We can use the same technique to determine who gets hurt:

injured_unit = random.choice(['player', 'enemy'])

But hold on a minute. Sir Foo has something to say:

We should take into account the chance of an injury to the player and to the enemy. In the attack function shown next, we will assume that for about 60% of the time, the enemy will get hit and for the remaining 40%, it is Sir Foo who is on the receiving end.

The simplest way is to create a list with 10 elements. This list should have six entries of 'enemy' and four entries of 'player'. Then, let random.choice select an element from this list. You can always introduce a difficulty level in the game and change this distribution:

Once the injured_unit is selected randomly, the injury is determined by picking a random number between 10 and 15, inclusive. Here, we use the random.randint function. The final important step is to update the health_meter dictionary for the injured unit by reducing its number of hit points.

Running Attack of the Orcs v0.0.5

We have discussed the most important functions in this game. Review the other supporting functions from the downloaded file. The following screenshot shows the game in action:

 

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:

 

Abstract base classes in Python


In the previous section, we redesigned the code using the OOP approach. We also demonstrated the use of inheritance by defining a superclass GameUnit, and inheriting from it to create the Knight and OrcRider subclasses. As the last topic in this chapter, let's talk about using abstract base classes in Python.

Tip

This section is intended to provide a basic understanding of ABCs in Python. The discussion here is far from being comprehensive but will be just enough to implement an ABC in our application code. For further reading, check out the Python documentation at https://docs.python.org/3/library/abc.html.

If you are familiar with OOP languages such as Java or C++, you probably already know the concept of an ABC.

A base class is a parent class from which other classes can be derived. Similarly, you can have an abstract base class and create other classes that inherit this class. So, where is the difference? One of the major differences is that an ABC can't be instantiated. But that is not the only difference. An ABC forces the derived classes to implement specific methods defined within that class. This much knowledge about an ABC should be good enough to work through the examples in this book. For more details, see the aforementioned Python documentation link. Let's review a simple example that shows how to implement an abstract base class in Python and how it differs from an ordinary base class. The abc module provides the necessary infrastructure. The following code snippet compares the implementation of an ABC to an ordinary base class:

The class on the left, AbstractGameUnit, is the abstract base class, whereas the GameUnit class on the right is an ordinary base class. The three differences in the ABC implementation are marked with numbers, as shown in the preceding screenshot.

  • The argument metaclass=ABCMeta is used to define AbstractGameUnit as an ABC.

  • The ABCMeta is a metaclass to define the abstract base class. It is a broad discussion topic, but the simplified meaning of a metaclass is as follows: to create an object, we use a class. Likewise, imagine a metaclass as one used to create a class.

  • A Python decorator provides a simple way to dynamically alter the functionality of a method, a class, or a function. This is a special Python syntax that starts with an @ symbol followed by the decorator name. A decorator is placed directly above the method definition.

  • The @abstractmethod is a decorator that makes the method defined on the next line an abstract method.

  • The abstract method is the one that the ABC requires all the subclasses to implement. In this case, AbstractGameUnit requires its Knight subclass to implement the info() method. If the subclass does not implement this method, Python simply doesn't instantiate that subclass and will throw TypeError. You can try this by removing the Knight.info method and running the code.

  • There is no such restriction if the Knight class inherits from an ordinary base class, such as GameUnit.

Tip

The code illustrated here is for Python version 3.5. For version 2.7, the syntax is different. Refer to the ch01_ex03_AbstractBaseClass.py file in the Python2 directory of the supporting material for an equivalent example.

Exercise

In the ch01_ex03.py file, you will see some comments. These are intentionally kept to give you an opportunity to improve portions of the code. There is plenty of room for improvement in this code. See if you can rewrite portions of the code to make it more robust. If you prefer a well-defined problem, here is one:

The Knight and OrcRider classes inherit from the GameUnit superclass. This exercise is about converting GameUnit to AbstractGameUnit, an abstract base class. Here is a cheat sheet for you; the skeleton code shown in the following figure is with the Python 3.5 syntax.

Refer to the ch01_ex03_AbstractBaseClass.py file:

Tip

Note that for Python 2.7, there is a separate version of this code. Refer to the src_ch1_Python2 directory in the supporting code bundle.

 

Summary


In this chapter, we touched upon some introductory concepts in Python to develop a simple command-line application. We first equipped ourselves by setting up a Python development environment.

The first program we wrote was a simple Python script. We soon realized that a simple script would be hard to maintain if more features are added. As a next step, we did a bit of refactoring and wrapped the code inside functions. This improved the code readability and also made it easier to manage. The proposed introduction of more features to the application made us rethink the design. We learned how to transform the code into an object-oriented design and implemented a few of these new features.

And how can we forget Sir Foo! He will accompany us throughout this book.

Is the code developed free from bugs? You might have already noticed some problems while playing the game! In the next chapter, we will see how to make the application more robust by handling exceptions.

Very important note for e-book readers

The code illustrations that you see in this book are actually image files or code snapshots.

The rendering quality of these images will vary depending on your PDF reader's page display resolution and the zoom level.

If you have trouble clearly reading this code, you may try the following in your PDF or e-book reader:

  • Set the zoom level to 100%

  • Use the page display resolution of 96 pixels/inch or similar

If the problem still persists, you can try with a different resolution.

How do you set this resolution? It will depend on your e-book reader. For example, if you are using Adobe Reader, go to Edit | Preferences and then select Page Display from the left panel. You will see Resolution as an option in the right panel. Select 96 pixels/inch or similar and see if that helps render the images better.

About the Author

  • Ninad Sathaye

    Ninad Sathaye has spent several years of his professional career designing and developing performance-critical engineering applications written in a variety of languages, including Python and C++. He has worked as a software architect in the semiconductor industry, and more recently in the domain of Internet of Things. He holds a master's degree in mechanical engineering.

    Browse publications by this author

Latest Reviews

(3 reviews total)
Thank you for the great content!
This product is easy to read, has been provided in multiple packages (I use both pdf and kindle versions), and I am using it to learn Python.
Ottimo manuale, semplice e completo. Rispecchia alla perfezione quello che viene espresso nella presentazione.
Book Title
Unlock this full book FREE 10 day trial
Start Free Trial