Python for PHP Developers: Deep Dive Part 1


What is it like moving from PHP to Python? What are the similarities and differences between them?

Using the broad overview from learningpython.org, we’ll examine the ins and outs of Python from a PHP developer’s perspective, while showing the equivalent PHP and Python code, side-by-side.

Clone github.com/bill-c-martin/python-for-php-devs to get a Python environment setup and follow along with the code examples below.

In Part 1 of this series, we will:

And with those two out of the way, we’ll then zoom way out to the conceptual level and examine how fundamentally different Python can be than PHP:

Table of Contents

Hello World

Python does not require opening tags like PHP.

Most importantly, Python does not require semicolons at the end of lines.

PHP

<?php
echo 'hello world';

Python

print('hello world')

Indentation

Indentation is important in Python.

You must indent with 4 spaces per nesting level, which should feel familiar to PHP developers following PSR standards.

The difference is this: not indenting properly in Python causes fatal errors because Python’s 4-space indentation replaces the need for { and } in code blocks.

PHP

$x = 1;
if($x == 1) {
    echo 'x is 1';
}

Python

x = 1
if x == 1:
    print('x is 1');

Data Types

Python has quite a few built-in data types compared to PHP. Some will feel familiar. Others, not so much.

There’s 14 data types in total vs PHP’s 6 or so, so half of them will feel foreign.

Those 14 can be divided into 3 groups:

Primitives

Integers, floats, booleans, and strings all work similarly as PHP.

The complex data type will be new to you, which is more applicable in the domain of mathematics.

Type Example Description
str 'hello world' text/strings, same as PHP
int 42 integers, same as PHP
float 1.23 floating point numbers, similar to PHP, but definitely more feature-rich and easier to work with. More on why, later.
bool True False booleans, same as PHP. Must be capitalized though.
complex 23j Real/imaginary numbers, useful in mathematics applications. Has set of functions specific to this domain.

Data Collections

Python’s list and dict collections are basically PHP’s numeric and associative arrays.

tuple and set collections will be trickier.

Here’s some examples:

Type Example Description When to Use
list ['foo', 1, 2.34, True] Like PHP numeric arrays Same as PHP. Use when dynamically building collections of things. Eg. list of order IDs purchased today.
range range(1,100,2) Immutable list over a range of numbers. Eg all odd numbers until 100. Often uses in for loops
dict {'foo': 'bar', 'biz': 'baz'} Like PHP associative arrays meets JSON syntax Same as PHP. Use when you need key/value pairs. Eg. list of order IDs pointing to Order objects.
tuple ('apple', 'banada', 'cherry') Like a PHP numeric array, but immutable. PHP has no native equivalent to this. Static lists of constants. Eg. list of billing states to populate a dropdown with.
set {'apple', 'banana', 'cherry'} Like PHP numeric arrays, but no duplicates Think set theory, or SELECT DISTINCT type data. Eg. list of distinct states that orders were shipped to today.

Confusing? Determine your needs first, then decide:

Type Key -> Values? Mutable? Ordered? Sliceable? Can Have Duplicates?
list no yes yes yes yes
range no no yes yes no
dict yes yes ? yes no
tuple no no yes yes yes
set no yes no no no

Another way of deciding is by their built-in methods.

Each collection type’s built-in methods will determine how you will be able to interact with its data:

Binary Types

Python has some binary types that will probably be very foreign to PHP developers, except maybe from those old C classes you took in college.

From what I gather, these are useful in certain situations:

Type Example Description When to Use
bytes bytes('Hello World', 'utf-8') Store a string as raw, immutable bytes. When performance or encoding matters.
bytearray bytearray[1,2,3,4] Same as bytes, except it’s iterable and mutable. Same as bytes, or when bytes needs to be iterated or mutated.
memoryview memoryview(bytes('Hello World')) Allows direct read/write access to an object’s byte-oriented data without needing to copy it first. Performance gains on really large objects

Debugging

The glorious var_dump() has no native equivalent in Python.

var_dump() of course shows us structure, methods, properties, types, keys, values, lengths, everything:

PHP

var_dump('1');       # prints: string(1) "1"
var_dump(1);         # prints: int(1)
var_dump([1]);       # prints: array(1) { [0]=>int(1)} 
var_dump(new Foo()); # prints: object(Foo)#1 (1) { ["bar"]=>string(3) "bar" }

Python

Python has numerous options, all are lackluster:

Luckily, there is a var_dump pip package we can install to get PHP’s superior var_dump() in Python.

But let’s go through those above 3 Pythonic inferiors, first.

vars()

vars() shows state as key values pairs, but not structure, methods, types, or lengths.

For example, for this simple class with a constructor, method, and 2 properties:

class Foo:
    def __init__(self, x=5):
        self.x = x
    def bar(self, y = 10):
        self.y = y
        print('baz sets 10')

foo = Foo()
foo.bar(10)

vars() shows only;

{'x': 5, 'y': 10}

Worse, vars() only works for some types of variables, not all.

dir()

dir() will show properties and method names, but not structure, state, values, or types.

For example, for strings, Lists, and Dictionaries:

print(dir('test'))
print(dir([1,2,3]))
print(dir({'foo': 'bar', 'biz': 'baz'}))

dir() only shows method names those native types/objects have:

# string
['capitalize', 'upper', 'lower', 'join', 'split'] # etc
# List
['count', 'pop', 'append', 'reverse'] # etc
# Dictionary
['copy', 'keys', 'items', 'values'] # etc

as well as special methods (more on this later):

['__eq__','__gt__','__lt__','__add__', '__mul__']

However, for objects instantiated from classes:

# Create and instatiate a quick class (more on this later too)
class Foo:
    x = 5
    def bar(self):
        print('baz')
foo = Foo()

print(dir(foo))

dir() does show the methods (unlike vars()), as well as the properties. But again, not the values (unlike vars()):

['bar', 'x', '__class__', '__getattribute'] # etc

pprint()

pprint() is a module that can be pulled in, and it shows structure, state, and types, but not methods.

For example, for strings, Lists, and Dictionaries:

pprint('test'))
pprint([1,2,3]))
pprint({'foo': 'bar', 'biz': 'baz'}))

pprint() only shows:

'test'
[1,2,3]
{'foo': 'bar', 'biz': 'baz'}

For that Foo class earlier:

pprint(foo)

pprint() unhelpfully shows just:

__main__.Foo object at 0x7f3887cbbc70>

For objects, you can pull in the getmembers() module and combine it with pprint().

from inspect import getmembers
from pprint import pprint
pprint(getmembers(foo))

which will finally print a List of all properties with values and types, as well as all methods.

[('bar', <bound method Foo.bar of <__main__.Foo object at 0x7fba1718bc70>>),
('x', 5),
('y', 10)]

Unfortunately, a verbose list of some ~25 special methods clutters that output, which is not shown above.

var_dump() through pip

Clearly, the above Pythonic smorgasbord of methods are inadequate for somebody used to PHP.

Enter: var_dump for Python.

Install it from the command through pip;

pip install var_dump

Import:

from var_dump import var_dump
var_dump('test', [1,2,3], {'foo': 'bar', 'biz': 'baz'}, foo)

And profit:

#0 str(4) "test"
#1 list(3) 
    [0] => int(1) 
    [1] => int(2) 
    [2] => int(3) 
#2 dict(2) 
    ['foo'] => str(3) "bar"
    ['biz'] => str(3) "baz"
#3 object(Foo) (2)
    x => int(5) 
    y => int(10)

Concepts

Everything is an Object

This might be a bit of a foreign concept for PHP developers, but:

In Python, (mostly) everything is an object.

Strings, integers, floats, and booleans are objects:

print('hello'.upper())    # Prints: HELLO
print((9).bit_count())    # Prints: 2
print((2.0).is_integer()) # Prints: True
print(False.conjugate())  # Prints: 0

Lists and dictionaries are objects:

print([1,0,0,1,0].count(0))                   # prints: 3
print({'foo': 'bar', 'biz': 'baz'}.pop('foo')) # Prints: bar

And so on.

Well, operators are not objects..

Operators are Methods

+, *, %, ==, <, and > are not just operators, they are syntactic sugar for methods that exist on the object to its left (and sometimes, its right).

So when you do this:

1 + 2

You are really just doing this behind the scenes:

(1).__add__(2)

Where 2 is being passed to 1’s __add__() method which all ints have.

This is also true for all of the other operators.

For example:

Those magic methods are also known as “special methods”, or “dunder methods”: double underscore methods.

Most Data Types Have Magic Methods

int, str, float, list, dict (and the rest) all have these kind of magic methods.

You can see them all when you dump a variable/object with dir().

Take a look at the int, str, and list types:

print(dir(42))
print(dir('42'))
print(dir([42]))

Note how they all share, for example, __add__ and __mul__.

This means the magic methods behind + and * have implementation specific to each type.

Observe how + plays out on these 3 data types:

print(42 + 1)     # Prints: 43
print('42' + '1') # Prints: 421
print([42] + [1]) # Prints: [42, 1]

Since each type has its own __add__, then + does:

Observe the same with *:

print(2 * 3)       # Prints: 6
print('2' * 3)     # Prints: 222
print([1,2,3] * 3) # Prints: [1,2,3,1,2,3,1,2,3]

Since each type has its own __mul__, then * does:

Python’s ability for operators to have different meaning in different objects is called operator overloading

So, say goodbye to all those type-specific, inconsistently-named, globally-namespaced, one-trick pony functions PHP has, like: array_merge(), array_product(), str_repeat() and that ugly . concatenation operator.

Operator Overloading

Operator overloading is not just limited to just + and *, or the int, str, and list types in the prior example.

It works for all operators.

Consider some trivial Python code that looks at today’s orders by email, and gathers a list of users created more than 1 day ago:

for email in today_orders:
    user = User(email)
    if user.created < date.today():
        users[] = user

In this code alone, there are quite a few overriden operators:

..and so on.

This is wildly different than how you picture code as a PHP developer.

But once it sinks in, try this revalation: You can override operators in your custom classes too.

The classic example for this is a Point class consisting of coordinates x and y.

p1 = Point(1,2)
p2 = Point(5,6)

Let’s say you wanted to be able to add these together, or even just print a Point on screen in some custom way.

In PHP, you’re looking at having to add add() and print() functions to the Point class, which isn’t the worst thing in the world.

But in Python, instead of having add() and print(), let’s say you named them __add__ and __str__.

Your calling code would then look like:

print(p1 + p2)

instead of:

echo $p1->add($p2);

PHP is not very “pythonic” it would seem.

Pythonic

“Pythonic” is doing things “The Python Way”, which is a challenge for PHP developers used to doing things their native way.

To be fair though, the same can be said for Python developers entering PHP world, with its null coalescing and spaceship operators, interfaces, autoloading, and PSR standards in general.

We just don’t have a catcy name for it, is all.

But to be fair to Python as well, “Pythonic” ultimately just means writing simple code in the way that humans think. A philosophy, really.

Some examples of Pythonic are:

..and many more.

Versatility

In the beginning, PHP was created to make websites, and it still shows to this day. Its singular focus on web has produced the likes of Laravel, Wordpress, Magento, etc, and it is really good at it.

However, the versatility of Python is staggering:

As a PHP developer, you are used to thinking in terms of the web.

So when you look at the code from a portfolio built in Python’s Django web framework, it won’t seem that foreign to you.

Switch over to some Python code from the lollypop mp3 player desktop application though, and it’s going to feel more foreign.

Jump over to even just the rudimentary “hello world” of ML, and it’ll definitely feel quite foreign.

Accidentally wander into Deep Speech, and your PHP brain buckles.

The versatility of Python is largely due to its rich ecosystem, sprawling data science community, and really.. its low barrier of entry.

And by rich ecosystem, we’re talking:

I am not sure how often the average web Python developer is going to be building roboadvisors, performing complex calculations in NumPy/Pandas, generating plots in Plotly and so on.

But the versatility of Python is worth being aware of.

Conclusion

Alright, so we’ve examined some Python basics, data types, and debugging to get a taste for Python vs beloved PHP.

Then we covered the biggest high-level Python concepts that will throw off PHP developers.

In Part 2, we will get into the nitty gritty details of Python, while comparing it to PHP-equivalent code every step of the way.