Basic Python
1. Python Type Checking (Guide)
Python is dynamically typed, but you can use type hints for better readability, tooling, and static analysis.
✅ Type Hinting Example
def greet(name: str, age: int) -> str:
return f"Hello {name}, you are {age} years old."
✅ Type Hinting for Complex Structures
Python uses the typing
module to define complex types.
🔹 Imports you'll need:
from typing import Dict, List, Tuple, Any
🔸 Example 1: Function that returns a list of dictionaries
from typing import Dict, List
def transform_data(data: Dict[str, int]) -> List[Dict[str, str]]:
"""
Converts a dict of {key: int} to a list of dicts with stringified values.
Example:
{"a": 1, "b": 2} → [{"key": "a", "value": "1"}, {"key": "b", "value": "2"}]
"""
return [{"key": k, "value": str(v)} for k, v in data.items()]
✅ Type Breakdown
-
Dict[str, int]
: Input is a dictionary with string keys and integer values. -
List[Dict[str, str]]
: Output is a list of dictionaries with string keys and string values.
🔸 Example 2: Function that returns a list of tuples
from typing import Dict, List, Tuple
def dict_to_tuples(data: Dict[str, int]) -> List[Tuple[str, int]]:
"""
Converts a dict to a list of key-value tuples.
Example:
{"a": 1, "b": 2} → [("a", 1), ("b", 2)]
"""
return list(data.items())
✅ Type Breakdown
-
List[Tuple[str, int]]
: Output is a list of tuples, each containing a string and an integer.
🔸 Example 3: Mixed or Unknown Types
If your dictionary or list contains mixed types, use Any
:
from typing import Dict, List, Any
def process(data: Dict[str, Any]) -> List[Dict[str, Any]]:
return [{"key": k, "value": v} for k, v in data.items()]
🔹 Bonus: Using TypedDict
for Structured Dicts
If your dicts have a fixed structure, you can define a TypedDict
:
from typing import TypedDict, List
class User(TypedDict):
name: str
age: int
def get_users() -> List[User]:
return [{"name": "Ashutosh", "age": 30}, {"name": "Assaf", "age": 40}]
Using TypedDict
in Python provides several advantages, especially when you're working with dictionaries that have a fixed structure — like JSON-like data or configuration objects. Let's explore this in detail.
✅ What is TypedDict
?
TypedDict
is a feature from the typing
module that lets you define the expected structure of a dictionary — including the types of its keys and values.
🔹 Example:
from typing import TypedDict
class User(TypedDict):
name: str
age: int
is_active: bool
def get_user() -> User:
return {"name": "Ashutosh", "age": 30, "is_active": True}
✅ Advantages of Using TypedDict
1. Type Safety
You get static type checking with tools like mypy
, pyright
, or IDEs (VS Code, PyCharm).
user: User = {"name": "Ashutosh", "age": "thirty", "is_active": True} # ❌ Type error
2. Better IDE Support
- Autocompletion for keys
- Inline type hints
- Error highlighting
3. Self-Documenting Code
It’s clear what structure the dictionary should have — no need to guess or read through comments.
def create_user(user: User) -> None:
...
4. Improved Refactoring
If you change the structure of User
, type checkers will help you find all affected code.
5. Cleaner JSON Handling
When working with APIs, you often parse JSON into dictionaries. TypedDict
helps define the expected shape.
import json
def parse_user(json_str: str) -> User:
return json.loads(json_str)
🔸 Optional and Required Keys
You can define optional keys using total=False
:
class PartialUser(TypedDict, total=False):
name: str
age: int
🔸 Comparison: TypedDict
vs Raw Dict[str, Any]
Feature | Dict[str, Any] |
TypedDict |
---|---|---|
Type safety | ❌ No | ✅ Yes |
IDE support | ❌ Limited | ✅ Autocompletion, hints |
Documentation | ❌ Implicit | ✅ Explicit structure |
Error detection | ❌ Runtime only | ✅ Static analysis |
✅ When Should You Use TypedDict
?
- When working with structured data (e.g., JSON from APIs)
- When you want type-safe dictionaries
- When building data models without needing full classes
- When you want to avoid overengineering with
dataclasses
orpydantic
Absolutely! Let's dive into how to use TypedDict
with required and optional keys in Python, with clear examples and explanations.
✅ What is TypedDict
?
TypedDict
allows you to define the structure of a dictionary with specific key names and value types. It’s part of the typing
module and is especially useful when working with structured data like JSON.
🔹 Required vs Optional Keys
By default, all keys in a TypedDict
are required. You can make keys optional by using total=False
or by using NotRequired
(Python 3.11+).
✅ Example 1: Required Keys Only
from typing import TypedDict
class User(TypedDict):
name: str
age: int
def get_user() -> User:
return {"name": "Ashutosh", "age": 30} # ✅ All keys are required
If you omit a key:
return {"name": "Ashutosh"} # ❌ mypy will complain: 'age' is missing
✅ Example 2: Optional Keys with total=False
from typing import TypedDict
class PartialUser(TypedDict, total=False):
name: str
age: int
email: str
def get_partial_user() -> PartialUser:
return {"name": "Ashutosh"} # ✅ Only 'name' is provided
Here, all keys are optional — you can include any, all, or none.
✅ Example 3: Mixed Required and Optional (Python 3.11+)
from typing import TypedDict, NotRequired
class UserProfile(TypedDict):
username: str
age: int
email: NotRequired[str]
phone: NotRequired[str]
def get_profile() -> UserProfile:
return {
"username": "ashu_dev",
"age": 30,
"email": "ashu@example.com"
} # ✅ 'phone' is optional
This is the most flexible and readable way to define mixed key requirements.
🔸 Summary
Method | Python Version | Behavior |
---|---|---|
TypedDict |
3.8+ | All keys required |
TypedDict, total=False |
3.8+ | All keys optional |
NotRequired |
3.11+ | Selectively optional keys |
🧩 1. BaseModel
(from pydantic
)
✅ Features
- Runtime data validation and type enforcement
- Automatic serialization/deserialization (e.g.,
.json()
,.dict()
) - Useful for APIs, config files, and anywhere you need robust data models
- Supports default values, validators, and nested models
🧪 Example:
from pydantic import BaseModel, Field
class User(BaseModel):
name: str
age: int = Field(..., ge=0) # age must be >= 0
🔍 Behavior:
- If you pass invalid data, it raises a
ValidationError
.
User(name="Alice", age=-5) # ❌ Raises error due to age < 0
🧩 2. TypedDict
(from typing
or typing_extensions
)
✅ Features
- Static type checking only (via tools like MyPy, Pyright)
- No runtime validation
- Lightweight and faster
- Good for defining dict-like structures with known keys
🧪 Example:
from typing import TypedDict
class UserDict(TypedDict):
name: str
age: int
🔍 Behavior:
- No runtime checks — it's just a hint for linters and IDEs.
user: UserDict = {"name": "Alice", "age": -5} # ✅ No error at runtime
🆚 Summary Comparison
Feature |
BaseModel (Pydantic) |
TypedDict (typing) |
---|---|---|
Runtime validation | ✅ Yes | ❌ No |
Type hints | ✅ Yes | ✅ Yes |
Serialization | ✅ .dict() , .json()
|
❌ Manual |
Performance | ❌ Slower | ✅ Faster |
Use case | APIs, configs, data models | Lightweight dicts |
Nested models | ✅ Supported | ❌ Manual nesting |
🧠 When to Use What?
-
Use
BaseModel
when:- You need data validation
- You're building APIs (e.g., FastAPI)
- You want structured, reliable data
-
Use
TypedDict
when:- You want lightweight type hints
- You don’t need runtime validation
- You're working with plain dicts and want IDE/type checker support
2. Duck Typing in Python
Duck Typing is a concept where the type of an object is determined by its behavior (methods/attributes), not its actual class.
“If it walks like a duck and quacks like a duck, it’s a duck.”
✅ Example
class Duck:
def quack(self):
print("Quack!")
class Person:
def quack(self):
print("I'm pretending to be a duck!")
def make_it_quack(thing):
thing.quack()
make_it_quack(Duck()) # Quack!
make_it_quack(Person()) # I'm pretending to be a duck!
🔸 Benefits
- Flexibility
- Decoupled code
- Easier testing and mocking
>>> numbers = [1, 2, 3]
>>> person = ("Jane", 25, "Python Dev")
>>> letters = "abc"
>>> ordinals = {"one": "first", "two": "second", "three": "third"}
>>> even_digits = {2, 4, 6, 8}
>>> collections = [numbers, person, letters, ordinals, even_digits]
>>> for collection in collections:
... for value in collection:
... print(value)
...
1
2
3
Jane
25
Python Dev
a
b
c
one
two
three
8
2
4
6
✅ General Operations on Built-in Collections
Operation | Lists | Tuples | Strings | Ranges | Dictionaries | Sets |
---|---|---|---|---|---|---|
Iteration | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Indexing | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Slicing | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Concatenating | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
Finding length | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Reversing | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Sorting | ✅ | ✅* | ✅* | ❌ | ❌ | ✅ |
* Sorting tuples and strings returns a new sorted list, not a sorted tuple/string.
🔹 Detailed Examples
✅ Iteration
for x in [1, 2, 3]: print(x)
for x in (1, 2, 3): print(x)
for x in "abc": print(x)
for x in range(3): print(x)
for k in {"a": 1, "b": 2}: print(k)
for x in {1, 2, 3}: print(x)
✅ Indexing
print([1, 2, 3][0]) # 1
print((1, 2, 3)[1]) # 2
print("hello"[2]) # 'l'
print(range(5)[3]) # 3
# Dictionaries and sets do not support indexing
✅ Slicing
print([1, 2, 3][1:]) # [2, 3]
print((1, 2, 3)[1:]) # (2, 3)
print("hello"[1:4]) # 'ell'
print(range(10)[2:5]) # range(2, 5)
✅ Concatenating
print([1, 2] + [3, 4]) # [1, 2, 3, 4]
print((1, 2) + (3, 4)) # (1, 2, 3, 4)
print("ab" + "cd") # 'abcd'
# Sets use union, not +
✅ Finding Length
print(len([1, 2, 3]))
print(len((1, 2)))
print(len("hello"))
print(len(range(10)))
print(len({"a": 1, "b": 2}))
print(len({1, 2, 3}))
✅ Reversing
print(list(reversed([1, 2, 3])))
print(tuple(reversed((1, 2, 3))))
print("".join(reversed("abc")))
print(list(reversed(range(5))))
# dict and set are unordered → not reversible directly
✅ Sorting
print(sorted([3, 1, 2])) # [1, 2, 3]
print(sorted((3, 1, 2))) # [1, 2, 3]
print(sorted("cba")) # ['a', 'b', 'c']
print(sorted({3, 1, 2})) # [1, 2, 3]
# dicts can be sorted by keys or values manually
🔸 Notes
- Dictionaries are unordered mappings — you can iterate over keys, values, or items, but not index or slice.
- Sets are unordered collections — no indexing or slicing, but support union, intersection, etc.
- Tuples and strings are immutable — operations like sorting or reversing return new objects.
3. Sets in Python
Sets are unordered collections of unique elements.
✅ Basic Usage
a = {1, 2, 3}
b = {3, 4, 5}
# Union
print(a | b) # {1, 2, 3, 4, 5}
# Intersection
print(a & b) # {3}
# Difference
print(a - b) # {1, 2}
# Symmetric Difference
print(a ^ b) # {1, 2, 4, 5}
🔸 Set Comparison
a == b # Checks if sets are equal
a.issubset(b)
a.issuperset(b)
4. Other Data Types
✅ List
fruits = ["apple", "banana", "cherry"]
fruits.append("orange")
✅ Dictionary
person = {"name": "Ashutosh", "age": 30}
print(person["name"])
✅ Tuple
point = (10, 20)
✅ String
text = "Hello, World!"
5. Why Use Dictionary When We Have JSON?
🔹 JSON is a data format (text-based).
🔹 Dictionary is a Python data structure.
You use dictionaries to work with data in Python, and convert them to/from JSON when communicating externally (e.g., APIs, files).
✅ Example
import json
data = {"name": "Ashutosh", "age": 30}
json_str = json.dumps(data) # Convert dict to JSON string
parsed = json.loads(json_str) # Convert JSON string back to dict
6. sorted()
vs .sort()
✅ sorted()
- Returns a new sorted list
- Works on any iterable
- Doesn’t modify the original
nums = [3, 1, 2]
new_nums = sorted(nums)
✅ .sort()
- Sorts the list in-place
- Only works on lists
- Returns
None
nums = [3, 1, 2]
nums.sort()
🔸 Why Both?
-
sorted()
is functional and flexible. -
.sort()
is efficient for large lists when mutation is acceptable.
✅ Summary
Concept | Key Takeaway |
---|---|
Type Checking | Improves safety and tooling |
Duck Typing | Behavior > Type |
Sets | Unique, unordered, fast ops |
Lists, Dicts, Tuples | Core data structures |
Dict vs JSON | Dict = Python, JSON = format |
sorted() vs .sort()
|
New list vs in-place sort |
Why we need init?
1. Purpose of init.py
- init.py file in a directory tells Python that the directory should be treated as a package.
- This allows you to import modules from that directory using package syntax, e.g.:
- Without init.py, Python (pre-3.3) would not recognize the folder as a package, and imports might fail.
- The file can be empty, or it can contain initialization code for the package.
Starting with Python 3.3 and above (including Python 3.11), you can technically remove the init.py file from a package directory, and Python will still recognize it as a package due to "implicit namespace packages."
However, you should keep init.py:
When you add an init.py file to a package directory, it does not create a global namespace. Instead, it defines a package namespace.
All modules and submodules inside that package share the package’s namespace (e.g., tools_package.audio.utils).
This keeps your package’s contents organized and separate from the global namespace, preventing naming conflicts.
Module vs Package
Module: A single Python file (e.g., utils.py). You import it like import utils or from utils import foo.
Package: A directory containing an init.py file and (usually) multiple modules (e.g., audio/ with init.py and utils.py).
2. What is pycache?
When you run or import Python code, Python compiles .py files to bytecode for faster execution.
The compiled bytecode files are stored in the pycache directory, with names like utils.cpython-311.pyc.
cpython-311 means the file was compiled by CPython version 3.11.
These .pyc files are used by Python to speed up future imports of the module.
3. PIP (pip installs packages) (package Manager for Python):-
Using pip in a Python Virtual Environment
$ python -m venv venv/
$ source venv/bin/activate
(venv) $ pip3 --version
pip 24.2 from .../python3.12/site-packages/pip (python 3.12)
(venv) $ pip --version
pip 24.2 from .../python3.12/site-packages/pip (python 3.12)
- Here you initialize a virtual environment named venv by using Python’s built-in venv module.
- After running the command above, Python creates a directory named venv/ in your current working directory.
Then, you activate the virtual environment with the source command. - The parentheses (()) surrounding your venv name indicate that you successfully activated the virtual environment.
Finally, you check the version of the pip3 and pip executables inside your activated virtual environment.
Both point to the same pip module, so once your virtual environment is activated, you can use either pip or pip3.
Installing Packages With pip
install a package
pip install package_name
uninstall a package
pip uninstall package_name
list installed packages
pip list
install a specific version of a package
pip install package_name==version_number
upgrade a package
pip install --upgrade package_name
show package information
pip show package_name
search for packages
pip search package_name
Using Requirements Files
requirement.text package
pip install -r requirements.txt
make a requirements.txt file from the packages installed
pip freeze > requirements.txt
certifi==x.y.z
charset-normalizer==x.y.z
idna==x.y.z
requests>=x.y.z, <3.0
urllib3==x.y.z
Changing the version specifier for the requests package ensures that any version greater than or equal to 3.0 doesn’t get installed.
very important
We can create 3 files
- requirements_dev.txt (For Development)
- requirements_prod.txt (For Production)
- requirements_lock.txt (After Development complete --> We will freeze it)
Uninstalling Packages With pip
pip show <package Name>
Name: requests
Version: 2.32.3
Summary: Python HTTP for Humans.
Location: .../python3.12/site-packages
Requires: certifi, idna, charset-normalizer, urllib3
Required-by:
- Notice the last two fields, Requires and Required-by. The show command tells you that requests requires certifi, idna, charset-normalizer, and urllib3. You probably want to uninstall those too. Notice that requests isn’t required by any other package.
- So it’s safe to uninstall it.
pip uninstall certifi urllib3 -y
Here you uninstall urllib3. Using the -y switch, you suppress the confirmation dialog asking you if you want to uninstall this package.
In a single call, you can specify all the packages that you want to uninstall
To create a virtual environment in Python, you can use the built-in venv
module. A virtual environment is a self-contained directory that contains a Python installation for a particular version of Python, plus a number of additional packages.
✅ Steps to Create and Use a Virtual Environment
🔧 1. Create the Virtual Environment
python -m venv myenv
-
python
: Usepython3
if you're on macOS/Linux andpython
points to Python 2. -
myenv
: This is the name of the virtual environment folder. You can name it anything.
📂 2. Activate the Virtual Environment
- Windows:
myenv\Scripts\activate
- macOS/Linux:
source myenv/bin/activate
Once activated, your terminal prompt will change to show the environment name, e.g., (myenv)
.
📦 3. Install Packages Inside the Virtual Environment
pip install requests
This installs requests
only inside myenv
, not globally.
❌ 4. Deactivate the Virtual Environment
deactivate
This returns you to the global Python environment.
Top comments (1)
Thank you for sharing this article.
I now have a deeper understanding of Python.