When you come from PHP, learning Python can feel deceptively easy at first. Variables, functions, classes: nothing that shocking. Reading Python code is very easy.
And yet, after a few days or weeks coding a real program, things start to behave in unexpected ways.
Most of the time, Python isn’t being weird. You’re just still thinking like a PHP developer.
In this article, I’ll go through the most common pitfalls PHP developers run into when learning Python. Once you understand these, things feel less mysterious and magical.
1. The execution model is fundamentally different
This is the biggest conceptual shift for PHP developers.
PHP is quite special when it comes to its style of execution. An HTTP request comes, a php-fpm process is spawned, the request is handled, and the process then dies.
- Every HTTP request starts from scratch
- The whole runtime is created
- Your code runs
- Everything is destroyed at the end of the request
Unless you explicitly persisted some state to an external system (database, cache, session), the state is gone.
This means you can focus on the context of the request and not care about memory leaks.
In Python (as in Node.js and most other programming languages), the program keeps running and is reused across requests.
- The program starts and keeps running
- The running program is reused for each request
- Modules are only imported once
- State can leak across requests
If you only translate PHP code into Python but keeps the same architecture and coding patterns, memory-leaks are guaranteed and security issues may arise.
Here is an example of what a PHP developer could write:
class ProductRepository
{
private static array $cache = [];
public function getProducts(
string $category
): array {
if (!isset(self::$cache[$category])) {
self::$cache[$category] = $this->getProductsForCategory(
$category
);
}
return self::$cache[$category];
}
// ...
}
This works perfectly in PHP because $cache is destroyed after each request. A PHP developer familiar with static variables might write similar Python code:
# Module level - executed once when imported
_product_cache = {}
def get_products(category: str):
# BUG: This cache grows forever across all requests!
if category not in _product_cache:
_product_cache[category] = get_products_for_category(category)
return _product_cache[category]
The problem: _product_cache is created once when the module loads and persists across all requests forever. It grows indefinitely with every unique category ever requested. In PHP, the static variable is scoped to the request lifecycle, so this same pattern is safe.
2. Mutability will bite you sooner or later
Python declares default parameters only once (when the function is defined). With mutable types, it can lead to terrible bugs.
A classic example:
def add_item(item, items=[]):
items.append(item)
return items
For a PHP developer, this looks harmless. In Python, that default list is created once, not per call.
So state accumulates across calls.
# ['a']
add_item('a')
# ['a', 'b']
add_item('b')
# ['a', 'b', 'c']
add_item('c')
You have to be very careful about default parameters: they don't look dangerous at first but can lead to many "wtf" moments.
Python developers usually deal with it like this:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
# ['a']
add_item('a')
# ['b']
add_item('b')
# ['c']
add_item('c')
3. Not using virtual environments and modern package managers
In PHP, composer handles both dependency management and autoloading in a straightforward way. Python's ecosystem is more fragmented, which catches PHP developers off guard.
PHP developers often start by installing packages globally with pip install, the same way they might have started with Composer. But unlike PHP where each project naturally isolates its dependencies through vendor/, Python requires explicit virtual environments.
Without virtual environments, you'll run into version conflicts between projects and polluted global python installation.
The modern Python approach uses virtual environments, created manually through the built-in venv module or indirectly by package managers like uv or poetry.
Think of virtual environments like having a separate vendor/ directory that also includes the Python interpreter itself.
4. Looking for interfaces
PHP developers love interfaces. They're how we define contracts, enable dependency injection, and make code testable.
Python doesn't have interfaces in the traditional sense. At first, this feels wrong. How do you ensure a class implements certain methods? How do you do proper dependency injection?
Python relies on duck typing: "If it walks like a duck and quacks like a duck, it's a duck."
You don't declare that a class implements an interface; you just use it. If it has the methods you call, it works.
Modern Python does have tools that bridge this gap:
- Abstract Base Classes (ABC) for enforcing method implementation
- Protocol classes (Python 3.8+) for structural subtyping
But the Python way is to embrace duck typing for flexibility and use these stricter approaches only when you need them: not by default like in PHP.
5. Type hints
PHP type hints are enforced at runtime.
function greet(string $name): string {
return "Hello, " . $name;
}
greet(123); // Fatal error: Argument must be of type string
Python's runtime doesn't care about them (although some tools like Pydantic can rely on them at runtime).
def greet(name: str) -> str:
return f"Hello, {name}"
# "Hello, 123"
greet(123) # Runs fine! No error at runtime
Python type hints exist for static analysis tools, not the runtime. Tools like mypy, pyright, or your IDE will catch type errors before you run the code — similar to how TypeScript works.
Why this matters:
- You need to actually run a type checker (it won't happen automatically)
- Type hints are optional — you can mix typed and untyped code
- Some libraries use type hints at runtime (like Pydantic, FastAPI), but that's the exception
Think of Python type hints like TypeScript: helpful for development and catching bugs early, but not a runtime guarantee. The flexibility is powerful once you adjust your mental model.
6. Not writing pythonic code
PHP developers often translate their patterns directly: lots of classes, explicit getters/setters, verbose loops. Python has idioms that feel strange at first but become natural:
List comprehensions instead of foreach loops:
# PHP-style Python (verbose)
items = []
for user in users:
items.append(user.name)
# Pythonic
items = [user.name for user in users]
Context managers instead of try/finally:
# PHP-style
file = open('data.txt')
try:
content = file.read()
finally:
file.close()
# Pythonic
with open('data.txt') as file:
content = file.read()
Decorators for reusable concerns:
@login_required
@cache(ttl=300)
def get_user_profile(user_id):
return fetch_profile(user_id)
7. Being reluctant to monkey patching
In PHP, modifying third-party code means forking packages or extending classes. Python allows you to modify classes and modules at runtime. This is called monkey patching. PHP developers initially see this as dangerous and hacky. And yes, it can be when abused. But it's also a powerful tool for testing (mocking dependencies without dependency injection containers).
# Mock a dependency in tests
from unittest.mock import patch
def test_my_function():
api_response = {
"user": {
"id": 1,
"username": "John"
}
}
with patch('external_library.api_call', return_value=api_response):
result = my_function()
assert result == {"id": 1, "username": "John"}
# Automatically restored after the context
The Python community has conventions around when monkey patching is acceptable (mostly in tests and development tools). Learning to use it appropriately, rather than avoiding it entirely, makes you more effective.
Conclusion
If you recognize yourself in these pitfalls, that’s a good sign.
They don’t mean you’re bad at Python. They mean you’re an experienced developer, just in a different ecosystem.
Having a guide in this process, knowing exactly where you could fall into traps, is a major accelerator in your learning.
In From PHP to Python, these differences are explained step by step, with side-by-side examples designed specifically for PHP developers.