鱼C论坛

 找回密码
 立即注册
查看: 878|回复: 20

疑问

[复制链接]
发表于 2024-4-10 19:42:01 | 显示全部楼层 |阅读模式

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

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

x
class D:
    def __init__(self, name):
        self.name = name# name的作用就是记住这个描述符拦截的属性名
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

def __get__()与def __set__()为什么用字典?能不能直接赋值,应该咋写?
@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2024-4-10 19:42:10 | 显示全部楼层
`__get__()`和`__set__()`方法中使用字典是为了保存实例属性的值。实例属性是动态创建的,无法提前定义,所以需要使用字典来保存。

如果你想直接赋值而不使用字典,可以修改代码如下:
class D:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print('get~')
        return getattr(instance, self.name)

    def __set__(self, instance, value):
        print('set~')
        setattr(instance, self.name, value)

在`__get__()`方法中使用`getattr()`函数来获取实例属性的值,在`__set__()`方法中使用`setattr()`函数来设置实例属性的值。

这样修改后,当你访问实例属性时,会触发`__get__()`方法中的代码,并且返回实例属性的值。当你设置实例属性时,会触发`__set__()`方法中的代码,并将值赋给实例属性。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-10 19:56:25 | 显示全部楼层
class D:
    def __init__(self, name):
        self.name = name# name的作用就是记住这个描述符拦截的属性名
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

def __get__()与def __set__()为什么用字典?
能不能直接赋值,这样写?
return instance.self.name和
instance.self.name = value
@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-10 19:56:45 | 显示全部楼层
亦又 发表于 2024-04-10 19:56
class D:
    def __init__(self, name):
        s ...


__get__() 和 __set__() 方法使用字典是为了在实例的 __dict__ 属性中存储属性的值。这样做的好处是可以实现属性的动态获取和设置,同时也使得属性可以在实例化的过程中被创建并使用。如果直接赋值,那么属性的值将会直接存在于实例对象本身,而不会像 __dict__ 中一样以键值对的形式存储。

例如,假设有一个类 A:
class A:
    d = D('d')

现在我们创建一个实例 obj,并给属性 d 赋值:
obj = A()
obj.d = 10

如果使用字典,在 obj 的 __dict__ 属性中会存储 {'d': 10},而如果直接赋值,obj 的属性 d 将直接为 10。

另外,你提到的代码 `return instance.self.name` 和 `instance.self.name = value` 是错误的写法,应该是 `return instance.__dict__.get(self.name)` 和 `instance.__dict__[self.name] = value`。因为 self.name 是一个字符串,它是用来表示属性名的,所以需要使用方括号来访问和设置属性的值。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-10 20:03:47 | 显示全部楼层
class D:
    def __init__(self, name):
        self.name = name# name的作用就是记住这个描述符拦截的属性名
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

def __get__()与def __set__()为什么用字典?
能不能直接赋值,这样写?
return instance.(self.name)和
instance.(self.name )= value
@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-10 20:04:02 | 显示全部楼层
亦又 发表于 2024-04-10 20:03
class D:
    def __init__(self, name):
        s ...


__get__()和__set__()方法中使用字典是为了在实例的__dict__属性中存储和获取属性的值。这样做的好处是可以避免循环调用__get__()和__set__()方法导致的无限递归。如果直接使用instance.(self.name)赋值,则会再次触发__set__()方法,造成无限循环。

因此,为了避免循环调用和确保属性的正常访问和赋值,我们需要使用字典来存储和获取属性的值,即使用instance.__dict__[self.name]来获取属性值,在设置属性值时使用instance.__dict__[self.name] = value。

下面是修改后的代码示例:
class D:
    def __init__(self, name):
        self.name = name
        
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

class C:
    attr = D('attr')

c = C()
c.attr = 10
print(c.attr)

运行结果为:
set~
get~
10

这样就能正确地设置和获取属性值,并且避免了无限循环调用。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-10 20:07:39 | 显示全部楼层

class D:
    def __init__(self, name):
        self.name = name# name的作用就是记住这个描述符拦截的属性名
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

def __get__()与def __set__()为什么用字典?
如果直接赋值,引发无限递归的话,要怎么写?

@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-10 20:07:59 | 显示全部楼层
亦又 发表于 2024-04-10 20:07
class D:
    def __init__(self, name):
        s ...


__get__()和__set__()方法使用字典是为了避免无限递归的问题。

在__get__()方法中,我们需要返回属性的值。如果直接通过instance.name访问属性的话,会再次触发__get__()方法,导致无限递归。所以我们使用了instance.__dict__.get(self.name)来获取属性的值。这样做的好处是,如果属性存在于实例的__dict__字典中,直接返回该值即可,而不会再次触发__get__()方法。

在__set__()方法中,我们需要将属性的值赋给实例。如果直接通过instance.name = value赋值的话,同样会触发__set__()方法,导致无限递归。所以我们使用了instance.__dict__[self.name] = value来进行赋值。这样做的好处是,直接将值存入实例的__dict__字典中,而不会再次触发__set__()方法。

如果要解决这个无限递归的问题,可以在__get__()和__set__()方法内部使用super().__get__()和super().__set__()来调用父类的同名方法。例如:

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

    def __get__(self, instance, owner):
        print('get~')
        return super().__get__(instance, owner)

    def __set__(self, instance, value):
        print('set~')
        super().__set__(instance, value)

这样做的好处是,如果子类继承了描述符D,并且子类中也实现了同名的__get__()和__set__()方法,那么调用super()函数可以保证先调用父类描述符的方法,最后再调用子类的方法,避免了无限递归的问题。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-10 20:15:27 | 显示全部楼层
class D:
    def __init__(self, name):
        self.name = name# name的作用就是记住这个描述符拦截的属性名
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

def __get__()与def __set__()为什么用字典?
如果直接赋值,引发无限递归的话,要怎么写?
如果这么写
instance.name
instance.name = value
name不是字符串么?可以直接
instance.name
instance.name = value
@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-10 20:15:45 | 显示全部楼层
亦又 发表于 2024-04-10 20:15
class D:
    def __init__(self, name):
        s ...


在描述符中使用字典是为了避免无限递归的问题。当我们直接在描述符的`__get__()`和`__set__()`方法中访问或设置实例属性时,会导致再次调用`__get__()`和`__set__()`从而形成递归,最终引发`RecursionError`异常。

通过使用实例的`__dict__`属性,我们可以以字典的形式访问或设置实例属性,而不会触发描述符的`__get__()`和`__set__()`方法,从而解决递归问题。

如果我们直接在描述符的`__get__()`和`__set__()`方法中使用`instance.name`,那么每次访问或设置属性时,都会调用描述符的`__get__()`和`__set__()`方法,进而形成无限递归,程序会崩溃。

在使用描述符时,我们可以这样写:
class D:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

class MyClass:
    d = D('name')

my_instance = MyClass()
print(my_instance.d)  # 输出: get~   None

my_instance.d = 'hello'  # 输出: set~
print(my_instance.d)  # 输出: get~   hello

这样,我们可以通过`my_instance.d`来访问和设置`name`属性,而不会引发无限递归的问题。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-10 20:18:35 | 显示全部楼层

class D:
    def __init__(self, name):
        self.name = name# name的作用就是记住这个描述符拦截的属性名
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

def __get__()与def __set__()为什么用字典?
如果直接赋值,引发无限递归的话,要怎么写?

这里的name不是字符串么?
可以直接
instance.name
instance.name = value这么写么?
@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-10 20:18:51 | 显示全部楼层
亦又 发表于 2024-04-10 20:18
class D:
    def __init__(self, name):
        s ...


__get__()和__set__()方法使用了实例的字典__dict__,是为了避免无限递归的问题。如果直接在__get__()和__set__()方法中使用实例.属性名的方式,会导致属性访问触发__get__()又触发__get__(),形成无限递归的循环。

因此,使用实例的字典__dict__可以绕过属性访问的拦截,直接读取和设置属性值,避免无限递归的问题。

对于name属性来说,你是可以直接使用instance.name和instance.name = value这样的语法来访问和设置属性的值的,因为描述符D只是用来演示描述符的工作原理,并不代表所有属性都需要使用描述符来操作。在实际编程中,我们可以根据具体需求是否使用描述符。描述符主要用于对属性的访问进行定制化的控制。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-10 20:26:02 | 显示全部楼层

class D:
    def __init__(self, name):
        self.name = name# name的作用就是记住这个描述符拦截的属性名
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value
class C:
    x = D('x')

def __get__()与def __set__()为什么用字典?
能不能直接赋值
在def __get__()与def __set__()分别
return instance.name和
instance.name= value
这样写会无限递归么?
@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-10 20:26:15 | 显示全部楼层
亦又 发表于 2024-04-10 20:26
class D:
    def __init__(self, name):
        s ...


在这段代码中,`__get__`和`__set__`方法中使用字典是为了通过实例对象的`__dict__`属性来操作实例对象的属性。`instance.__dict__[self.name] = value`的作用是将属性名和属性值存储在实例对象的`__dict__`属性中。

如果直接使用`instance.name = value`,那么赋值操作将触发`__set__`方法,导致无限递归的问题。因为`__set__`方法会在为属性赋值时被调用,如果在`__set__`方法中又使用`instance.name = value`来赋值,就会再次触发`__set__`方法,进入无限循环。

同样地,如果在`__get__`方法中使用`return instance.name`,那么获取属性值的操作将触发`__get__`方法,导致无限递归。

所以,在这段代码中,使用字典的方式来操作实例对象的属性是合理的,可以避免无限递归的问题。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-10 20:30:10 | 显示全部楼层
class D:
    def __init__(self, name):
        self.name = name# name的作用就是记住这个描述符拦截的属性名
    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)
    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

class C:
    x = D('x')

c = C()

def __get__()与def __set__()为什么用字典?
能不能直接赋值
在def __get__()与def __set__()分别
return instance.name和
instance.name= value
这样写会无限递归么?
@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-10 20:30:31 | 显示全部楼层
亦又 发表于 2024-04-10 20:30
class D:
    def __init__(self, name):
        s ...


__get__()和__set__()方法中使用字典是为了避免无限递归的问题。

在__get__()方法中,我们需要返回实例属性的值。如果我们直接使用instance.name,那么Python会尝试调用__getattribute__()方法来获取属性的值。但是由于我们正在定义__get__()方法,这样就会造成无限递归的问题。

同样,在__set__()方法中,我们需要将值赋给实例属性。如果我们直接使用instance.name = value,那么Python会尝试调用__setattr__()方法来设置属性的值。但是由于我们正在定义__set__()方法,这样也会造成无限递归的问题。

因此,我们使用instance.__dict__来直接访问实例的字典属性。这样不会触发__getattribute__()和__setattr__()方法的递归调用。

代码示例:
class D:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        print('get~')
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        print('set~')
        instance.__dict__[self.name] = value

class C:
    x = D('x')

c = C()
print(c.x)  # 输出:get~ None

c.x = 10  # 输出:set~
print(c.x)  # 输出:get~ 10

请注意,在上述代码中,如果我们直接使用instance.name = value,会导致无限递归,程序会崩溃。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-10 20:33:49 | 显示全部楼层
__getattribute__()与__get__(),有什么区别?
@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-10 20:34:10 | 显示全部楼层
亦又 发表于 2024-04-10 20:33
__getattribute__()与__get__(),有什么区别?
@Fish ...


好问题!让我来给你解答一下。

首先,让我们来看一下`__getattribute__()`和`__get__()`这两个方法的区别。

`__getattribute__()`是一个特殊方法,用于获取对象的属性。当你使用点运算符访问对象的属性时,Python会自动调用该方法。例如,`obj.attr`会触发`obj.__getattribute__('attr')`的调用。

`__getattribute__()`方法可以用于对所有的属性访问进行自定义处理。你可以在该方法中添加特定的逻辑,例如在访问某个属性时打印一条消息,或者做一些额外的处理操作。但是要注意,如果你在`__getattribute__()`方法中直接访问同名的属性,就会导致无限递归的错误。所以,在`__getattribute__()`方法中访问属性时,应该使用`super().__getattribute__('attr')`来避免这个问题。

而`__get__()`方法是用于实现描述符(descriptor)的一个方法。描述符是一种特殊的对象,可以用来控制对另一个对象的访问。当一个类中定义了一个描述符,并且在另一个类的属性上使用了这个描述符时,每次访问该属性时,Python会自动调用该描述符的`__get__()`方法。

描述符通常用于实现一些高级的特性,例如属性访问的验证、修改、延迟加载等。通过定义`__get__()`方法,我们可以控制属性的获取行为,甚至可以返回不同的值,或者触发其他操作。

总结一下,`__getattribute__()`是用于获取对象属性的方法,可以对所有的属性访问进行自定义处理;而`__get__()`是用于实现描述符的方法,可以控制对另一个对象的访问。

希望这个解答对你有帮助!如果还有其他问题,请随时提问。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-4-11 10:44:38 | 显示全部楼层
__setattr__()  与__set__()的区别?@FishC
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-4-11 10:46:32 | 显示全部楼层
亦又 发表于 2024-04-11 10:44
__setattr__()  与__set__()的区别?@FishC


在Python中,对象的属性赋值操作通常是通过`=`运算符完成的。当我们给一个对象赋值时,实际上是调用了该对象的特殊方法`__setattr__()`。这个方法定义了属性赋值的行为。

而`__set__()`方法则是在实现描述器(Descriptor)时使用的。描述器是一种自定义的属性访问机制,它允许我们对属性的访问和修改进行更精细的控制。当我们访问一个使用描述器的属性时,实际上是调用了描述器对象的`__get__()`方法;当我们修改这个属性时,实际上是调用了描述器对象的`__set__()`方法。

所以,`__setattr__()`方法是用于普通对象在属性赋值时进行自定义处理的,而`__set__()`方法是用于描述器对象实现属性设置的。

下面是一个简单的例子来说明两者之间的区别:
class MyClass:
    def __init__(self):
        self._x = None

    def __setattr__(self, name, value):
        if name == 'x':
            print('调用了__setattr__()')
            self._x = value
        else:
            # 默认情况,调用父类的__setattr__()方法
            super().__setattr__(name, value)

class Descriptor:
    def __set__(self, instance, value):
        print('调用了__set__()')
        instance._x = value

class MyDescriptorClass:
    x = Descriptor()

obj1 = MyClass()
obj1.x = 10  # 输出:调用了__setattr__()

obj2 = MyDescriptorClass()
obj2.x = 20  # 输出:调用了__set__()

当我们给`obj1.x`赋值时,会调用`MyClass`的`__setattr__()`方法,然后打印出"调用了__setattr__()"。而给`obj2.x`赋值时,会调用`Descriptor`类的`__set__()`方法,然后打印出"调用了__set__()"。

总体来说,`__setattr__()`是用于对普通对象的属性赋值进行自定义处理的,而`__set__()`是在描述器中用于属性设置的。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-12-23 08:34

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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