【问题标题】:Why do attribute references act like this with Python inheritance? [duplicate]为什么属性引用在 Python 继承中会这样? [复制]
【发布时间】:2010-09-17 10:19:11
【问题描述】:

以下看起来很奇怪.. 基本上,somedata 属性似乎在继承自the_base_class 的所有类之间共享。

class the_base_class:
    somedata = {}
    somedata['was_false_in_base'] = False


class subclassthing(the_base_class):
    def __init__(self):
            print self.somedata


first = subclassthing()
{'was_false_in_base': False}
first.somedata['was_false_in_base'] = True
second = subclassthing()
{'was_false_in_base': True}
>>> del first
>>> del second
>>> third = subclassthing()
{'was_false_in_base': True}

__init__ 函数中定义self.somedata 显然是解决此问题的正确方法(因此每个类都有自己的somedata dict)-但是什么时候需要这种行为?

【问题讨论】:

    标签: python class inheritance


    【解决方案1】:

    请注意,您看到的部分行为是由于 somedatadict,而不是像 bool 这样的简单数据类型。

    例如,看看这个不同的例子,它的行为不同(尽管非常相似):

    class the_base_class:
        somedata = False
    
    class subclassthing(the_base_class):
        def __init__(self):
            print self.somedata
    
    
    >>> first = subclassthing()
    False
    >>> first.somedata = True
    >>> print first.somedata
    True
    >>> second = subclassthing()
    False
    >>> print first.somedata
    True
    >>> del first
    >>> del second
    >>> third = subclassthing()
    False
    

    这个例子的行为与问题中给出的不同的原因是因为这里first.somedata被赋予了一个新值(对象True),而在第一个例子中,first.somedata引用的dict对象(以及其他子类实例)正在修改。

    请参阅 Torsten Marek 对此答案的评论以获得进一步说明。

    【讨论】:

    • 您是说简单数据类型不共享,而复杂数据类型共享?
    • 不,它们是共享的,Python 中的每个数据类型都是引用类型,甚至整数和布尔值等。原因是 first.somedata 不包含值 False/True,它引用了对象假/真。如果它被重新分配,它只是引用一个不同的对象。
    • @Oscar,我怀疑差异是由于参考的性质。当 somedata 是字典时, first 和 second 有自己的引用,但每个引用都指向内存中的同一个字典。当 somedate 是布尔值时,它们仍然会获得自己的副本,但它们会直接修改布尔值。
    【解决方案2】:

    你是对的,somedata 在类的所有实例及其子类之间共享,因为它是在类定义时创建的。线条

    somedata = {}
    somedata['was_false_in_base'] = False
    

    在定义类时执行,即当解释器遇到 class 语句时 - not 在创建实例时执行(想想 Java 中的静态初始化程序块)。如果类实例中不存在属性,则检查类对象的属性。

    在类定义时,您可以运行任意代码,如下所示:

     import sys
     class Test(object):
         if sys.platform == "linux2":
             def hello(self):
                  print "Hello Linux"
         else:
             def hello(self):
                  print "Hello ~Linux"
    

    在 Linux 系统上,Test().hello() 将打印 Hello Linux,在所有其他系统上,将打印其他字符串。

    相比之下,__init__ 中的对象是在实例化时创建的,并且仅属于实例(当它们被分配给self 时):

    class Test(object):
        def __init__(self):
            self.inst_var = [1, 2, 3]
    

    在类对象而不是实例上定义的对象在许多情况下很有用。例如,您可能希望缓存类的实例,以便可以共享具有相同成员值的实例(假设它们应该是不可变的):

    class SomeClass(object):
        __instances__ = {}
    
        def __new__(cls, v1, v2, v3):
            try:
                return cls.__insts__[(v1, v2, v3)]
            except KeyError:
                return cls.__insts__.setdefault(
                   (v1, v2, v3), 
                   object.__new__(cls, v1, v2, v3))
    

    大多数情况下,我将类体中的数据与元类或通用工厂方法结合使用。

    【讨论】:

    • 那么你如何声明一个实例变量呢?
    • 我在回答中添加了一个示例。
    • 简单数据和复杂数据有区别吗?正如 TimB 所说的那样?
    • @Oscar:问一个新问题:) 否则,请参阅我对另一个答案的评论。
    • 实例的 dict 中没有引用类属性。相反,python的查找方法是首先检查实例变量,如果没有找到变量,则检查类的变量(然后是基类)。 dict 中的任何内容都明确设置在实例上。
    【解决方案3】:

    我认为理解这一点(以便您可以预测行为)的最简单方法是意识到您的 somedata 是类的属性,而不是该类的实例(如果您以这种方式定义它)。

    实际上始终只有一个somedata,因为在您的示例中,您没有分配给该名称,而是使用它来查找字典,然后为其分配一个项目(键,值)。这是 python 解释器工作方式的结果,一开始可能会令人困惑。

    【讨论】:

      猜你喜欢
      • 2012-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-27
      • 1970-01-01
      • 2019-05-20
      • 2013-06-20
      相关资源
      最近更新 更多