Python coding
π Python Tip #88: contextvars Ever needed a variable whose value is local to the current task or execution context? Python's contextvars module is especially useful in asynchronous applications where multiple tasks can be running at the same time. --- β¦
π Python Tip #89:
Sometimes you have a few related values and want to access them with dot notation instead of dictionary brackets.
---
Importing
from types import SimpleNamespace
---
Basic Example
from types import SimpleNamespace
user = SimpleNamespace(
name="Yehya",
age=16,
language="Python"
)
print(user.name)
print(user.age)
print(user.language)
Output:
Yehya
16
Python
Instead of:
user["name"]
you can use:
user.name
---
Changing Values
The attributes can be changed normally:
user.age = 17
print(user.age)
Output:
17
You can also add new attributes:
user.country = "Ethiopia"
print(user.country)
Output:
Ethiopia
---
Creating One From a Dictionary
You can also convert dictionary data into a namespace:
from types import SimpleNamespace
data = {
"name": "Sara",
"score": 95
}
student = SimpleNamespace(**data)
print(student.name)
print(student.score)
Output:
Sara
95
---
Converting Back to a Dictionary
Use
data = vars(student)
print(data)
Output:
{'name': 'Sara', 'score': 95}
---
When Is It Useful?
β Small data containers
β Configuration objects
β Temporary structured data
β Making dictionary data easier to access
β Quick prototypes
It's not a replacement for
---
Real Example
from types import SimpleNamespace
server = SimpleNamespace(
host="localhost",
port=8000,
debug=True
)
print(f"Server: {server.host}:{server.port}")
print("Debug:", server.debug)
Output:
Server: localhost:8000
Debug: True
---
Challenge:
Create a
name = "Yehya"
grade = 11
subject = "Python"
Then:
1. Print the student's name.
2. Change the grade to 12.
3. Add a new attribute called
4. Print all the information.
Can you do it without using a dictionary?
types.SimpleNamespaceSometimes you have a few related values and want to access them with dot notation instead of dictionary brackets.
SimpleNamespace from Python's types module gives you a simple object for storing attributes.---
Importing
SimpleNamespacefrom types import SimpleNamespace
---
Basic Example
from types import SimpleNamespace
user = SimpleNamespace(
name="Yehya",
age=16,
language="Python"
)
print(user.name)
print(user.age)
print(user.language)
Output:
Yehya
16
Python
Instead of:
user["name"]
you can use:
user.name
---
Changing Values
The attributes can be changed normally:
user.age = 17
print(user.age)
Output:
17
You can also add new attributes:
user.country = "Ethiopia"
print(user.country)
Output:
Ethiopia
---
Creating One From a Dictionary
You can also convert dictionary data into a namespace:
from types import SimpleNamespace
data = {
"name": "Sara",
"score": 95
}
student = SimpleNamespace(**data)
print(student.name)
print(student.score)
Output:
Sara
95
---
Converting Back to a Dictionary
Use
vars():data = vars(student)
print(data)
Output:
{'name': 'Sara', 'score': 95}
---
When Is It Useful?
SimpleNamespace can be handy for:β Small data containers
β Configuration objects
β Temporary structured data
β Making dictionary data easier to access
β Quick prototypes
It's not a replacement for
dataclass when you need a more structured data model.---
Real Example
from types import SimpleNamespace
server = SimpleNamespace(
host="localhost",
port=8000,
debug=True
)
print(f"Server: {server.host}:{server.port}")
print("Debug:", server.debug)
Output:
Server: localhost:8000
Debug: True
---
Challenge:
Create a
SimpleNamespace called student with:name = "Yehya"
grade = 11
subject = "Python"
Then:
1. Print the student's name.
2. Change the grade to 12.
3. Add a new attribute called
score.4. Print all the information.
Can you do it without using a dictionary?
Python coding
π Python Tip #89: types.SimpleNamespace Sometimes you have a few related values and want to access them with dot notation instead of dictionary brackets. SimpleNamespace from Python's types module gives you a simple object for storing attributes. --- Importingβ¦
π Python Tip #90:
If a function keeps receiving the same arguments and doing the same expensive calculation again and again, caching can save a lot of work.
Python's
---
Importing
from functools import cache
---
Basic Example
from functools import cache
@cache
def square(number):
print("Calculating...")
return number ** 2
print(square(5))
print(square(5))
print(square(10))
Output:
Calculating...
25
25
Calculating...
100
Notice that
The second
---
A More Useful Example
from functools import cache
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10))
Output:
55
Without caching, recursive Fibonacci calculations repeat many of the same calculations.
With
---
Checking the Cache
You can inspect the cache with:
fibonacci.cache_info()
For example, you might see:
CacheInfo(hits=8, misses=11, maxsize=None, currsize=11)
---
Clearing the Cache
If you want to remove all stored results:
fibonacci.cache_clear()
After that, the function will calculate the results again when called.
---
Important Note
That means stored results can continue growing as new arguments are used.
If you need a limited cache size,
---
Why use
β Avoids repeating expensive calculations
β Extremely useful with recursive functions
β Simple to add with
β Can make repeated function calls much faster
β Part of Python's standard library
---
Challenge:
Create a cached function:
@cache
def factorial(n):
It should calculate:
5! = 120
Then call:
factorial(5)
factorial(5)
factorial(6)
Finally, use:
factorial.cache_info()
and check how many cache hits you get.
functools.cache()If a function keeps receiving the same arguments and doing the same expensive calculation again and again, caching can save a lot of work.
Python's
functools.cache() automatically remembers previous results and reuses them when the same arguments appear again.---
Importing
cachefrom functools import cache
---
Basic Example
from functools import cache
@cache
def square(number):
print("Calculating...")
return number ** 2
print(square(5))
print(square(5))
print(square(10))
Output:
Calculating...
25
25
Calculating...
100
Notice that
"Calculating..." only appears once for 5.The second
square(5) gets the stored result instead of running the function again.---
A More Useful Example
from functools import cache
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10))
Output:
55
Without caching, recursive Fibonacci calculations repeat many of the same calculations.
With
@cache, previously calculated results are reused.---
Checking the Cache
You can inspect the cache with:
fibonacci.cache_info()
For example, you might see:
CacheInfo(hits=8, misses=11, maxsize=None, currsize=11)
hits = results retrieved from the cache.misses = calculations that had to actually run.currsize = number of stored results.---
Clearing the Cache
If you want to remove all stored results:
fibonacci.cache_clear()
After that, the function will calculate the results again when called.
---
Important Note
@cache is basically an unlimited cache.That means stored results can continue growing as new arguments are used.
If you need a limited cache size,
functools.lru_cache() is usually more appropriate.---
Why use
cache()?β Avoids repeating expensive calculations
β Extremely useful with recursive functions
β Simple to add with
@cacheβ Can make repeated function calls much faster
β Part of Python's standard library
---
Challenge:
Create a cached function:
@cache
def factorial(n):
It should calculate:
5! = 120
Then call:
factorial(5)
factorial(5)
factorial(6)
Finally, use:
factorial.cache_info()
and check how many cache hits you get.