Как заставить super () работать, вручную заполняя ячейку __class__? - PullRequest
10 голосов
/ 03 февраля 2011

В Python 3 можно использовать super() вместо super(MyClass, self), но это работает только в методах, которые были определены внутри класса. Как описано в статье Микеле Симионато , следующий пример не работает:

def __init__(self):
    print('calling __init__')
    super().__init__()

class C(object):
    __init__ = __init__

if __name__ == '__main__':
    c = C()

Сбой, потому что super() ищет __class__ ячейку , которая в этом случае не определена.

Можно ли установить эту ячейку вручную после определения функции или это невозможно?

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

__init__.__class_cell_thingy__ = C

Конечно, я бы использовал это только в ситуации, когда присвоение класса однозначно / уникально (весь процесс добавления методов в класс в моем случае автоматизирован, поэтому было бы просто добавить такую ​​строку).

Ответы [ 3 ]

19 голосов
/ 03 февраля 2011

Серьезно: вы действительно не хотите этого делать.

Но для опытных пользователей Python полезно это понять, поэтому я объясню это.

Ячейки и freevarsявляются значениями, назначенными при создании замыкания.Например,

def f():
    a = 1
    def func():
        print(a)
    return func

f возвращает замыкание на основе func, сохраняя ссылку на a.Эта ссылка хранится в ячейке (на самом деле во freevar, но какая из них зависит от реализации).Вы можете проверить это:

myfunc = f()
# ('a',)
print(myfunc.__code__.co_freevars)
# (<cell at 0xb7abce84: int object at 0x82b1de0>,)
print(myfunc.__closure__)

(«ячейки» и «freevars» очень похожи. У Freevars есть имена, где ячейки имеют индексы. Они оба хранятся в func.__closure__, причем ячейки идут первыми.Мы заботимся только о freevars здесь, поскольку именно это __class__.)

Как только вы поймете это, вы увидите, как на самом деле работает super ().Любая функция, содержащая вызов super, на самом деле является замыканием с произвольным именем __class__ (которое также добавляется, если вы сами ссылаетесь на __class__):

class foo:
    def bar(self):
        print(__class__)

(Предупреждение: этоздесь все становится злым.)

Эти клетки видны в func.__closure__, но доступны только для чтения;ты не можешь это изменить.Единственный способ изменить это - создать новую функцию, что делается с помощью конструктора types.FunctionType.Тем не менее, ваша __init__ функция вообще не имеет __class__ freevar - поэтому нам нужно добавить ее.Это означает, что мы также должны создать новый объект кода.

Код ниже делает это.Я добавил базовый класс B для наглядности.Этот код делает некоторые предположения, например.что у __init__ еще нет свободной переменной с именем __class__.

Здесь есть еще один хак: кажется, нет конструктора для типа ячейки.Чтобы обойти это, создается фиктивная функция C.dummy, в которой есть необходимая переменная ячейки.

import types

class B(object):
    def __init__(self):
        print("base")

class C(B):
    def dummy(self): __class__

def __init__(self):
    print('calling __init__')
    super().__init__()

def MakeCodeObjectWithClass(c):
    """
    Return a copy of the code object c, with __class__ added to the end
    of co_freevars.
    """
    return types.CodeType(c.co_argcount, c.co_kwonlyargcount, c.co_nlocals,
            c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names,
            c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno,
            c.co_lnotab, c.co_freevars + ('__class__',), c.co_cellvars)

new_code = MakeCodeObjectWithClass(__init__.__code__)
old_closure = __init__.__closure__ or ()
C.__init__ = types.FunctionType(new_code, globals(), __init__.__name__,
    __init__.__defaults__, old_closure + (C.dummy.__closure__[0],))

if __name__ == '__main__':
    c = C()
2 голосов
/ 03 февраля 2011

Вы можете использовать словарь функции.

def f(self):
    super(f.owner_cls, self).f()
    print("B")

def add_to_class(cls, member, name=None):
    if hasattr(member, 'owner_cls'):
        raise ValueError("%r already added to class %r" % (member, member.owner_cls))
    member.owner_cls = cls
    if name is None:
        name = member.__name__
    setattr(cls, name, member)

class A:
     def f(self):
         print("A")

class B(A):
     pass

add_to_class(B, f)

B().f()

Вы даже можете добавить другой атрибут member_name, если не хотите жестко задавать имя имени члена внутри функции.

1 голос
/ 03 февраля 2011

Может быть, но вы бы?В обоих случаях вам нужно как-то явно указать, какой это класс, потому что неявный способ не сработал.Может быть, вы можете установить ячейку как-то явно, но нет причин делать это.Просто передайте параметры в явном виде.

def __init__(self):
    print('calling __init__')
    super(self.__class__, self).__init__()

class C(object):
    __init__ = __init__

if __name__ == '__main__':
    c = C()

(Лучше, если вы сможете напрямую передать действительный класс, например:

def __init__(self):
    print('calling __init__')
    super(C, self).__init__()

class C(object):
    __init__ = __init__

if __name__ == '__main__':
    c = C()

Но если вы можете, вы можете поместить__init__ на C напрямую, поэтому предположим, что вы не можете.

...