【Python】魔术方法是真的魔法!(第三期)

#新星杯·14天创作挑战营·第11期#

可以看看本系列开篇:【Python】小子!是魔术方法!-CSDN博客

在对象属性不存在时,__getattr__魔术方法的作用是什么?

__getattr__魔术方法是在尝试访问对象不存在的属性时被调用的。如果未定义__getattr__函数,程序会抛出AttributeError。但若定义了__getattr__函数,并在其中返回特定值(如None),则当读取不存在属性时,程序将不会出现错误,而是返回预设的值。

class MyClassA:
    def __init__(self, a=1, b=2):
        self.a = a
        self.b = b

    def __getattr__(self, name):
        print(f"试图访问不存在的属性: {name}")
        # 可以返回一个默认值,或者抛出自定义错误
        return f"属性 '{name}' 不存在,返回默认值"

# 示例
obj_a = MyClassA()
print(f"访问存在的属性 a: {obj_a.a}") # 访问存在的属性 a: 1
print(f"访问存在的属性 b: {obj_a.b}") # 访问存在的属性 b: 2
print(f"访问不存在的属性 c: {obj_a.c}") # 试图访问不存在的属性: c
                                  # 访问不存在的属性 c: 属性 'c' 不存在,返回默认值
print(f"访问不存在的属性 xyz: {obj_a.xyz}") # 试图访问不存在的属性: xyz
                                    # 访问不存在的属性 xyz: 属性 'xyz' 不存在,返回默认值

class MyClassB:
    def __init__(self, x=10):
        self.x = x
    # 没有定义 __getattr__

obj_b = MyClassB()
print(f"访问存在的属性 x: {obj_b.x}") # 访问存在的属性 x: 10
try:
    print(obj_b.y)
except AttributeError as e:
    print(f"访问不存在的属性 y (无__getattr__): {e}") # 访问不存在的属性 y (无__getattr__): 'MyClassB' object has no attribute 'y'

__getattr____getattribute__这两个魔术方法有何区别?__delattr__魔术方法的作用是什么?

  • __getattr__函数只有在尝试读取不存在的属性时才会被调用。
  • __getattribute__函数无论何时尝试读取任何属性都会被调用,其行为更为广泛。

__delattr__魔术方法是在尝试删除对象属性时被调用。它不会在对象正常生命周期结束时被调用,而是在使用del关键字删除属性时执行。该方法没有返回值,但在其中可以进行一些操作,如打印提示信息。

class AttributeDemo:
    def __init__(self):
        self.existing_attr = "这是一个存在的属性"
        self._internal_val = 42 # 用于 __getattribute__ 避免递归

    def __getattr__(self, name):
        print(f"--- __getattr__ 被调用: 尝试访问不存在的属性 '{name}' ---")
        return f"属性 '{name}' 通过 __getattr__ 返回"

    def __getattribute__(self, name):
        print(f"--- __getattribute__ 被调用: 尝试访问属性 '{name}' ---")
        # 为了避免无限递归,访问属性时必须使用 super()
        # 或者直接访问 __dict__ (但不推荐,因为它绕过了描述符等机制)
        try:
            return super().__getattribute__(name)
        except AttributeError:
            # 如果 super().__getattribute__ 抛出 AttributeError,
            # 并且定义了 __getattr__,那么 __getattr__ 会被调用。
            # 这里我们为了演示清晰,直接在 __getattribute__ 中处理,
            # 实际中 Python 会在 super() 失败后自动调用 __getattr__。
            # 此处不需要显式调用 self.__getattr__(name)
            raise # 重新抛出 AttributeError,让 __getattr__ (如果存在) 被Python解释器调用

    def __setattr__(self, name, value):
        print(f"--- __setattr__ 被调用: 设置属性 '{name}' = '{value}' ---")
        super().__setattr__(name, value)

    def __delattr__(self, name):
        print(f"--- __delattr__ 被调用: 尝试删除属性 '{name}' ---")
        if name == "non_deletable_attr":
            print(f"属性 '{name}' 不可删除!")
            # 可以抛出 AttributeError 或者什么都不做来阻止删除
            # raise AttributeError(f"属性 '{name}' 是受保护的,不能删除")
        else:
            try:
                super().__delattr__(name)
                print(f"属性 '{name}' 已删除")
            except AttributeError:
                print(f"属性 '{name}' 不存在,无法删除")


demo = AttributeDemo()
print("\n1. 设置新属性:")
demo.new_attr = "新属性的值"
# --- __setattr__ 被调用: 设置属性 'new_attr' = '新属性的值' ---

print("\n2. 访问存在的属性:")
print(f"值: {demo.existing_attr}")
# --- __getattribute__ 被调用: 尝试访问属性 'existing_attr' ---
# 值: 这是一个存在的属性

print("\n3. 访问不存在的属性 (会先经过 __getattribute__,失败后调用 __getattr__):")
# 注意:Python 解释器在 __getattribute__ 内部的 super().__getattribute__(name) 失败后,
# 会自动查找并调用 __getattr__。
# 因此,__getattr__ 中的 print 会被触发。
try:
    print(f"值: {demo.non_existent_attr}")
except AttributeError: # 如果__getattr__也抛出AttributeError或未定义
     print("最终还是 AttributeError")

# --- __getattribute__ 被调用: 尝试访问属性 'non_existent_attr' ---
# --- __getattr__ 被调用: 尝试访问不存在的属性 'non_existent_attr' ---
# 值: 属性 'non_existent_attr' 通过 __getattr__ 返回


print("\n4. 删除存在的属性:")
demo.another_attr = "用于删除测试"
# --- __setattr__ 被调用: 设置属性 'another_attr' = '用于删除测试' ---
del demo.another_attr
# --- __delattr__ 被调用: 尝试删除属性 'another_attr' ---
# 属性 'another_attr' 已删除

print("\n5. 尝试删除不存在的属性:")
try:
    del demo.ghost_attr
except AttributeError as e: # __delattr__ 可能会自己处理或 super() 抛出
    print(e)
# --- __delattr__ 被调用: 尝试删除属性 'ghost_attr' ---
# 属性 'ghost_attr' 不存在,无法删除

print("\n6. 尝试删除受保护的属性:")
demo.non_deletable_attr = "不可删除"
# --- __setattr__ 被调用: 设置属性 'non_deletable_attr' = '不可删除' ---
del demo.non_deletable_attr # __delattr__ 会阻止
# --- __delattr__ 被调用: 尝试删除属性 'non_deletable_attr' ---
# 属性 'non_deletable_attr' 不可删除!

# 验证属性是否还存在
print(f"non_deletable_attr 仍然存在: {hasattr(demo, 'non_deletable_attr')}")
# --- __getattribute__ 被调用: 尝试访问属性 'non_deletable_attr' ---
# non_deletable_attr 仍然存在: True

如何正确使用__getattribute__函数以避免无限递归?

在使用__getattribute__函数时,如果要恢复其默认行为,应使用super().__getattribute__(),以防止因递归调用__getattr__函数而导致无限递归。

class SafeGetAttribute:
    def __init__(self):
        self.my_value = 100
        self.another_value = 200

    def __getattribute__(self, name):
        print(f"SafeGetAttribute: __getattribute__ 正在尝试获取 '{name}'")
        # 错误的实现: 直接访问 self.name 会再次调用 __getattribute__,导致无限递归
        # if name == "my_value":
        #     return self.my_value # 错误! 会无限递归调用 __getattribute__('my_value')

        # 正确的实现: 使用 super() 来调用父类(object类)的 __getattribute__
        # 这样可以安全地获取属性值,而不会再次触发当前的 __getattribute__
        if name == "_secret_value": # 假设我们想阻止直接访问某个内部属性
             print(f"SafeGetAttribute: 尝试访问受保护属性 '{name}',将返回 None")
             return None # 或者 raise AttributeError("...")

        try:
            value = super().__getattribute__(name)
            print(f"SafeGetAttribute: 成功获取 '{name}', 值为: {value}")
            return value
        except AttributeError:
            print(f"SafeGetAttribute: 属性 '{name}' 未找到")
            raise # 重新抛出异常,或者可以调用 __getattr__ (如果定义了)

class UnsafeGetAttribute:
    def __init__(self):
        self.my_value = 10

    def __getattribute__(self, name):
        print(f"UnsafeGetAttribute: __getattribute__ 正在尝试获取 '{name}'")
        # 这是一个会导致无限递归的例子
        return self.my_value # 错误! 这里会再次调用 __getattribute__('my_value')

# 安全示例
safe_obj = SafeGetAttribute()
print("\n--- 安全的 __getattribute__ 示例 ---")
print(f"获取 my_value: {safe_obj.my_value}")
# SafeGetAttribute: __getattribute__ 正在尝试获取 'my_value'
# SafeGetAttribute: 成功获取 'my_value', 值为: 100
# 获取 my_value: 100

print(f"获取 another_value: {safe_obj.another_value}")
# SafeGetAttribute: __getattribute__ 正在尝试获取 'another_value'
# SafeGetAttribute: 成功获取 'another_value', 值为: 200
# 获取 another_value: 200

print(f"获取 _secret_value: {safe_obj._secret_value}")
# SafeGetAttribute: __getattribute__ 正在尝试获取 '_secret_value'
# SafeGetAttribute: 尝试访问受保护属性 '_secret_value',将返回 None
# 获取 _secret_value: None

try:
    print(f"获取 non_existent: {safe_obj.non_existent}")
except AttributeError as e:
    print(e)
# SafeGetAttribute: __getattribute__ 正在尝试获取 'non_existent'
# SafeGetAttribute: 属性 'non_existent' 未找到
# 'SafeGetAttribute' object has no attribute 'non_existent'


# 不安全示例 (会导致 RecursionError)
unsafe_obj = UnsafeGetAttribute()
print("\n--- 不安全的 __getattribute__ 示例 (会导致 RecursionError) ---")
try:
    # 注意:这个调用会很快导致 RecursionError,所以可能只会打印几次 __getattribute__ 的消息
    print(unsafe_obj.my_value)
except RecursionError as e:
    print(f"捕获到错误: {e}")
# UnsafeGetAttribute: __getattribute__ 正在尝试获取 'my_value'
# UnsafeGetAttribute: __getattribute__ 正在尝试获取 'my_value'
# ... (多次重复)
# 捕获到错误: maximum recursion depth exceeded while calling a Python object

__setattr__魔术方法是如何工作的?

__setattr__魔术方法有两个参数,namevalue,当设置对象属性时会被调用。通常会采用super().__setattr__()来实现默认行为,但也支持自定义实现,例如将name-value对存储在类级别的字典中,使得所有该类的对象共享这些自定义属性。

class Configurable:
    # 类级别的字典,用于存储所有实例共享的属性
    _shared_attributes = {}

    def __init__(self, initial_val):
        # 普通实例属性的设置也会经过 __setattr__
        self.initial_value = initial_val # 这会调用下面的 __setattr__

    def __setattr__(self, name, value):
        print(f"Configurable: __setattr__ 被调用,尝试设置 '{name}' = {value}")

        if name.startswith("shared_"):
            # 如果属性名以 'shared_' 开头,则存储在类级别字典中
            print(f"  -> 将 '{name}' 存储为共享属性")
            Configurable._shared_attributes[name] = value
        else:
            # 否则,正常设置实例属性,使用 super() 避免递归
            print(f"  -> 将 '{name}' 存储为实例属性")
            super().__setattr__(name, value)

    def __getattr__(self, name): # 用于演示共享属性的获取
        print(f"Configurable: __getattr__ 被调用,尝试获取 '{name}'")
        if name.startswith("shared_") and name in Configurable._shared_attributes:
            print(f"  <- 从共享属性获取 '{name}'")
            return Configurable._shared_attributes[name]
        raise AttributeError(f"属性 '{name}' 未找到")

    def __getattribute__(self, name): # 用于更细致地观察属性访问
        print(f"Configurable: __getattribute__ 尝试获取 '{name}'")
        # 对于非共享属性的常规访问,通过super
        if not name.startswith("shared_"):
            return super().__getattribute__(name)
        # 对于共享属性,我们希望通过 __getattr__ (如果常规查找失败)
        # 或者直接在这里检查 _shared_attributes
        try:
            return super().__getattribute__(name) # 尝试正常获取
        except AttributeError:
            if name in Configurable._shared_attributes: # 如果不存在于实例,则检查共享
                 print(f"  <- (来自getattribute) 从共享属性获取 '{name}'")
                 return Configurable._shared_attributes[name]
            raise # 如果共享属性里也没有,则让AttributeError继续传播,可能被__getattr__捕获

# 示例
print("\n--- __setattr__ 示例 ---")
conf1 = Configurable(10)
# Configurable: __setattr__ 被调用,尝试设置 'initial_value' = 10
#   -> 将 'initial_value' 存储为实例属性

conf2 = Configurable(20)
# Configurable: __setattr__ 被调用,尝试设置 'initial_value' = 20
#   -> 将 'initial_value' 存储为实例属性

print(f"\nconf1.initial_value: {conf1.initial_value}")
# Configurable: __getattribute__ 尝试获取 'initial_value'
# conf1.initial_value: 10
print(f"conf2.initial_value: {conf2.initial_value}")
# Configurable: __getattribute__ 尝试获取 'initial_value'
# conf2.initial_value: 20

print("\n设置普通实例属性:")
conf1.instance_specific = "val1"
# Configurable: __setattr__ 被调用,尝试设置 'instance_specific' = val1
#   -> 将 'instance_specific' 存储为实例属性

print(f"conf1.instance_specific: {conf1.instance_specific}")
# Configurable: __getattribute__ 尝试获取 'instance_specific'
# conf1.instance_specific: val1
try:
    print(f"conf2.instance_specific: {conf2.instance_specific}")
except AttributeError as e:
    # Configurable: __getattribute__ 尝试获取 'instance_specific'
    # Configurable: __getattr__ 被调用,尝试获取 'instance_specific'
    print(e) # '属性 'instance_specific' 未找到'

print("\n设置共享属性:")
conf1.shared_setting = "Global Setting Alpha"
# Configurable: __setattr__ 被调用,尝试设置 'shared_setting' = Global Setting Alpha
#   -> 将 'shared_setting' 存储为共享属性

print(f"conf1.shared_setting: {conf1.shared_setting}")
# Configurable: __getattribute__ 尝试获取 'shared_setting'
# Configurable: __getattr__ 被调用,尝试获取 'shared_setting'
#   <- 从共享属性获取 'shared_setting'
# conf1.shared_setting: Global Setting Alpha

print(f"conf2.shared_setting: {conf2.shared_setting}") # conf2 也能访问到
# Configurable: __getattribute__ 尝试获取 'shared_setting'
# Configurable: __getattr__ 被调用,尝试获取 'shared_setting'
#   <- 从共享属性获取 'shared_setting'
# conf2.shared_setting: Global Setting Alpha

print(f"类级别字典: {Configurable._shared_attributes}")
# 类级别字典: {'shared_setting': 'Global Setting Alpha'}

conf2.shared_setting = "Global Setting Beta" # 修改共享属性
# Configurable: __setattr__ 被调用,尝试设置 'shared_setting' = Global Setting Beta
#   -> 将 'shared_setting' 存储为共享属性

print(f"修改后 conf1.shared_setting: {conf1.shared_setting}") # conf1 也看到变化
# Configurable: __getattribute__ 尝试获取 'shared_setting'
# Configurable: __getattr__ 被调用,尝试获取 'shared_setting'
#   <- 从共享属性获取 'shared_setting'
# 修改后 conf1.shared_setting: Global Setting Beta
print(f"类级别字典更新后: {Configurable._shared_attributes}")
# 类级别字典更新后: {'shared_setting': 'Global Setting Beta'}

关于作者

  • CSDN 大三小白新手菜鸟咸鱼长期更新强烈建议不要关注

作者的其他文章

Python

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sonetto1999

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值