Python: an introduction for PHP (and other) programmers
I love Python. It is clean, it simple and it evolves in a natural and healthy way. I have been working with PHP for over 10 years now, but Python stole my heart. Ahhhww :P
Here are some pointers to get you started, and hopefully make you enthusiastic too. Of course this is just a little tip of the iceberg. I can't give you a course in Python, but it should give you the push to explore a bit further.
Please note that this tutorial is primarily targeted at Python 21, but the examples all should work in Python 3 without trouble.
The interpreter
Before telling more about the language, you should know that the python interpreter has a great interface for use in the shell. You can type in statements, which are interpreted on the fly. Results of evaluations are printed to stdout, so you can see the result directly:
#!shell
$ python
>>> 1
1
>>> type(1)
<type 'int'>
To var_dump
an object as in PHP, Python has a non-recurring variant dir()
, which will show you all properties an object has.
#!shell
>>> dir(1)
['__abs__', ..., '__xor__']
Here you can see 1
has all the methods that are defined in the int
type. 1 is, in fact, an instance of the int type, with value 1:
#!shell
>>> (1).__str__() # __str__ is equivalent to PHP's __toString()
'1'
It is useful to know throughout this post that every name in python is in fact an object, whether it is a constant, a literal, an object, class, function, or type. This is similar to JavaScript, which you might be familiar with.
Code structure
Structure is based on indentation. This means that you will no longer use curly braces for compound statements and class or function bodies, but you will indent another step, similar to how YAML works. How big this indentation is, you decide. I use 4 spaces, but you may use tabs, or other amounts, you can even mix indentation styles. Of course it is best to choose just one style and stick with it.
#!python
def toBeOrNotToBe(a):
if a:
print(a, "is")
print(not a, "is not")
#!php
function toBeOrNotToBe($a) {
if($a) {
echo $a, " is\n";
echo !$a, " is not\n";
}
}
Notice you don't need parentheses around the if's expression, and the blocks are never closed (unless a new line with lower indent is encountered). Also notice you don't need a statement terminator besides a newline, like you need the semicolon in PHP.
Be aware that print
is a special case statement in Python 22, not a regular function call. This is similar to php's echo
.
If you need an empty block, you'd use the $pass$ statement:
#!python
def thisDoesNothing():
pass
def thisNeither():
if False:
pass
You may keep a block at one line, as long as it poses no ambiguity to the parser. Therefore
#!python
def thisDoesNothing(): pass
Is the same as the above version, but
#!python
def thisNeither(): if False: pass
renders a syntax error. The following version, however, is ok:
#!python
def thisNeither():
if False: pass
Control structures
Flow control
If-statements work as follows:
#!python
if a:
print(a)
elif b:
print(b)
else:
print("?")
#!php
if($a) {
echo $a;
} elseif($b) {
echo $b;
} else {
echo "?";
}
Note that the following piece of code would render a syntax error in python.
#!python
if a: pass
else if b: pass
#!php
if($a) ;
else if($b) ;
Switch statements don't exist.
Loops
The C-style for-loops don't exist in Python. You should use sequences to iterate over. Sequence types are more thoroughly explained in the documentation, but for now it suffices to say that a sequence is best compared to a (non-associative) array in PHP.
Python-style for-loops are best compared to PHP's foreach. Following code, using a range of numeric values as a sequence iterated over, is equivalent:
#!python
for i in range(0, 10):
pass
#!php
foreach(range(0, 10) as $i)
;
// or
for($i = 0; $i < 10; $i ++)
;
Both would translate into the following C-style for-loop:
#!C
int i;
for(i = 0; i < 10; i ++)
;
While loops are present in Python, with effectively the same syntax as in PHP:
#!python
i = 0
while i < 10:
print i
i += 1
but do-while loops aren't.
Comments
Comments start with a hash (#
) and end with a newline (just like PHP's #
and //
comments). Comments spanning multiple lines don't exist.
Built-in types
First of all, you can check types of variables by using type()
. Try using the interpreter to find out how types are handled.
I won't cover all types, but basically the most common 6 built-in types are:
None
, which is best compared to PHP'snull
- booleans, which are either
True
orFalse
- numbers:
int
,long
,float
andcomplex
- sequences:
string
,list
,tuple
(comparable to PHP'sarray
) andstr
(which is thetype()
of a string) - mappings:
dict
, best compared to an associative array in PHP - functions: either named, defined with
def
, orlambda
expressions - classes
- All other instances. Since in fact everything is an instance of some type, all remaining instances are typically object instances of user space classes.
Variables
Variables have no special prefix as in PHP. All variables are of the same order, which means that a function is a variable too, so is a class
, so is True
, so is NoneType
which is the type()
of None
.
Operators
Operators work pretty much the same as in PHP. Some differences are:
Operation | Python | PHP |
---|---|---|
String concatenation | a + b |
$a . $b |
Boolean expressions | not a or b and c |
!$a || $b && $c |
Member access | obj.property |
$obj->property |
Python has a few added nice features with operators, e.g. slicing in sequence types using the brackets operators:
Python | PHP |
---|---|
a = [1, 2, 3] |
$a = array(1, 2, 3) |
b = a[1:2] |
$b = array_slice($a, 1, 1) |
(Note that Python uses indexes rather than lengths for the second argument)
... and in Python 2, the string formatting operator:
Python2 | Python3 | PHP |
---|---|---|
a = "string %d formatted" % 1 |
a = "string {0:d} formatted".format(1) |
$a = sprintf("string %d formatted", 1) |
Functions
Functions are defined using the def keyword. A function consists of a name, an argument list and a body. The simplest function definition is as follows:
#!python
def f(): pass
The argument list may contain positional arguments with or without default values (which are grammatically equivalent to PHP arguments), keyword arguments with or without default values (which means that you can juggle the argument order around when calling the function) and a list of arbritrary arguments or keyword arguments, absorbed by a tuple
or a dict
at call time.
#!python
def fn1(a, b): # regular, positional arguments, and required by default
print (a + " and " + b)
def fn2(c="c"):
print (c)
def fn3(*args, **kwargs):
print (args)
print (kwargs)
fn1("a", "b") # "a and b"
fn1(b="b", a="a") # "a and b"
fn2() # "c"
fn2("d") # "d"
fn3() # () {} (an empty list and an empty dict)
fn3(1, 2, 3,
a="foo", b="bar") # (1, 2, 3) {'a': 'foo', 'b': 'bar'}
Functions may return and yield values. Yielding values makes the function a generator, which is a compile-time feature of python, basically meaning you can iterate over multiple variables returned by a function, breaking the current execution of the function itself. Again, examples tell more. The range function could be implemented as follows:
#!python
def range(start, end):
i = start
while ( i < end ):
yield i
i += 1
for i in range(5, 8):
print(i)
Resulting in:
5
6
7
Classes
- The main difference between python and PHP classes is that Python's classes are mutable. This means that at any given time in the execution of a script, the class's internals can be altered. This is somewhat like in JavaScript, where you can alter an object's prototype, which in fact alters the behaviour of all instances based on that prototype.
- Second, Python knows no access modifiers. Public, protected and private are in no way forced. The rule of thumb is that anything prefixed with a single underscore is considered private. The rest is public. But this is a convention, nothing forced by Python itself. Python doesn't care if you want to change private variables. If that's what you want to do, go ahead ;). Abstraction, such as abstract methods and interfaces, are not available. This leads to another fun aspect of Python, it has no compiler-level type checking. This is all runtime, and only if you want to do something that involves typing, you can write the code yourself (e.g. using
isinstance()
) - Third, classes support multiple inheritance, which means that a class can derive it's methods and properties from multiple parent classes.
- Fourth, all methods get the current object's instance as a first parameter. This means that calling the method statically with an object instance as the first parameter would render the exact same results as calling the method dynamically on the object instance. Convention is that you call the current instance
self
.
There is also a funny similarity between Python and PHP classes. In python, special methods have the form __method__
, where "method" is the special name in case. In PHP, we know the same thing for e.g. __toString()
, __call()
, etc.
The following is an example of classes in Python.
#!python
class Point:
def __init__(self, x, y):
self._x = x
self._y = y
class Circle(Point): # let the class Circle inherit from the class Point
def __init__(self, x, y, radius):
super(Circle, self).__init__(x, y) # Explicitly call the parent constructor
self._radius = radius
def __str__(self):
return "This is a circle"
def another_str_impl(obj):
return "It is a " + str(type(obj)) + " instance";
c = Circle(10, 10, 10)
print(c) # "This is a circle"
Circle.__str__ = another_str_impl;
print(c) # It is a <class '__main__.Circle'> instance
As you can see, the 'init' is the constructor, __str__
is more or less the same as PHP's __toString()
. Also note the omission of a new
keyword; Pythons classes are called directly.
The following table shows a mapping of PHP's magic methods to Python's
PHP | Python |
---|---|
__get |
__getattr__ |
__set |
__setattr__ |
__isset |
not available |
__unset |
__delattr__ |
__call |
not available |
__construct |
__init__ |
__destruct |
__del__ |
__invoke |
__call__ |
ArrayAccess::offsetGet |
__getitem__ |
ArrayAccess::offsetSet |
__setitem__ |
ArrayAccess::offsetExists |
not available |
ArrayAccess::offsetUnset |
__delitem__ |
Also, implementing Iterators like in PHP is even easier in Python. Simply implement the __iter__
method generating all items, and you're good to go:
#!python
class A:
def __init__(self):
self._items = ["a", "b", "c"]
def __iter__(self):
self._items.reverse();
for i in self._items:
yield i
a = A()
for i in a:
print(i)
for i in a:
print(i)
Result:
c
b
a
a
b
c
Ultimately, you can recreate virtually all types with the magic methods in Python. See the entire list in the documentation.
Modules
A module in Python is a file containing code. The name of the module is the filename, and the code can be used in other files by importing the modules. The file run from the command line is called the "main" module, which makes the name of the special __name__
variable contain "__main__"
.
file1.py:
#!python
import file2
from file3 import bar #import bar from file3 as a local name 'bar'
print(__name__)
print(file2.foo)
print(bar)
file2.py:
#!python
foo="foo"
file3.py:
#!python
bar = "bar"
Running file1.py from the command line would give the following results:
#!shell
$ python file1.py
__main__
foo
bar
The most common practice is to put classes into modules which are then imported in other modules. It is considered bad practice to have files run code that is not needed for their bootstrapping, so main code is usually fenced inside an if
, checking main is the current scope:
file1.py:
#!python
import file2
if __name__ == "__main__":
print("file1.py says hi")
file2.py:
#!python
if __name__ == "__main__":
print("file2.py says hi")
Running these file from the command line:
#!shell
$ python file1.py
file1.py says hi
$ python file2.py
file2.py says hi
Decorators
Decorators are functions or classes that can change a piece of code run-time, but does not clutter the code. This makes for excellent aspect-oriented programming, being able to mix in any kind of functionality into your class or function. I'll just leave it with a simple example:
#!python
debugging = False
def traced(fn):
def decorated(*args, **kwargs):
if debugging:
print("Enter: " + fn.__name__ + "()");
fn(*args, **kwargs)
if debugging:
print("Exit: " + fn.__name__ + "()");
return decorated
@traced
def foo(bar, baz):
print("bar: ", bar, "\nbaz: ", baz)
foo2(bar, baz)
@traced
def foo2(bar, baz):
pass
foo("hello", "world");
debugging = True
foo("world", "hello!");
The output would be:
#!shell
$ python ./test2.py
bar: hello
baz: world
Enter: foo()
bar: world
baz: hello!
Enter: foo2()
Exit: foo2()
Exit: foo()
Docstring
Python has a language feature which let you document classes and functions by inserting a string between the declaration and it's definition. This documentation is available at runtime in the __doc__
property.
#!python
def fn():
"This function actually does nothing"
pass
print fn.__doc__ # prints the docstring
This makes it very easy to get documentation on code using the interpreter:
#!shell
>>> import os
>>> print os.path.realpath.__doc__
Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path.
... and much, much, much, much, much more.....
This post could continue for ages. But I'm going to stop here. I hope I have given you an appetite. Python is one of the most flexible languages out there, and I'd really encourage you to try something with the Django web Framework, Google App Engine, or get started building GTK or QT desktop applications, binding to VLC's library, or write your own threaded web server, the possibilities are endless.
If you're getting enthusiastic, just go monk and simply read the Python documentation.
Have fun :)
-
Python 2 is rapidly becoming outdated, but let me tell you that learning python 2 won't be wasted time. Code samples in this post work in both python 2 and 3. ↩︎
-
In Python 3 this actually became a regular function, so all print calls in the examples have parentheses, even though they're not needed in Python 2. ↩︎