【问题标题】:How python adds atributes to class in this code?python如何在这段代码中向类添加属性?
【发布时间】:2020-04-17 11:09:05
【问题描述】:

python 如何将属性 val1 和 val2 添加到类中。 python 是否在内部调用 b1.__shared_state['val1'] = 'Jaga Gola!!!' 之类的东西?

# Borg (monostate pattern) lets a class have as many instances as one likes,
# but ensures that they all share the same state


class Borg:
    __shared_state = {}

    def __init__(self):
        self.__dict__ = self.__shared_state



b1 = Borg()
b2 = Borg()

print(b1 == b2)
b1.val1 = 'Jaga Gola!!!'
b1.val2 = 'BOOOM!!!'
print(b2.val1, b1.val2)

为什么如果我删除 _shared_state and self.__dict__ = self.__shared_state 我不能向类添加属性并得到错误:AttributeError: 'Borg' object has no attribute 'val1'?

class Borg:

    def __init__(self):
        pass



b1 = Borg()
b2 = Borg()

print(b1 == b2)
b1.val1 = 'Jaga Gola!!!'
b1.val2 = 'BOOOM!!!'
print(b2.val1, b1.val2)

【问题讨论】:

  • 第二个问题:因为没有明确的共享状态,b1 和b2 不共享状态,所以b2.val1 不存在。
  • “python 是否在内部调用 b1.__shared_state['val1'] = 'Jaga Gola!!!' 之类的东西?” - 是的。实例属性存储在__dict__。
  • 好吧,我没有看到,我使用 b2.val1 而不是 b1.val1。我检查了一下,它适用于此更改。

标签: python


【解决方案1】:

在这段代码中:

class Borg:
    __shared_state = {}

    def __init__(self):
        self.__dict__ = self.__shared_state
  • __shared_state = {} 行出现在类级别,因此它被添加到类Borg 一次,而不是添加到Borg 类型的每个单独对象。和后面写Borg.__shared_state = {}一样。
  • self.__dict__ = self.__shared_state 令人困惑,因为它使用了两次self.,但效果却截然不同:
    • 当分配给self.something时,something被设置在对象self中。这并不奇怪。
    • 但是当读取 self.something 时,首先在self 对象中查找something,如果在那里找不到,则在对象的类中查找它。这个想法听起来很奇怪,但你实际上一直在使用它:这就是方法通常的工作方式。例如,在s = "foo"; b = s.startswith("f") 中,对象s 没有属性startswith,但它的类str 有,这就是调用方法时使用的属性。

这一行:

b1.val1 = 'Jaga Gola!!!'

最终翻译为:

b1.__dict__['val1'] = 'Jaga Gola!!!'

但我们知道b1.__dict__ 等于Borg.__shared_state,所以它被分配给它。那么:

print(b2.val1, ...

翻译为:

print(b2.__dict__['val1'])

我们再次知道b2.__dict__ 等于相同 Borg.__shared_state 所以找到val1。

如果您在开头删除有关__shared_state 的内容,那么b1 和b2 将获得自己的__dict__ 对象,因此将val1 放入b1 的字典中对b2 没有影响,这就是你得到你提到的错误的方式。


这一切都可以用来了解正在发生的事情,但你应该意识到,这段代码不能保证工作并且可能会破坏,例如在 Python 的未来版本或 PyPy 等其他实现中。 Python documentation for __dict__ 将其描述为“只读属性”,因此您根本不应该分配给它。不要在其他人可能运行的代码中执行此操作!

事实上,a.foo 只是 a.__dict__['foo'] 的想法是一个巨大的简化。首先,我们已经遇到过有时在阅读时会跟在a.__class__.__dict__['foo'] 后面。另一个例子是a.__dict__ 显然不是a.__dict__['__dict__'],否则它会如何结束!?这个过程有些复杂,记录在the Data Model docs。

获得此行为的支持方法是使用特殊的 __setattr__ 和 __getattr__ 方法(也在这些数据模型文档中描述),如下所示:

class Borg:
    __shared_state = {}

    def __getattr__(self, name):
        try:
            return Borg.__shared_state[name]
        except KeyError:
            raise AttributeError

    def __setattr__(self, name, value):
        Borg.__shared_state[name] = value

【讨论】:

    【解决方案2】:

    你在那里做的事情很有趣,它基于可变性:

    您声明的初始__shared_state 是在执行任何代码之前创建的。该字典称为类属性,因为它链接到类,而不是实例(不使用self 进行声明)。这意味着__shared_state 在b1 和b2 之间共享,因为它是在它们之前创建的,并且因为它是dict,所以它是可变的。

    可变是什么意思?

    这意味着分配给两个不同实例的一个字典将引用相同的内存地址,即使我们更改字典,内存地址也将保持不变。这是一个探测:

    class Example:
    
        __shared_state = {1: 1}
    
        def __init__(self):
            self.__dict__ = self.__shared_state
            print(self.__shared_state)
    
    ex1 = Example()
    ex2 = Example()
    
    print(id(ex1.__dict__), id(ex2.__dict__))
    
    # Prints
    # {1: 1}
    # {1: 1}
    # 140704387518944 140704387518944
    

    注意他们有相同的 id 吗?那是因为它们引用的是同一个对象,并且由于 dictionary 类型是可变的,因此更改一个对象中的字典意味着您正在为两者更改它,因为它们是相同的:

    # Executing this
    ex1.val1 = 2
    # Equals this
    ex1.__dict__['val1'] = 2
    # Which also equals this
    Example.__shared_state['val1'] = 2 
    

    整数不会发生这种情况,整数是不可变的:

    class Example:
    
        __shared_state = 2
    
        def __init__(self):
            self.a = self.__shared_state
            print(self.__shared_state)
    
    ex1 = Example()
    ex2 = Example()
    
    ex2.a = 3
    
    print(id(ex1.a), id(ex2.a))
    # Prints
    # 2
    # 2
    # 9302176 9302208
    # Notice that once we change ex2.a, its ID changes!
    

    当你删除你的__shared_state时,你分配b1.val1 = 'Jaga Gola!!!'和b1.val2 = 'BOOOM!!!'的那一刻,它只是从b1分配给字典,这就是为什么当你尝试打印b2.val1和b2.val2它时引发错误。

    【讨论】:

    • 这是一个不错的答案,但您正在讨论与此处无关的可变性。你说“可变是什么意思?”下一句是“这意味着你的类的两个实例都引用了同一个字典”。这不是可变的意思:您可以拥有共享的不可变状态和不共享的可变状态,因此可变性实际上与共享状态无关。
    猜你喜欢
    • 2014-11-06
    • 1970-01-01
    • 2011-12-12
    • 1970-01-01
    • 1970-01-01
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    • 2011-11-26
    相关资源
    最近更新 更多