Immutability (especially for strings and tuples):

PHP Python
Strings and arrays are mutable. You can change a character in a string by its index ($str[0] = 'H';) or directly modify array elements. Strings and tuples are immutable. Once created, their content cannot be changed. Operations that seem to modify them actually create new objects.
PHP devs might try to assign to a string index (my_string[0] = 'h') in Python and get a TypeError. We need to learn patterns like string concatenation/slicing to build new strings or converting tuples to lists for modification and then back if needed. This impacts performance considerations and how data is handled.

“Everything is an Object” and Magic Methods (Dunder Methods):

PHP Python
PHP has a robust OOP system, but its “everything is an object” philosophy isn’t as pervasive as Python’s. PHP’s magic methods (__construct, __get, etc.) serve similar purposes but Python’s are more extensive and integral to the language’s protocols. In Python, functions, classes, numbers, strings—everything is an object with attributes and methods. Magic methods like __init__, __str__, __len__, __getitem__ allow objects to emulate built-in types and integrate with Python’s syntax.
Grasping how deeply objects are integrated and how to leverage magic methods to create idiomatic Python classes (e.g., making your object iterable by implementing __iter__ and __next__) is a learning curve.

Error Handling Philosophy (EAFP vs. LBYL):

PHP Python
PHP developers often use functions like isset(), empty(), array_key_exists(), file_exists() before performing an operation (Look Before You Leap - LBYL). Python often encourages “Easier to Ask for Forgiveness than Permission” (EAFP), meaning you try an operation and catch exceptions if it fails (try...except).
Shifting from proactive checking to reactive error handling via exceptions can feel less safe initially, but it’s often more readable and efficient in Python for many cases.

Mutable Default Arguments in Functions:

PHP Python
Default arguments in PHP functions are re-evaluated each time the function is called (if the argument isn’t provided). Default arguments are evaluated once when the function is defined. If a default argument is a mutable object (like a list or dictionary), and it’s modified in the function, that modification persists across subsequent calls.
This is a classic Python “gotcha.” PHP developers would not expect
def foo(a, my_list=[]):
	my_list.append(a)
	print(my_list)

to behave the way it does across multiple calls. The standard Python idiom is to use None as a default and create the mutable object inside the function.