Python 魔法方法的工作原理:实用指南(三)
以前的文章我们讲了python中对象表示、运算符等魔法方法的用法,下面我们继续深入探索容器、资源管理、性能方面的魔法方法。
容器方法(类似列表、字典对象)
当你需要自定义存储和检索数据的对象时,此方式可以减少大量编码逻辑。
列表对象定义
方法 | 描述 | 示例用法 |
__len__ | 返回容器的长度 | len(obj) |
__getitem__ | 允许使用obj[key] | obj[0] |
__setitem__ | 允许分配obj[key] = value | obj[0] = 42 |
__delitem__ | 允许删除del obj[key] | del obj[0] |
__iter__ | 返回容器的迭代器 | for item in obj: |
__contains__ | 实现in运算符 | 42 in obj |
字典对象定义
方法 | 描述 | 示例用法 |
__getitem__ | 按键获取值 | obj["key"] |
__setitem__ | 通过键设置值 | obj["key"] = value |
__delitem__ | 删除键值对 | del obj["key"] |
__len__ | 获取键值对的数量 | len(obj) |
__iter__ | 遍历键 | for key in obj: |
__contains__ | 检查键是否存在 | "key" in obj |
容器魔法方法的实例
定义一个管理时间的字典容器,实现功能如下:
缓存使用OrderedDict来存储键值对以及时间戳。
每个值都存储为一个元组(value, timestamp)。访问某个值时,我们会检查它是否已过期。
该类实现了所有必要的方法,使其行为像字典一样:
- __getitem__:检索值并检查到期日期
- __setitem__:存储带有当前时间戳的值
- __delitem__:删除条目
- __len__:返回未过期条目的数量
- __iter__:迭代未过期的键
- __contains__:检查键是否存在
import time
from collections import OrderedDict
class ExpiringCache:
def __init__(self, max_age_seconds=60):
self.max_age = max_age_seconds
self._cache = OrderedDict() # {key: (value, timestamp)}
def __getitem__(self, key):
if key not in self._cache:
raise KeyError(key)
value, timestamp = self._cache[key]
if time.time() - timestamp > self.max_age:
del self._cache[key]
raise KeyError(f"Key '{key}' has expired")
return value
def __setitem__(self, key, value):
self._cache[key] = (value, time.time())
self._cache.move_to_end(key) # Move to end to maintain insertion order
def __delitem__(self, key):
del self._cache[key]
def __len__(self):
self._clean_expired() # Clean expired items before reporting length
return len(self._cache)
def __iter__(self):
self._clean_expired() # Clean expired items before iteration
for key in self._cache:
yield key
def __contains__(self, key):
if key not in self._cache:
return False
_, timestamp = self._cache[key]
if time.time() - timestamp > self.max_age:
del self._cache[key]
return False
return True
def _clean_expired(self):
"""Remove all expired entries from the cache."""
now = time.time()
expired_keys = [
key for key, (_, timestamp) in self._cache.items()
if now - timestamp > self.max_age
]
for key in expired_keys:
del self._cache[key]
使用示例
# Create a cache with 2-second expiration
cache = ExpiringCache(max_age_seconds=2)
# Store some values
cache["name"] = "Vivek"
cache["age"] = 30
# Access values
print("name" in cache) # Output: True
print(cache["name"]) # Output: Vivek
print(len(cache)) # Output: 2
# Wait for expiration
print("Waiting for expiration...")
time.sleep(3)
# Check expired values
print("name" in cache) # Output: False
try:
print(cache["name"])
except KeyError as e:
print(f"KeyError: {e}") # Output: KeyError: 'name'
print(len(cache)) # Output: 0
属性设置
属性访问方法允许您控制对象如何处理属性的获取、设置和删除。这对于实现属性、验证和日志记录尤其有用。
getattr 和 getattribute
Python 提供了两种控制属性访问的方法:
- __getattr__:仅当属性查找失败时调用(即,当属性不存在时)
- __getattribute__:每次访问属性时都会调用,即使属性已经存在。
setattr 和 delattr
类似地,您可以控制如何设置和删除属性:
- __setattr__:设置属性时调用
- __delattr__:删除属性时调用
此内容过于简单不在编写示例。
上下文管理器
上下文管理器是 Python 中一个强大的功能,可以帮助你正确地管理资源。即使发生错误,它们也能确保资源被正确地获取和释放。with语句是使用上下文管理器的最常见方式。
进入和退出
要创建上下文管理器,您需要实现两个魔术方法:
- __enter__:进入with块时调用。它应该返回要管理的资源。
- __exit__:退出块时调用with,即使发生异常。它应该处理清理工作。
该__exit__方法接收三个参数:
- exc_type:异常的类型(如果有)
- exc_val:异常实例(如果有)
- exc_tb:回溯(如果有)
数据库管理示例
import sqlite3
import logging
# Set up logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
class DatabaseConnection:
def __init__(self, db_path):
self.db_path = db_path
self.connection = None
self.cursor = None
def __enter__(self):
logging.info(f"Connecting to database: {self.db_path}")
self.connection = sqlite3.connect(self.db_path)
self.cursor = self.connection.cursor()
return self.cursor
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
logging.error(f"An error occurred: {exc_val}")
self.connection.rollback()
logging.info("Transaction rolled back")
else:
self.connection.commit()
logging.info("Transaction committed")
if self.cursor:
self.cursor.close()
if self.connection:
self.connection.close()
logging.info("Database connection closed")
# Return False to propagate exceptions, True to suppress them
return False
创建对象时候使用几个魔法方法
__call__当你尝试创建对象的时候该方法就会被调用。
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
# Create instances that behave like functions
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # Output: 10
print(triple(5)) # Output: 15
__new__方法在创建之前被调用__init__,负责创建并返回该类的新实例。这对于实现单例或不可变对象等模式非常有用。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name=None):
# This will be called every time Singleton() is called
if name is not None:
self.name = name
# Usage
s1 = Singleton("Vivek")
s2 = Singleton("Wewake")
print(s1 is s2) # Output: True
print(s1.name) # Output: Wewake (the second initialization overwrote the first)
__slots__变量限制了实例可以拥有的属性,从而节省内存。当某个类拥有多个实例,且每个实例的属性都固定时,这尤其有用。
import sys
class RegularPerson:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
class SlottedPerson:
__slots__ = ['name', 'age', 'email']
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
# Compare memory usage
regular_people = [RegularPerson("Vivek" + str(i), 30, "hello@wewake.dev") for i in range(1000)]
slotted_people = [SlottedPerson("Vivek" + str(i), 30, "hello@wewake.dev") for i in range(1000)]
print(f"Regular person size: {sys.getsizeof(regular_people[0])} bytes") # Output: Regular person size: 48 bytes
print(f"Slotted person size: {sys.getsizeof(slotted_people[0])} bytes") # Output: Slotted person size: 56 bytes
print(f"Memory saved per instance: {sys.getsizeof(regular_people[0]) - sys.getsizeof(slotted_people[0])} bytes") # Output: Memory saved per instance: -8 bytes
print(f"Total memory saved for 1000 instances: {(sys.getsizeof(regular_people[0]) - sys.getsizeof(slotted_people[0])) * 1000 / 1024:.2f} KB") # Output: Total memory saved for 1000 instances: -7.81 KB