Python 魔法方法的工作原理:实用指南(三)

liftword13小时前技术文章3

以前的文章我们讲了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 提供了两种控制属性访问的方法:

  1. __getattr__:仅当属性查找失败时调用(即,当属性不存在时)
  2. __getattribute__:每次访问属性时都会调用,即使属性已经存在。

setattr 和 delattr

类似地,您可以控制如何设置和删除属性:

  1. __setattr__:设置属性时调用
  2. __delattr__:删除属性时调用

此内容过于简单不在编写示例。


上下文管理器

上下文管理器是 Python 中一个强大的功能,可以帮助你正确地管理资源。即使发生错误,它们也能确保资源被正确地获取和释放。with语句是使用上下文管理器的最常见方式。

进入和退出

要创建上下文管理器,您需要实现两个魔术方法:

  1. __enter__:进入with块时调用。它应该返回要管理的资源。
  2. __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

相关文章

太好用!教你几招Python魔法方法的妙用

专注Python、AI、大数据,请关注公众号七步编程!Python是一种简单的编程语言,满足一个需求,可以有各种各样的实现方法。正是因为它可以通过各种串联满足很多复杂的逻辑,因此,对代码可读性关注度不...

11 每个程序员都应该知道的 Python 魔法方法

在 Python 中,魔法方法帮助你模拟 Python 类中内置函数的行为。这些方法有前后双下划线(__),因此也被称为魔法方法 。这些魔法方法也帮助你实现 Python 中的运算符重载。你很可能见...

Python中关于魔法方法、单例模式的知识

目录:init,del,add,str和 repr,call,单例模式,class,dict,doc,bases,mro魔法方法:定义:在特定条件下,触发方法在python里面很多以双下划线开头且结尾...

掌握Python的"魔法":特殊方法与属性完全指南

在Python的世界里,以双下划线开头和结尾的"魔法成员"(如__init__、__str__)是面向对象编程的核心。它们赋予开发者定制类行为的超能力,让自定义对象像内置类型一样优雅工...

Python进阶——如何正确使用魔法方法?(上)

微信搜索关注「水滴与银弹」公众号,第一时间获取优质技术干货。7年资深后端研发,用简单的方式把技术讲清楚。在做 Python 开发时,我们经常会遇到以双下划线开头和结尾的方法,例如 __init__、_...