Почему ссылки на атрибуты действуют так, как наследование Python?

Похоже, странно. В принципе, атрибут 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}

Определение self.somedata в функции __init__, очевидно, является правильным способом обойти это (поэтому у каждого класса есть собственный somedata dict) - но когда такое поведение желательно?

Ответ 1

Вы правы, somedata разделяется между всеми экземплярами класса и его подклассов, потому что он создается во время определения класса. Линии

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

выполняются, когда класс определен, т.е. когда интерпретатор встречает оператор class - не, когда экземпляр создан (подумайте о статических инициализационных блоках в 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, а во всех остальных системах будет напечатана другая строка.

В constrast объекты в __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))

В основном, я использую данные в телах классов в сочетании с метаклассами или общими методами factory.

Ответ 2

Обратите внимание, что часть видимого вами поведения связана с тем, что somedata является dict, в отличие от простого типа данных, такого как 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), тогда как в первом примере объект dict, на который ссылается first.somedata ( а также другими экземплярами подкласса).

См. комментарий Torsten Mareks к этому ответу для дальнейшего уточнения.

Ответ 3

Я думаю, что самый простой способ понять это (чтобы вы могли предсказать поведение) - понять, что ваш somedata является атрибутом класса, а не экземпляром этого класса, если вы его определяете.

В действительности существует только один somedata, потому что в вашем примере вы не назначали это имя, но использовали его для поиска дикта, а затем назначали ему элемент (ключ, значение). Это результат, который является следствием работы интерпретатора python и может сбить с толку вначале.