March 2025
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
...
No boilerplate, always straightforward:
print("Hello, World!")
# Variables (no type declarations)
name = "Python" # str
age = 30 # int
price = 19.99 # float
is_available = True # bool
# Type checking
print(type(name)) # <class 'str'>
print(isinstance(name, str)) # True => Safer!
Python is dynamically typed but strongly typed
# Python - Raises TypeError
>>> a = 42
>>> b = "answer"
>>> a + b
Traceback (most recent call last):
TypeError: unsupported operand type(s) for +: 'int' and 'str'
// JavaScript - Implicit conversion
> a = 42
42
> b = 'answer'
'answer'
> a + b
'42answer'
Container data types to store multiple items
Mutable arrays (not linked lists)
l = ["a", "b", "c"]
l.append("d") # Add one element
l.extend(["e", "f"]) # Add multiple elements
print(l) # ['a', 'b', 'c', 'd', 'e', 'f']
print(l[0]) # 'a'
print(l[1:3]) # ['b', 'c'] - Range indexing
print(l[-1]) # 'f' - Negative indexing
Like lists but immutable
t = (1, 2, 3) # Defines a 3 elements tuple
print(t) # (1, 2, 3)
t = (1,) # Defines a 1 element tuple
print(t) # (1,)
t = 1, 2, 3 # Defines the same 3 elements tuple
print(t) # (1, 2, 3)
t = 1, # Valid 1 element tuple
print(t) # (1,)
Key-value pairs like HashMap (O(1) average operations)
AGE = "age"
user = {
"name": "Alice",
AGE: 30, # Variable as key name
"languages": ["Python", "Java"],
}
print(user["name"]) # prints: Alice
user = {}
>>> user["a"]
Traceback (most recent call last):
KeyError: "a"
>>> print(a.get(3))
None
>>> print(a.get(3, 42))
42
Hash table without values (unique elements)
unique_numbers = {1, 2, 3, 3, 4} # Will contain 1, 2, 3, 4
print(1 in unique_numbers) # True in nearly constant time
More collections in collections module
x = 10
if x > 5:
print("x is greater than 5")
elif x == 5:
print("x equals 5")
else:
print("x is less than 5")
x = 10
print("greater" if x > 5 else "equals" if x == 5 else "less")
languages = ["french", "english", "italian"]
for language in languages:
print(language)
for i in range(5): # 0 to 4
print(i)
# List comprehension
[x for x in range(10)] # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Dictionary comprehension
{k: v for k, v in pairs} # Creates a dictionary
# Set comprehension
{x for x in items} # Creates a set
# Generator expression
(x for x in range(10)) # Creates a generator
# Dict comprehension example
the_dict = {"a": 3, "b": 4}
print([k * 2 for k in the_dict.keys()]) # ["aa", "bb"]
print([v * 2 for v in the_dict.values()]) # [6, 8]
print({k * 2: v * 2 for k, v in the_dict.items()})
# {'aa': 6, 'bb': 8}
# Creating lists with loop logic
squares = [x * x for x in range(10)]
# With conditions
even_squares = [x * x for x in range(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]
count = 0
while count < 5:
print(count)
count += 1
# Defining functions
def greet(name, greeting="Hello"):
"""Greet a person with a custom greeting.""" # Docstring
return f"{greeting}, {name}!"
# Calling functions
print(greet("Java Developer")) # Default greeting
print(greet("Python Novice", "Welcome")) # Custom greeting
# Variable arguments
def sum_all(*numbers):
return sum(numbers)
print(sum_all(1, 2, 3, 4)) # 10
Modify behavior of functions or classes without changing source code
# Basic decorator example
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Run before the function is called.")
result = func(*args, **kwargs)
print("Run after the function is called.")
return result
return wrapper
@my_decorator # <==== say_hello = my_decorator(say_hello)
def say_hello(name):
return f"Hello, {name}!"
print(say_hello("Python Developer"))
def repeat(n=1):
def decorator(func):
def wrapper(*args, **kwargs):
result = None
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name): # This prints "Hello, World!" three times
print(f"Hello, {name}!")
Common uses: authentication, logging, caching, timing, validation...
class Person:
species = "Human"
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return (
f"Hi, I'm {self.name} "
f"and I'm {self.age} years old."
)
@staticmethod
def get_species_info_hardcoded():
return "Humans are social beings."
@classmethod
def get_species_info_from_class(cls):
return f"{cls.species} are social beings."
alice = Person("Alice", 30)
print(alice.introduce())
print(Person.get_species_info_hardcoded())
print(Person.get_species_info_from_class())
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some sound"
class Dog(Animal):
def speak(self):
return "Woof!"
# Create an instance of the subclass
dog = Dog("Rex")
print(dog.name)
print(dog.speak())
# Multiple inheritance
class Swimmer:
def swim(self):
return "Swimming"
class Duck(Animal, Swimmer):
def speak(self):
return "Quack!"
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def make_sound(self):
"""This method must be implemented by subclasses"""
pass
def eat(self):
print("Eating...")
class Dog(Animal):
def make_sound(self):
return "Woof!"
# This works
dog = Dog()
print(dog.make_sound()) # Woof!
# This would raise TypeError
# animal = Animal()
Anonymous single-expression functions
add = lambda a, b: a + b
print(add(5, 3)) # 8
# Useful with higher-order functions
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x**2, numbers))
print(squares) # [1, 4, 9, 16, 25]
class SpecificKindOfValueError(ValueError):
"""Defines an Exception inheriting from ValueError"""
pass
try:
do_something()
except SpecificKindOfValueError:
print("Here, handle SpecificKindOfValueError")
except ValueError:
print("Not a valid number")
except (TypeError, KeyError):
print("Type or key error occurred")
except Exception as e:
print(f"Other error: {e}")
else:
print("No exceptions occurred")
finally:
print("This always executes")
# Single import
import math
print(math.sqrt(16)) # 4.0
# Import specific functions
from datetime import datetime
print(datetime.now())
# Import with alias
import numpy as np
arr = np.array([1, 2, 3])
Ensure specific logic executes before and after your code
# File handling (automatically closes file)
with open("example.txt", "w") as file:
file.write("Hello, Python!")
Using async/await for concurrent operations
# Core concepts (sample code - not for execution)
result = await some_coroutine()
async def my_async_function():
pass
results = await asyncio.gather(coro1(), coro2(), coro3())
task = asyncio.create_task(some_coroutine())
async for item in async_iterable:
process(item)
Entering async context
# Run coroutine from non-async context
asyncio.run(main_coroutine())
coro = main_coroutine(a, b) # Coroutine with parameters
print(coro) # <coroutine object coro at 0x7f1bcc2fb940>
asyncio.run(coro) # Pass coroutine between calls
Consider Trio for better async management
def greeting(name: str) -> str:
return f"Hello, {name}"
age: int = 30
# Types aren't enforced at runtime
a: int = "0" # No error
print(type(a)) # <class 'str'>
Benefits: Better code documentation, IDE support, static checking
value: Regular public attribute/method_value: "Private by convention" (not enforced)__value: Name mangling (becomes _ClassName__value)__value__: Special methods controlled by Python (magic/dunder methods)value_: Avoid conflicts with Python keywordsClaude (Anthropic) is excellent for learning Python concepts