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 |
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
Top comments (1)
Thank you for sharing this article.
I now have a deeper understanding of Python.