Buy me a coffee »

Pythonic

So, as I mentioned before I’ll list some portions of Python code here, eventually describing the differences between earlier (2.7 and before) and newer (3 and above) versions of Pythons, have some GUI examples and such kind of work.

To not jump right into deep water I prepared a simple game “Guess the Number” in Python 2.7. This code is available here. The script should work with Python 2.7 (I tested it on my Raspberry PI having a Version ‘2.7.3rc2 (default, May  6 2012, 20:02:25) \n[GCC 4.6.3]’), but it could run with previous versions. If you are experiencing some problems feel free to bother me.

And as you can guess (no, not the number): it does not run with Python 3 (or it does not with my Python 3.3.1 (v3.3.1:d9893d13c628, Apr  6 2013, 20:30:21) [MSC v.1600 64 bit (AMD64)] on win32).

And why not? Because as many programming languages Python evolved too to a next level and the developers made this language more to look like common languages. The first thing you encounter is the behaviour of the “print” function: it works like a function so you have to enclose the output into brackets, or you’ll get an “invalid syntax” error message (or something similar) pointing to the first “print” statement in your source code.

So what I’m going to do is to take my 2.7 script and make it run under 3.3.1 — and after that I’ll try to run the same code with Python 2.7… Let’s get started.

After adding brackets to the “print” statements I considered to use much less printing. For the blank I added only the brackets:

print()

Ok, gave it a try… Nope, the following message occurs: “NameError: global name ‘raw_input’ is not defined”. So after a bit research I notice that “raw_input” has been removed and I should use “input” instead. No problem, it works the same than the raw version…

Well, that was it. With only 2 changes (if I do not count every “print” as a single one) I could convert my 2.7 code into a 3.3 one. Not bat, is is?

I uploaded the code here.

Then let’s jump with this code to the 2.7 interpreter…
Running the code goes smoother than expected. I get the credits, the help text — however between some lines there are empty brackets. This can be managed with the following code-snippet:

print("")

So the Interpreter recognizes that we do not want to print the brackets but to use the print function as a function printing an empty line. But this was only a beauty issue, we have more interesting treasures hiding under the surface. For example the input() method. This works quite different in 2.7 and below than in 3 and above. Give it a try and enter a simple numerical guess. The end of the exception is something like this:

AttributeError: 'int' object has no attribute 'lower'

OK. Then we can assume, that our guess was converted on-the-fly into an integer… Let’s restart the application and enter one of the commands:

AttributeError: 'function' object has no attribute 'lower'

This message comes if we enter either “about” or “help”. So the input() converts our input into functions if they are defined in the context. What about exit?

AttributeError: 'Quitter' object has no attribute 'lower'

So Python 2.7 seems to recognize this one too, so there we could experimenting with this hidden feature. What could we do to keep up the functionality in 3.3 but be able to execute our code with 2.7?

What about types? Python’s variables are dynamic but they have a type after each assignment. We could think about looking at the type of the input, and then decide what to do: if it is a string, let’s try the 3.3 version of our code, if it’s an int, resume the game logic, if it is a function, let’s call it. How about that?

We could build some if-elif-else statement chain to execute the code. We need a str, int and function so our code would look like this:

if input_type is str:
    if "exit" == user_input.lower().strip():
        return
    evaluate_input(user_input.lower().strip())
elif input_type is int:
    handle_guess(user_input)
elif input_type is function:
    ...

Now if we run the application with 3.3 we get the same game than before, at 2.7 with entering only guesses the game goes on and on without any exceptions. So, now try for the commands we implemented. Ok, we get the following message:

NameError: global name 'function' is not defined

As a good trial-and-error developer we try if the built-in type of a function is ‘func’:

NameError: global name 'func' is not defined

So this was not the best solution. Let’s think a bit and add the elif for out exit definition. It’s type was Quitter so add it to the code…

Nope, was no help. Trying as a string makes the things not better so we have to do a little trick (or at least I’m out of ideas how to achieve reflection in Python).

What I did, I defined a variable which has the type of a function object and I check the user_input against this variable. They are equal if the input is a function defined in the scope of the script (so Python built-in functions and user defined). And to get the exiting code working, I defined an exit() function which simply exits the application.

input_type = type(user_input)
function_type = type(help)
if input_type is str:
    if "exit" == user_input.lower().strip():
        return
    evaluate_input(user_input.lower().strip())
elif input_type is int:
    handle_guess(user_input)
elif input_type is function:
    user_input()

What you see in the highlighted line 10 is the reflection in Python. If you have a function type object in a variable you only have to append the brackets after it and python calls the function for you. If the function needs any arguments you have to provide them — there’s no magic in programming, only some traces of syntactic sugar 😉

One thing is left to implement: starting a new game. This is a hard piece, because if you enter more than one word as input, Python gives you an unpredictable error message:

SyntaxError: unexpected EOF while parsing

This is why everyone says you should not use input() prior Python 3. This is described in the documentation: Equivalent to eval(raw_input(prompt)). So Python tries to evaluate the input and then throws an error. To solve this need I simply modified the application. Added a new() function which reads another input from the console and let’s the user return to the game before calling the new method. Here I used the same technique for getting the input than in the main loop — if an exception occurs than nothing happens.

You can get the multi Python version script here.

Feel free to comment and share.

I promise I’ll use less quotations from Monty Python in my future posts for our sake.

About the author

GHajba

Senior developer, consultant, author, mentor, apprentice. I love to share my knowledge and insights what I achieve through my daily work which is not trivial -- at least not for me.

1 comment

Leave a comment: