鱼C论坛

 找回密码
 立即注册
查看: 2017|回复: 4

[已解决]关于__setattr__无限递归的疑惑

[复制链接]
发表于 2023-5-30 09:58:17 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 awp 于 2023-5-30 10:03 编辑

新手求问:

class C:
    def __setattr__(self, key, value):
        self.key = value  # 无限递归
        # self.__dict__[key] = value 不会递归

类C造成无限递归。因为__setattr__本身是赋值,函数内部出现赋值,形成无限递归。
为什么self.__dict__[name] = value也是赋值语句,但不会形成无限递归?

同样的问题:

class D:
    def __init__(self, data):
        self.data = data
    def __contains__(self, item):
        return item in self.data

类D不会无限递归。但contains本身就是in,而函数内部也出现了in,为什么不无限递归?

还有:

类D可以参考类C的写法,省略掉__init__,这么写么?
class D:
    def __contains__(self, data, item):
        return item in self.data
最佳答案
2023-5-30 10:06:12
首先,我们来讨论关于 __setattr__ 的无限递归问题。当你在Python中定义一个类,并在类中重写 __setattr__ 方法的时候,你基本上就在告诉Python:每次我试图设置这个对象的属性时,我都希望你调用这个方法。因此,如果你在 __setattr__ 方法中再试图设置对象的属性(就像你的例子中那样),那么Python会再次调用 __setattr__ ,形成无限递归。

但是,当你直接操作对象的 __dict__ 属性时,你实际上是在绕过 __setattr__ ,直接在对象的属性字典中设置键/值对。这就是为什么 self.__dict__[key] = value 不会造成无限递归。

关于你的第二个问题,类 D 并不会无限递归,原因是 __contains__ 方法和 in 操作符的逻辑是不同的。 __contains__ 方法定义的是当使用 in 操作符检查一个对象是否包含某个元素时应该做什么。而 in 操作符本身的行为就是调用对象的 __contains__ 方法(如果存在的话)。但在 __contains__ 方法内部使用 in 操作符检查另一个对象(例如 self.data )时,并不会引发递归,因为你检查的对象是不同的。如果你把 return item in self.data 换成 return item in self 一样是会引发无线递归的问题的

对于你的最后一个问题,对于Python类的方法,第一个参数通常被命名为 self ,这是一个传统的约定,用于引用调用该方法的对象本身。而 __contains__ 方法只接受一个额外的参数,即要检查的元素。如果你尝试像你那样定义 __contains__ 方法,Python将会抛出一个TypeError,因为 __contains__ 被调用时只传递了一个参数,但你的定义需要两个。

在你的例子中,你可能需要将 data 定义为类的一个属性,然后在 __contains__ 方法中检查它。例如,你可以这样做:
class D:
    def __init__(self, data):
        self.data = data
    def __contains__(self, item):
        return item in self.data

这样,在创建一个 D 对象时,你可以给 data 传值,然后使用 in 操作符检查一个元素是否在 data 中。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-5-30 10:06:12 | 显示全部楼层    本楼为最佳答案   
首先,我们来讨论关于 __setattr__ 的无限递归问题。当你在Python中定义一个类,并在类中重写 __setattr__ 方法的时候,你基本上就在告诉Python:每次我试图设置这个对象的属性时,我都希望你调用这个方法。因此,如果你在 __setattr__ 方法中再试图设置对象的属性(就像你的例子中那样),那么Python会再次调用 __setattr__ ,形成无限递归。

但是,当你直接操作对象的 __dict__ 属性时,你实际上是在绕过 __setattr__ ,直接在对象的属性字典中设置键/值对。这就是为什么 self.__dict__[key] = value 不会造成无限递归。

关于你的第二个问题,类 D 并不会无限递归,原因是 __contains__ 方法和 in 操作符的逻辑是不同的。 __contains__ 方法定义的是当使用 in 操作符检查一个对象是否包含某个元素时应该做什么。而 in 操作符本身的行为就是调用对象的 __contains__ 方法(如果存在的话)。但在 __contains__ 方法内部使用 in 操作符检查另一个对象(例如 self.data )时,并不会引发递归,因为你检查的对象是不同的。如果你把 return item in self.data 换成 return item in self 一样是会引发无线递归的问题的

对于你的最后一个问题,对于Python类的方法,第一个参数通常被命名为 self ,这是一个传统的约定,用于引用调用该方法的对象本身。而 __contains__ 方法只接受一个额外的参数,即要检查的元素。如果你尝试像你那样定义 __contains__ 方法,Python将会抛出一个TypeError,因为 __contains__ 被调用时只传递了一个参数,但你的定义需要两个。

在你的例子中,你可能需要将 data 定义为类的一个属性,然后在 __contains__ 方法中检查它。例如,你可以这样做:
class D:
    def __init__(self, data):
        self.data = data
    def __contains__(self, item):
        return item in self.data

这样,在创建一个 D 对象时,你可以给 data 传值,然后使用 in 操作符检查一个元素是否在 data 中。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 3 反对 0

使用道具 举报

 楼主| 发表于 2023-5-31 09:17:34 | 显示全部楼层
isdkz 发表于 2023-5-30 10:06
首先,我们来讨论关于 __setattr__ 的无限递归问题。当你在Python中定义一个类,并在类中重写 __setattr__  ...

我以为等号=在类中就替代了_setattr_,in在类中就取代了_contains_,难道不是这么理解的么
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-5-31 09:34:27 | 显示全部楼层
awp 发表于 2023-5-31 09:17
我以为等号=在类中就替代了_setattr_,in在类中就取代了_contains_,难道不是这么理解的么

__setattr__ 只有这个类的属性被重新赋值的时候才会触发呀,

然而这里并没有重新赋值这个类的属性,这里使用的是 self.__dict__[key] = value,而不是 self.__dict__ = value

而 __contains__ 是只有在 in 这个类的对象的时候才会触发,所以在 __contains__ 使用 item in self.data 并不会有什么问题,

因为 self.data 并不是 D 的实例,而 item in self 则不行,因为 self 就是 D 的实例

主要是你需要搞清楚不同魔法方法的触发条件
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2023-5-31 09:35:57 | 显示全部楼层
isdkz 发表于 2023-5-31 09:34
__setattr__ 只有这个类的属性被重新赋值的时候才会触发呀,

然而这里并没有重新赋值这个类的属性,这 ...

很清晰,懂了。感谢前辈!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-12-27 11:15

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表