You are hereBlogs / marv's blog / Python Coolness Series, Part 1
Python Coolness Series, Part 1
Hello fellow coders! Today, I present you the Python Coolness Series, which will (hopefully) show how you can use Python to make cool apps and tools, that would otherwise take days or weeks to create. We will make use of many cool high-level Python APIs, that wrap complex code, and allow you to focus on the aspects that interest you most. So, you won't be seeing any Hello-Worlds in this series, but actually some really cool tools that you can show off to your friends. Take note, that some of these tools will be Mac OS X only. I also recommend all Mac users, who would like to follow this series, to grab a copy of Fink, so that they can install Python modules painlessly.
As I promised, this series will show you how to make some cool apps with Python. Well, that is, except for the first two parts. Today I want to introduce those coders to Python, that are not familiar with the language yet. This is not a complete tutorial on Python, and it does require that you are fluent in programming in general, but it should give you a fundamental understanding of how Python's inner machinery works. In the second part we will then focus on some cool tricks that can be done in Python.
However, today, we will stay on a more technical, lower level. This may not be everybody's taste, but I feel that for programmers of other languages, this gives an idea of what Python really is all about, and where its strengths lie.
First and foremost, Python follows a very simple principle: Everything is an object. Okay, so a bunch of languages follow that principle. But in Python we can be even more precise: Everything is a Dictionary (in Java terms), or a map (in C++ terms). As Python has a dictionary class as well, we shall call this universal type: map. So, to prove that this is true. Let's try the following. Start python (usually by typing python in the terminal), and enter the following:
x = -42 dir(x)
The dir() function is like the old DOS dir command (== UNIX ls): It allows you to see the contents of a directory, or in this case, of an object. As you see, a bunch of strange "__x__" strings are printed. The leading and trailing underscores are a naming convention of python: They signify members with special meaning. Let's find out more about one of the members. Enter
x.__abs__ > <method-wrapper '__abs__' of int object at 0x803570>
(The leading > means, that this line is a result returned by the Python interpreter). This tells us that __abs__ is a method-wrapper of int. Okay, so since it looks like __abs__ is a method, let's call it:
x.__abs__() > 42
So it looks like the __abs__ method returns the absolute value of x. Of course, this seems rather inconvenient. Well, remember that the underscores signify special meaning: In this case, __abs__ is actually called by the abs function. So, instead of the above, we could just write:
abs(x) > 42
Similarly, when we write x+2, the __add__ method is called. When we simply type x and hit enter, the __repr__ method is called, which returns a string representation of the object (which is then printed to the terminal). Essentially, all these methods together fully define the behavior and traits of an integer object.
A word on types. Unlike some may have you believe, Python is a typed language. To see this, we can enter
# One way... x.__class__ # ...or another type(x)
This will show you that x is an int type. If you want to know the type of int, we can enter type(int) to learn that the int type is of the type type. You see, Python is very consequent.
So why does it seem that Python is type-less? Well, because you do not explicitly set the type of an object. A type is part of the object, and is so implicitly set. However, the object IS typed! To see this enter
"The temperature is " + xA TypeError will scold you for being so naive! We will do some more string processing later.
Now, let's get back to object fundamentals. If we simply call dir(), we get a list of all objects in the root object, which is in this case an object called __main__. Notice, that our x is listed here. We would now like to define a callable object. If we attempt to call x by entering x(), we will get an error. One type of callable object, is a function. Let's define one:
def square(x): "Returns x squared." return x ** 2
Warning: In Python, tabs are part of the syntax, so make sure to indent the code exactly as in the example. Here, we can see a few cool things about Python. One of them are the doc-strings. If an orphan string value is found at the beginning of a function, it is assigned to the __doc__ attribute. This special attribute is used by the help() function. So, to learn more about a function, we can type:
help(square)
Also, this example shows some of the advanced operators of Python, such as the power operator **. Granted, this makes our square function look a bit obsolete, but we're just going to be geeky about it, and leave it at that.
If we enter dir() now, we see that square is in the global scope. Furthermore, if we enter callable(square), we learn that our object is callable. And, type(square) shows us, that this is indeed a function object. So let's call it:
square(5) > 25
Wow. Now let's get to some object-oriented goodness. Let's define a class:
class Person(object): def __init__(self, name="anonymous", age=0): self.name = name self.age = age def info(self): return "[Person named %s, %d years old]" % (self.name, self.age) species = "Human"
So, let's try to go through what we did here. First we defined a class Person, a subclass of object. We then defined an __init__ method with 3 arguments. When an instance method is called, it always receives a reference to the instance in the first argument. By convention, this argument is called self. We then specify two arguments name and age, that have default values, making them optional. Inside the method, we simply assign the arguments to our member variables. These are created on-the-fly just like our x was created when we assigned a value to it in the first example. Notice that we used the special underscores in the __init__ method name. That is because, when we instantiate a Person object, this method will be automatically called.
Let's check whether Person is callable, by entering callable(Person). Indeed it is. So what happens when we call this class? You guessed it: An instance of Person is created. All members of the Person class are mirrored in the instance marv, and the __init__ method is called with the arguments passed to the class instantiation method. Let's try it!
marv = Person("marv", 28) marv > <__main__.Person object at 0x30b0f0>
You can compare the members of the Person class and the marv instance, using dir(). Notice how both have the member methods, and the attribute species. However, the instance additionally has the name and age attributes. Proof that the __init__ method was called! We can access the attributes and methods the same way we did before:
marv.age > 28 marv.species == Person.species > True
To see the relationship between objects and classes, you can try the following experiment:
# Give Person class a new attribute Person.planet = "Earth" # Get its value from the instance marv.planet
If you are using a recent version of Python, marv should have the attribute planet as well. This allows us to modify the class, even after objects have been instantiated!
For instance, one annoying thing is that if we simply type in marv, and hit enter, we get a cryptic explanation of the object, and we learn nothing about its contents. Let's modify our class to do better. If we look at the info() method more closely, we see how using the % operator, strings can be formatted in a printf style. The thing you see behind the percent symbol, is called a tuple. You can create tuples using parantheses:
t = (1, 2, "three") type(t) > <type 'tuple'> t[2] > "three"
In the info() method, the tuple contains all the relevant values to format the string. Let's say we want the result of this method to be shown on the console, rather than the cryptic one right now. Well, recall that this is exactly what the __repr__ method does. So, to replace the current implementation with the new one, we can simply do the following:
Person.__repr__ = Person.info
Now, when we enter marv a more useful explanation is shown in the terminal. If you are using an older version of Python, you may have to manually change the __repr__ method in the instance as well.
Finally, let's take a brief look at modules. Modules are what headers are to C/C++. In Python they are like big maps, filled with lots of functions, classes, and other objects. To import a module into your current working environment, use the import statement:
import os # Lets see what is in the __main__ object now... dir() > ['Person', '__builtins__', '__doc__', '__name__', 'marv', 'os', 'square', 't', 'x']
As you can see, the os module is listed. Objects of type module are just big maps that can contain anything you like. Again, you could use the dir() command to see what is inside a particular module. As a quick example, let's make use of the path module inside the os module.
if os.path.exists("/System/Library"): print "Aha! It looks like you are on a Mac!" else: print "No Apples here."
If you import a module more than once in a program, then only new references are added to the module object. This is important for large modules, that would hog up memory, if every import would cause an entire copy of the module to reside in memory. Furthermore, you can import only parts of the module if you like. For instance, if we are only interested in the os.path.exists function, we can import it alone, by
from os.path import exists if exists(".svn"): print "This directory is svn-managed!"
So, I hope this gives you an idea of how Python works. As you can see, Python is very simple at its core. Next time, we will look at more practical things you can do with Python, and help you get passed any luggage you bring from your current programming language. Happy Coding!
- marv's blog
- Login to post comments
cool stuff!