Python面试题整理04——类-魔术方法
Python面试题整理04——类-魔术方法

Python面试题整理04——类-魔术方法

魔术方法,Magic Methods/Dunder Methods,这个名字真够奇怪的,实际意义是Python 对象协议的一部分,用来让你的类“接入 Python 语言本身的行为系统”。通俗点来讲,就是增加一些特定操作的快捷方式。

  • 生命周期:__new__(创建实例,返回实例)与 __init__(初始化实例)的区别。
  • 字符串表示:__str__(面向用户)与 __repr__(面向开发者/调试)。
  • 容器类:__getitem__, __setitem__, __len__, __iter__(实现自定义集合)。
  • 可调用对象:__call__(让对象像函数一样被调用)。
  • 上下文管理:__enter__ 与 __exit__(with 语句的底层实现)。

生命周期

__init__很常见,就是初始化实例的。__new__见的不多,了解了一下,似乎就是创建实例时用于判断是否创建,在init之前执行判断。

所以为什么创建实例还需要一个判断呢?这主要是针对极少情况,单例模式,比如某个类只能有一个实例的情况。也就是无论你调用多少次这个类,最终也只有一个实例,常用于数据库连接池、配置读取类啥的。cls是指当前这个类本身。self是指当前实例本身。

class DatabaseConnection:
    _instance = None

    def __new__(cls, *args, **kwargs):
        print("new")
        if not cls._instance:
            # 调用父类 object 的 __new__ 来创建对象
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self, db_url):
        print("init")
        self.db_url = db_url

# 测试
db1 = DatabaseConnection("mysql://localhost")
db2 = DatabaseConnection("mysql://remote")
print(db1 is db2)  # True,两者是同一个对象

# 输出
# new
# init
# new
# init
# True

字符串表示

这个很简单,就是可展示的标注。可以理解为这个对象的自我介绍。repr就是更详细一些,针对调试和开发,str就是可以粗糙直观一些,针对用户。

class User:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f"用户:{self.name}"

    def __repr__(self):
        return f"User(name={self.name!r})"

u = User("张三")
print(u)  # 调用 __str__
print(repr(u))  # 调用 __repr__

# 输出结果
# 用户:张三
# User(name='张三')

容器类

这个在深度学习数据处理那儿很常见,就是将类实现像列表/字典一样的操作。

# #
# __getitem__:通过 obj[key] 获取值。
# __setitem__:通过 obj[key] = value 设置值。
# __len__:len(obj)。
# __iter__:使对象可迭代。

class Bag:
    def __init__(self, items):
        self.items = items

    def __len__(self):
        return len(self.items)

    def __getitem__(self, i):
        return self.items[i]

    def __setitem__(self, i, v):
        self.items[i] = v

    def __iter__(self):
        return iter(self.items)

b = Bag([1,2,3])
print(len(b))      # 3
print(b[0])    # 1
b[1] = 9
for x in b: print(x) # 1 9 3

可调用对象

让一个类实例函数一样被调用。编写复杂的装饰器或者状态保持。在后端中,常用于配置好的逻辑单元(如权限检查器)。感觉不是很常见。

class permissionchecker:
    def __init__(self, role):
        self.role = role
    def __call__(self, user_role):
        # 像函数一样被调用,执行逻辑
        return self.role == user_role

# 预设一个“管理员检查器”
is_admin = permissionchecker("admin")

# 在后续逻辑中直接调用
if is_admin("guest"):
    print("通过")
else:
    print("拒绝")  # 输出拒绝

上下文管理

配合with语句使用,自动处理资源的分配与释放。

这个也不常用……不过看起来在工程中似乎作用很大。举个例子,比如数据库连接,加入连接中出了问题,程序一般就抛出异常卡在那儿了,但是实际上无论连接是否异常,我们最后都应该有一步去执行关闭连接的操作,否则连接池被异常中断的占满,就卡死了。

先来了解下with,代码为例:

# 这是一个常规的连接,如果出错、波动,提前退出,那就永远不会执行close
conn = get_db_connection()
cursor = conn.cursor()

if not valid:
    return   # 提前退出,下面不会执行

cursor.execute(...)
conn.close()   # 永远不会被调用

# 使用with之后
with get_db_connection() as conn:
    cursor = conn.cursor()
    cursor.execute(...)

# with语句等价于
conn = get_db_connection()
try:
    cursor = conn.cursor()
    cursor.execute(...)
finally:
    conn.close()   # 无论你中途 return、异常、崩溃,这行一定执行

OK,如果上述大概能看懂,我们在整个类结合with来看看:

class File:
    def __enter__(self):
        print("打开资源")
        return self

    def __exit__(self, exc_type, exc, tb):
        print("释放资源")

with File() as f:
    print("处理中")

# 这个with等价于
obj = File()                  # 1. 创建对象
f = obj.__enter__()           # 2. 进入上下文
try:
    print("处理中")           # 3. 执行代码块
finally:
    obj.__exit__(None, None, None)  # 4. 无论如何都退出

with简直是极致的便捷!

至于__exit__里面三个参数是啥,def __exit__(self, exc_type, exc, tb),当 with 里面发生异常时,exit 会收到:exc_type:异常类型(ZeroDivisionError),exc:异常对象,tb:traceback。

说实话,这一个章节整理的微微有点乱,主要是都是个人平时不怎么用的东东,很多工具只有具体场景下才能体现,后续有机会会继续优化!不过从面试角度来看,似乎主要是一些概念性的,了解即可,重点可能在应用场景的理解上。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注