What is it like moving from PHP to Python? Part 3 explores Python’s conditional operators and loops.
- Deep Dive Part 1 - overview, conventions, everything is an object
- Deep Dive Part 2 - primitives, lists, operators, and operations
- Deep Dive Part 3 - conditions, type checking, and loops
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 to follow along with the code examples in this series.
In Part 3 of this series, we will examine these in more detail to see how they compare in Python vs PHP:
- Conditions and blocks
- Conditional operators
- Type Checking
- Loops
Table of Contents
Conditions
Compared to PHP, Python conditions refreshingly read more like pseudocode, with some minor bash (like the elif
) and SQL (like the and
, or
, is
, is not
) vibes.
Check it out:
admins = ["john", "sue", "bob"]
name = "john"
age = 23
admin = True
if name == "john" and age == 23:
print('Your name is john')
print('And you are 23 years old')
elif name == "john" or name == "rick":
print("you are either john or rick..")
elif name in admins:
print("you are an admin")
elif admin is not True:
print("you are not an admin")
else:
pass
It’s simple and intuitive enough to read, but there’s a lot to unpack. Let’s get to it. Starting with the blocks themselves.
Blocks
Instead of the usual {
and }
curly braces to define a block, Python uses a single :
colon instead.
And since there’s no }
closing that block in Python, the indentation matters.
PHP
PHP does actually allow you to omit curly braces for a 1-statement block. But nobody really does that, because it increases the likelihood of errors when new lines are added to the body.
// Allowed, technically speaking..
if ($name == "john")
echo 'Your name is john';
if ($name == "john") {
echo 'Your name is john';
echo 'I am certain';
}
Python
Python, however, does consistently use the :
to denote a block, regardless if it contains 1 or more statements:
if name == "john":
print('Your name is john')
if name == "john":
print('Your name is john')
print('I am certain')
Think of it this way. If the body is not indented, Python thinks the if
condition has no corresponding block (and throws an error):
if name == "john":
print('Your name is john') # throws an error
If you have 2 statements, but don’t indent the last one, that last one basically “closes” the prior if statement and runs outside of the block:
if name == "john":
print('Your name is john') # Runs only when name is John
print('You must be human.') # Runs always
So, mind the indentation.
Nested If
Python has nested conditions too. These work exactly how you’d expect them to.
Again, mind the indentation, though.
PHP
if ($name == "john") {
echo 'Your name is john';
if($age == 23) {
echo "And you are 23 years old";
}
}
Python
if name == "john":
print('Your name is john')
if age == 23:
print('And you are 23 years old')
If, Else If, Else Ladder
These also are exactly what you’d expect.
Except instead of PHP’s elseif
, Python’s “Else If” is just elif
(like bash 😉)
PHP
if ($name == "john") {
echo 'Your name is john';
} elseif($name == "Bob") {
echo 'Your name is Bob';
}
else {
echo 'you are no one';
}
Python
if name == "john":
print('Your name is john')
elif name == "Bob":
print('Your name is Bob')
else:
print('you are no one')
Ternary Operators (aka Conditional Expressions in Python)
Python calls ternary operators: “Conditional expressions”.
Same idea, different syntactic sugar.
Python’s is more readable, especially with the “then” value up front like that.
PHP
echo "Your name is: ";
echo ($name == "John") ? "John" : "unknown";
Python
print("Your name is: ")
print( "John" if name == "John" else "unknown" )
pass Statement
pass
is a Python-only quirk. It is a null statement that does.. nothing at all.
It’s used as a placeholder inside the empty bodies of conditions (and loops, functions, and classes).
Why? Since bodies aren’t wrapped in curly braces in Python, and indentation dictates when the body begins and ends, then there is no body, and where there is no body, there is an error.
PHP
if ($name == "john") {
// Works just fine in PHP
}
else {
echo 'You are not John');
}
Python
if name == "john":
# Throws an error in Python
else:
print('You are not John')
It’s kind of like leaving the semicolon off of lines of PHP code. The parser runs into the next line, confused.
Operators
Logical Operators
PHP’s &&
, ||
, and !
is Python’s and
, or
, and not
.
PHP
$x = 9.99;
echo ($x > 9 && $x < 10); // true
echo ($x < 10 || $x > 10); // true
echo !($x > 9 && $x < 10); // false
Python
x = 9.99
print(x > 9 and x < 10) # True
print(x < 10 or x > 10) # True
print( not (x > 9 and x < 10) ) # False
Comparison Operators
PHP and Python use the same comparison operators: ==
, !=
, >
, <
, >=
, and <=
However, Python does not have ===
.
In fact, Python’s ==
confusingly falls somewhere in between PHP’s ==
and ===
.
PHP
As we know, ==
is super duck-typey, and ===
isn’t:
var_dump(1 == '1'); // true
var_dump(1 == 1.0); // true
var_dump(1 == true); // true
var_dump(1 === '1'); // false
var_dump(1 === 1.0); // false
var_dump(1 === true); // false
Python
Meanwhile, Python’s ==
is like PHP’s ==
, except when it comes to strings:
print(1 == '1') # False
print(1 == 1.0) # True
print(1 == True) # True
This is going to throw you off a lot.
So why does that integer vs boolean comparison work in Python, but the integer vs string comparison doesn’t?
Because bool
is a subclass of int
, as will be demonstrated shortly in the “Type Checking” section, below.
Identity Operators
Python has two identity operators: is
and is not
. These test for reference equality - when two variables point to the same location in memory.
Do not confuse these with ==
and !=
, which test for value equality - when two variables have the same value.
Coming from PHP, these are strangely easy to confuse even though PHP has no is
or is not
operators.
I suspect this is due to Python’s and
, or
, and not
SQL-esque logical operators, which leads to our muscle memory of also using the is
and is not
also-SQL-esque identity operators within the same conditions.
This would be an insidious habit that needs breaking, because it could lead to subtle bugs.
Python
Alright, let’s check out is
and is not
vs ==
and !=
.
a = [1,2,3]
b = [1,2,3]
print(a == b) # Prints out True. Same values.
print(a is b) # Prints out False. Different memory addresses.
x = [1,2,3]
y = x
print(x == y) # Prints out True. Same values.
print(x is y) # Prints out True too. Same memory address.
This is pretty straightforward.
Where it gets insidious is with ints, strings, floats etc:
x = 1
y = 1
print(x == y) # Prints out True. Same values.
print(x is y) # Prints out.. True.
# Print out.. same memory addresses!
print(id(x), id(y)) # Eg. 9788960 and 9788960
So what happened, how can x
and y
have the same memory address? Turns out, Python can cache integers and such into singleton objects for performance reasons.
Worse, CPython does not do this.
No, “CPython” was not a typo. CPython is the most common interpreter of Python “bytecode”. How do you know what interpreter you are using? Run these three in your terminal:
python3
import platform
platform.python_implementation()
It’ll probably output 'CPython'
.
The problem is that your terminal, container, server, or some other environment might be running some other Python interpreter. So don’t confuse is
and ==
.
Luckily, Python 3.8 and onward will actually helpfully throw a SyntaxWarning
if you even use is
or is not
to compare with literal values:
x = 1
print(x is 1) # SyntaxWarning: "is" with a literal. Did you mean "=="?
PHP
Surprise! PHP has no concept of reference equality. Which is why you will confuse is
and ==
. 😉
Membership Operators
Python’s in
and not in
operators check for values in iterable objects like strings, lists, dictionaries, etc.
Python
Pretty straightforward and intuitive:
x = [1,2,3]
y = "Hello world"
if 1 in x:
print('1 is in [1,2,3]')
if 6 not in x:
print('6 is not in [1,2,3]')
if "Hello" in y:
print('"Hello" is in "Hello World"')
if "H" in y:
print('And so is "H"')
if "X" not in y:
print('But "X" is not')
PHP
Of course, in PHP, the same is achieved through its inconsistent, built-in functions:
in_array()
, where needle comes first, then haystackstr_contains()
, where haystack comes first, then needle 😱
Or for versions older than PHP8, you’re stuck with strpos()
instead of str_contains()
, which is a wildly different developer experience with the whole !== false
thing. 😭
$x = [1,2,3];
$y = "Hello world";
if (in_array(1, $x)) {
echo '1 is in [1,2,3]';
}
if (!in_array(6, $x)) {
echo '6 is in [1,2,3]';
}
if (strpos($y, 'Hello') !== false) {
echo '"Hello" is in "Hello World"';
}
if (strpos($y, 'H') !== false) {
echo 'And so is "H"';
}
if (strpos($y, 'X') === false) {
echo 'But "X" is not';
}
Bitwise Operators
The good news is that the “bitwise” operators for twos-compliment binary are the same in both PHP and Python: <<
, >>
, &
, |
, ~
, and ^
.
Type Checking
Python has very straightforward type checking compared to PHP.
Determining Type
Python
Python uses type()
to determine the type for anything. The quirk is that you technically have to append the __name__
attribute to it to get the type in plain text:
print(type(123).__name__) # prints: int
print(type(3.14).__name__) # prints: float
print(type("Hello").__name__) # prints: str
print(type(["John", "Jane"]).__name__) # prints: list
print(type({"John": 25, "Jane": 21}).__name__) # prints: dict
Same for objects:
class Employee:
pass
john = Employee()
print(type(john).__name__) # prints: Employee
Without __name__
, you’ll get something like this:
print(type(123)) # prints: <class 'int'>
print(type(john)) # prints: <class '__main__.Employee'>
Now you’re probably wondering why type()
would return something like <class 'int'>
in the first place?
That’ll be addressed in the next section.
PHP
PHP’s gettype()
is mostly similar to Python’s type()
, except it returns string representations by default:
echo gettype(123); // prints: integer
echo gettype(3.14); // prints: float
echo gettype("Hello"); // prints: string
echo gettype(["John", "Jane"]); // prints: array
And worse, it merely returns object
for any and all objects:
class Employee{};
$john = new Employee();
echo gettype($john); // prints: object
So of course you’ll need to use a different function for objects:
class Employee{};
$john = new Employee();
echo get_class($john); // prints: Employee
Which, in standard PHP fashion, gettype()
and get_class()
have inconsistent naming conventions.
Type Comparison
Python
Alright, so back to why type()
returns stuff like this in the first place:
print(type(123)) # prints: <class 'int'>
print(type(john)) # prints: <class '__main__.Employee'>
Since you’re generally using type()
for sake of type comparison, there’s no need to deal with strings at all. You can compare a type directly against a class:
print(type(123) == int) # True
print(type(123).__name__ == 'int') # True
Same goes for custom classes too:
class Employee:
pass
john = Employee()
print(type(john) == Employee) # True
print(type(john).__name__ == 'Employee') # True
Type comparison in Python has one caveat though:
type()
does not support inheritanceisinstance()
does support inheritance
Consider the following:
# Parent class
class Employee:
pass
# Child class Engineer, inherits parent class Employee:
class Engineer(Employee):
pass
print(type(Employee()) is Employee) # True
print(type(Employee()) is Engineer) # False
print(type(Engineer()) is Employee) # False - except an Engineer IS an Employee..
print(type(Engineer()) is Engineer) # True
print(isinstance(Employee(), Employee)) # True
print(isinstance(Employee(), Engineer)) # False
print(isinstance(Engineer(), Engineer)) # True
print(isinstance(Engineer(), Employee)) # True
isinstance()
works for primitives too:
print(isinstance(123, int)) # True
print(isinstance(3.14, float)) # True
print(isinstance("Hello", str)) # True
print(isinstance(["John", "Jane"], list)) # True
print(isinstance({"John": 25, "Jane": 21}, dict)) # True
So for type comparison, you will generally want to go with isinstance()
.
PHP
Although PHP’s instanceof
operator is somewhat similar to Python’s isinstance()
, it is much more limited. And so you typically see all these one-off methods for type checking in PHP that you don’t have to deal with in Python:
echo is_int(123); // True
echo is_float(3.14); // True
echo is_string("John"); // True
echo is_array(["John", "Jane"]); // True
class Employee{};
$john = new Employee();
echo ($john instanceof Employee); // True
In summary, here we have a whole pile of methods in PHP for type checking and comparison, including a special instanceof
operator, inconsistently named get_class()
and gettype()
methods, and the whole pile of one-off methods above.
Whereas with Python, it’s just type()
and isinstance()
, but really, just mostly isinstance()
.
Loops
Looping constructs are similar between PHP and Python, but also kind of very different at the same time.
for
Python does not have PHP’s C-style for
loops that look like: for(i=0; i<10; i++)
.
What it does have is PHP’s foreach
concept of iterating over.. iterables, which looks like: for x in y
.
And to replace all that snarly, antiquated i=0; i<10; i++
stuff missing in Python, you use the range()
method, or in this case: range(10)
.
Furthermore, Python’s for x in y
looping construct supports all iterables, (or what Python calls “sequences”): lists, tuples, dictionaries, sets, strings, and ranges.
Python
The end result is consistency and readability:
# Iterate 10x
for i in range(10):
print(i)
# Print some odd numbers
for i in range(1, 11, 2):
print(i)
# Iterate each character in a string
for letter in "hello world":
print(letter)
# Iterate over a list, etc
for name in ["John", "Jane", "Bob"]:
print(name)
# Print first & last names
person = {"first": "Jane", "last": "Doe"}
for key in person:
print(person[key])
PHP
Whereas with PHP, you have the cobbled-together-over-time vibe we know and love:
// Iterate 10x
for ($i = 0; $i < 10; $i++) {
echo $i;
}
// Print some odd numbers
for ($i = 1; $i <= 11; $i=$i+2) {
echo $i;
}
# Iterate over an array, etc
foreach(["John", "Jane", "Bob"] as $name) {
echo $name;
}
# Print first & last names
foreach(["first" => "Jane", "last" => "Doe"] as $name) {
echo $name;
}
while
While loops are basically the same in Python as PHP, with one caveat worth pointing out: Python does not have do-while loops.
However, these can still be implemented with an “infinite” loop (while True
), followed by a break
when the condition is met.
Python
count = 0
# Print 0 through 4
while count < 5:
print(count)
count += 1
# Print 5 through 9
while True:
print(count)
count += 1
if count == 10:
break
PHP
$count = 0;
// Print 0 through 4
while ($count < 5) {
echo $count;
$count++;
}
// Print 5 through 9
do {
echo $count;
$count++;
} while($count < 10);
for else and while else
The usage of else
with for
and while
loops is something interesting that Python has that PHP does not.
It has to be paired with a break
to make sense to even use. Otherwise, the else
will always fire if a for
or while
loop is not “broken” with break
.
So, why do this? If the loop exhausts itself without doing something, it gives you the opportunity to intercept.
However, this is just syntactic sugar that saves you from having to declare a flag and separate if condition, and so this behavior can be easily replicated in PHP.
Python
Here is an example of the else
firing:
for name in ["John", "Jane", "Bob"]:
if name == "Joe":
print("Joe was found")
break
else:
# This fires because the above for loop did not break. Joe was never found.
raise ValueError("Joe not found")
And an example of the else
not firing:
for name in ["John", "Jane", "Bob"]:
if name == "Bob":
print("Bob was found")
# However since Bob is found, the for loop is exited (not exhausted)
break
else:
# And so this never fires, as a result
raise ValueError("Joe not found")
PHP
Since PHP does not have the built-in else
construct for foreach
and while
loops, the code is a bit more verbose (and error prone) due to the boolean flag and additional if conditions:
$found = false;
foreach(["John", "Jane", "Bob"] as $name){
if ($name == "Joe") {
$found = true;
print("Joe was found");
break;
}
}
if(!$found) {
# This fires because Joe was never found, the flag was never set to true.
throw new Exception("Joe not found");
}
$found = false;
foreach(["John", "Jane", "Bob"] as $name){
if ($name == "Bob") {
$found = true;
print("Bob was found");
break;
}
}
if(!$found) {
# Since Bob was found, and the flag was set to true, this never fires
throw new Exception("Bob not found");
}
map and filter
Maps and filters are nearly identical in Python and PHP.
Python
However, in Python, there are two caveats.
First, they return map and filter objects, not the data type you passed into them. This is different than PHP where you pass an array in, and get an array out of it. In the example below, their output is casted back to a list
using list()
.
Second, they use lambdas. A lamba in Python is just an anonymous function, but the syntax looks more like ES6 arrow functions. Other than that they work identically to PHP’s closures, which are also just anonymous functions.
values = [1, 2, 3, 4, 5]
doubled_values = list(map(lambda x: x * 2, values))
odd_values = list(filter(lambda x: x % 2, values))
print(doubled_values) # Prints: [2, 4, 6, 8, 10]
print(odd_values) # Prints: [1, 3, 5]
PHP
And of course, with PHP, you have the inconsistent parameter ordering of array_map
and array_filter
..
$values = [1, 2, 3, 4, 5];
$doubled_values = array_map(function ($x) {
return $x * 2;
}, $values);
$odd_values = array_filter($values, function ($x) {
return $x % 2;
});
print_r($doubled_values);
print_r($odd_values);
Conclusion
Compared to PHP, Python:
- Removes a lot of the
{
,}
,(
,)
syntatic noise - Has quirks like
pass
, andelse
forfor
andwhile
loops, and gotchas when it comes to type checking and type comparison - Has clean, consistent membership operators (
in
andnot in
) that aren’t chaotic like php’sstrpos
andin_array
solutions - Will confuse you with the whole “reference equality” gotcha of
is
vs==
- Has
==
, which falls somewhere between PHP’s==
and===
- Calls anonymous functions “lambdas”, and ternary operators “conditional expressions”
In Part 4, we will finally dive into classes and functions, go deeper into lambdas, and also dive into closures (yes, Python has these, and they are true closures), while comparing these to PHP-equivalent code every step of the way.
- Deep Dive Part 1 - overview, conventions, everything is an object
- Deep Dive Part 2 - primitives, lists, operators, and operations
- Deep Dive Part 3 - conditions, type checking, and loops