(Python) Замыкание создано, когда этого не ожидалось

Я получил неожиданное закрытие при создании вложенного класса. Я подозреваю, что это что-то связанное с метаклассами, супер или обоими. Это определенно связано с тем, как создаются замыкания. Я использую python2.7.

Вот пять упрощенных примеров, демонстрирующих ту же проблему, что и я (все они основаны на первом):

ПРИМЕР 1:

class Metaclass(type): 
    def __init__(self, name, bases, dict): 
        self.CONST = 5 

class Base(object): 
    __metaclass__=Metaclass 
    def __init__(self): 
        "Set things up." 

class Subclass(Base):
    def __init__(self, name):
        super(Subclass, self).__init__(self)
        self.name = name
    def other(self, something): pass

class Test(object):
    def setup(self):
        class Subclass(Base):
            def __init__(self, name):
                super(Subclass, self).__init__(self)
                self.name = name
            def other(self, something): pass
        self.subclass = Subclass
        class Subclass2(Base):
            def __init__(self, name):
                super(Subclass, self).__init__(self)
        self.subclass2 = Subclass2

"0x%x" % id(Metaclass)
# '0x8257f74'
"0x%x" % id(Base)
# '0x825814c'
t=Test()
t.setup()
"0x%x" % id(t.subclass)
# '0x8258e8c'
"0x%x" % id(t.subclass2)
# '0x825907c'
t.subclass.__init__.__func__.__closure__
# (<cell at 0xb7d33d4c: Metaclass object at 0x8258e8c>,)
t.subclass.other.__func__.__closure__
# None
t.subclass2.__init__.__func__.__closure__
# (<cell at 0xb7d33d4c: Metaclass object at 0x8258e8c>,)
Subclass.__init__.__func__.__closure__
# None

ПРИМЕР 2:

class Test2(object):
    def setup(self):
        class Subclass(Base):
            def __init__(self, name):
                self.name = name
            def other(self, something): pass
        self.subclass = Subclass

t2=Test2()
t2.setup()
t2.subclass.__init__.__func__.__closure__
# None

ПРИМЕР 3:

class Test3(object):
    def setup(self):
        class Other(object):
            def __init__(self): 
                super(Other, self).__init__()
        self.other = Other
        class Other2(object):
            def __init__(self): pass
        self.other2 = Other2

t3=Test3()
t3.setup()
"0x%x" % id(t3.other)
# '0x8259734'
t3.other.__init__.__func__.__closure__
# (<cell at 0xb7d33e54: type object at 0x8259734>,)
t3.other2.__init__.__func__.__closure__
# None

ПРИМЕР 4:

class Metaclass2(type): pass

class Base2(object): 
    __metaclass__=Metaclass2 
    def __init__(self): 
        "Set things up." 

class Base3(object): 
    __metaclass__=Metaclass2 

class Test4(object):
    def setup(self):
        class Subclass2(Base2):
            def __init__(self, name):
                super(Subclass2, self).__init__(self)
        self.subclass2 = Subclass2
        class Subclass3(Base3):
            def __init__(self, name):
                super(Subclass3, self).__init__(self)
        self.subclass3 = Subclass3
        class Subclass4(Base3):
            def __init__(self, name):
                super(Subclass4, self).__init__(self)
        self.subclass4 = Subclass4

"0x%x" % id(Metaclass2)
# '0x8259d9c'
"0x%x" % id(Base2)
# '0x825ac9c'
"0x%x" % id(Base3)
# '0x825affc'
t4=Test4()
t4.setup()
"0x%x" % id(t4.subclass2)
# '0x825b964'
"0x%x" % id(t4.subclass3)
# '0x825bcac'
"0x%x" % id(t4.subclass4)
# '0x825bff4'
t4.subclass2.__init__.__func__.__closure__
# (<cell at 0xb7d33d04: Metaclass2 object at 0x825b964>,)
t4.subclass3.__init__.__func__.__closure__
# (<cell at 0xb7d33e9c: Metaclass2 object at 0x825bcac>,)
t4.subclass4.__init__.__func__.__closure__
# (<cell at 0xb7d33ddc: Metaclass2 object at 0x825bff4>,)

ПРИМЕР 5:

class Test5(object):
    def setup(self):
        class Subclass(Base):
            def __init__(self, name):
                Base.__init__(self)
        self.subclass = Subclass

t5=Test5()
t5.setup()
"0x%x" % id(t5.subclass)
# '0x8260374'
t5.subclass.__init__.__func__.__closure__
# None

Вот что я понимаю (примеры ссылок):

  • Метаклассы наследуются, поэтому Subclass получает метакласс Base.
  • Затрагивается только метод __init__, метод Subclass.other — нет (#1).
  • Удаление Subclass.other ничего не меняет (#1).
  • Удаление self.name=name из Subclass.__init__ ничего не меняет (#1).
  • Объект в замыкающей ячейке не является функцией.
  • Объект не Metaclass или Base, а какой-то объект типа Metaclass, точно так же, как Base (#1).
  • Объект на самом деле является объектом типа вложенного Subclass (#1).
  • Замыкающие ячейки для t1.subclass.__init__ и t1.subclass2.__init__ одинаковы, хотя они принадлежат к двум разным классам (#1).
  • Когда я не вкладываю создание Subclass (#1), замыкание не создается.
  • Когда я не вызываю super(...).__init__ в Subclass.init__, замыкание не создается (#2).
  • Если я не назначу __metaclass__ и наследую от object, то появится то же самое поведение (#3).
  • Объект в закрытой ячейке для t3.other.__init__ — это t3.other (#3).
  • То же самое происходит, если в метаклассе нет __init__ (#4).
  • То же самое происходит, если у Base нет __init__ (#4).
  • Замыкающие ячейки для трех подклассов в примере 4 все разные, и каждая соответствует соответствующему классу (#4).
  • Когда super(...).__init__ заменяется на Base.__init__(self), замыкание исчезает (#5).

Вот чего я не понимаю:

  • Почему замыкание устанавливается для __init__?
  • Почему закрытие не устанавливается для других?
  • Почему объект в ячейке закрытия установлен в класс, к которому принадлежит __init__?
  • Почему это происходит только при вызове super(...).__init__?
  • Почему этого не происходит при вызове Base.__init__(self)?
  • Имеет ли это вообще какое-либо отношение к использованию метаклассов (вероятно, поскольку метакласс по умолчанию — type)?

Спасибо за помощь!

-Эрик

(Обновление) Вот что я нашел тогда (на основе понимания Джейсона):

def something1():
    print "0x%x" % id(something1)
    def something2():
        def something3():
            print "0x%x" % id(something1)
            print "0x%x" % id(something2)
            print "0x%x" % id(something3)
        return something3
    return something2

something1.__closure__
# None
something1().__closure__
# 0xb7d4056c
# (<cell at 0xb7d33eb4: function object at 0xb7d40df4>,)
something1()().__closure__
# 0xb7d4056c
# (<cell at 0xb7d33fec: function object at 0xb7d40e64>, <cell at 0xb7d33efc: function object at 0xb7d40e2c>)
something1()()()
# 0xb7d4056c
# 0xb7d4056c
# 0xb7d40e9c
# 0xb7d40ed4

Во-первых, имя функции находится в пределах ее собственного тела. Во-вторых, функции получают замыкания для функций, в которых они определены, если они ссылаются на эти функции.

Я не осознавал, что имя функции находится в такой области. То же самое касается классов. Когда класс определен в области действия функции, любые ссылки на имя этого класса внутри методов класса приводят к тому, что класс привязывается к замыканию функции этого метода, например так:

def test():
    class Test(object):
        def something(self):
            print Test
    return Test

test()
# <class '__main__.Test'>
test().something.__func__.__closure__
# (<cell at 0xb7d33c2c: type object at 0x825e304>,)

Однако, поскольку замыкания не могут быть созданы для не-функций, следующее не выполняется:

def test():
    class Test(object):
        SELF=Test
        def something(self):
            print Test
    return Test

# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 2, in test
#   File "<stdin>", line 3, in Test
# NameError: free variable 'Test' referenced before assignment in enclosing scope

Хорошая вещь!


person Eric Snow    schedule 30.07.2010    source источник


Ответы (1)


Почему замыкание устанавливается для __init__?

Он ссылается на локальную переменную (а именно Subclass) в объемлющей функции (а именно setup).

Почему закрытие не устанавливается для other?

Потому что он не ссылается ни на какие локальные переменные (или параметры) в каких-либо объемлющих функциях.

Почему объект в ячейке закрытия установлен в класс, к которому принадлежит __init__?

Это значение объемлющей переменной, на которую ссылаются.

Почему это происходит только при вызове super(...).__init__?

Почему этого не происходит, когда вызывается Base.__init__(self)?

Потому что Base не является локальной переменной ни в какой объемлющей функции.

Имеет ли это вообще какое-либо отношение к использованию метаклассов?

No.

person Jason Orendorff    schedule 30.07.2010
comment
Мне странно, что это работает так с определениями классов. Непредвиденный. - person Eric Snow; 30.07.2010
comment
[...] Это потому, что Base является глобальным? Да. - person Jason Orendorff; 30.07.2010
comment
Интересный. Думаю, я никогда не понимал, что имя функции находится в пределах тела функции. - person Eric Snow; 30.07.2010
comment
Хотя что-то смешно. Когда вы определяете класс (или функцию) в глобальной области, замыкание не создается, даже если вы ссылаетесь на имя класса внутри одного из его методов, и это свободная переменная в локальной области этого метода. - person Eric Snow; 30.07.2010
comment
Я предполагаю, что имя класса помещается в область, в которой оно определено (и объект класса привязывается к нему после выполнения тела определения). Если это область действия какой-либо функции, то в методе создается замыкание. Если это глобальная область, то замыкание не создается. Я бы ожидал, что ни в одном случае не будет создано закрытие. - person Eric Snow; 30.07.2010
comment
Я бы подумал, что имена классов и функций были бы особыми случаями для генерации замыканий (или, скорее, без генерации). - person Eric Snow; 30.07.2010
comment
Глобальные переменные проходят через func_global, а не через func_closure. Глобальное пространство имен сильно отличается от локальных пространств имен в Python. Не только как деталь реализации. - person Jason Orendorff; 31.07.2010
comment
Я бы подумал, что имена классов и функций были бы особыми случаями для генерации замыканий, которых я не понимаю. Что вы ожидали? Разная языковая семантика? Или просто другая техника реализации? - person Jason Orendorff; 31.07.2010
comment
Да, я вижу это сейчас. Единственный способ, которым метод __init__ узнает о своем собственном классе, — либо через себя, либо через замыкание. Функция ничего не знает о классе, в котором функция обернута как метод. Таким образом, в этом случае замыкание — единственный способ узнать о классе. Я забыл различие между методом и функцией. В итоге super вызвал ситуацию, которая привела меня в эту поездку. - person Eric Snow; 31.07.2010