2013-09-12 5 views
1

Недавно я столкнулся с проблемой в расширении python на основе C, пытаясь создать объекты без вызова его конструктора - это требование расширения.PyInstance_NewRaw() со старыми и новыми классами стилей

Класс будет использоваться для создания экземпляров получается динамически: в какой-то момент, у меня есть экземпляр x класс которого я хотел бы использовать для создания других экземпляров, поэтому я храню x.__class__ для последующего использования - пусть это значение будет klass ,

В более позднем пункте я вызываю PyInstance_NewRaw(klass, PyDict_New()), а затем возникает проблема. Кажется, что если klass является классом старого стиля, результатом этого вызова является желаемый новый экземпляр. Однако, если это класс нового типа, результат NULL и исключения, сгенерированные является:

SystemError: ../Objects/classobject.c:521: bad argument to internal function

Для записи, я использую Python версии 2.7.5. Погуляв, я заметил не более одного другого человека, который искал решение (и мне показалось, что он делает обходное решение, но не детализировал его).

Для записи # 2: экземпляры расширения является создание прокси для этих же x случаев - в x.__class__ и x.__dict__ «s известны, поэтому расширение порождая новые экземпляры на основе __class__ (с использованием вышеупомянутой функции C) и установка соответствующего __dict__ в новый экземпляр (данные __dict__ имеют данные совместной обработки между процессами). Не только концептуально проблематично вызвать второй экземпляр __init__ (первый: это состояние уже известно, во-вторых: ожидаемое поведение для ctors заключается в том, что они должны быть вызваны ровно один раз для каждого экземпляра), это также нецелесообразно, поскольку расширение не могут определить аргументы и их порядок для вызова __init__() для каждого экземпляра в системе. Кроме того, изменение __init__ каждого класса в системе, экземпляры которого могут быть прокси-серверами и информировать их о существовании механизма прокси-сервера, к которому они будут подвергнуты, концептуально проблематичны (они не должны знать об этом) и непрактичны.

Итак, мой вопрос: как выполнить то же поведение PyInstance_NewRaw, независимо от стиля класса экземпляра?

+0

[Я должен спросить ...] (http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), почему это требование расширения?Должна быть какая-то основная причина, по которой вам это нужно ... и, вероятно, лучший способ удовлетворить эту основную причину, чем это требование. – abarnert

+0

@abarnert, не вдаваясь в подробности, я отредактировал сообщение, чтобы объяснить, почему это требование. –

+0

Я думаю, что _is_ намного проще. Я отредактирую свой ответ, чтобы объяснить. – abarnert

ответ

1

Тип классов нового стиля не instance, это сам класс. Таким образом, методы PyInstance_* даже не имеют смысла для классов нового стиля.

В самом деле, the documentation явно объясняет это:

Note that the class objects described here represent old-style classes, which will go away in Python 3. When creating new types for extension modules, you will want to work with type objects (section Type Objects).

Таким образом, вы должны написать код, который проверяет klass является ли старым стилем или класс нового стиля и делает соответствующую вещь для каждого случая. Тип старого стиля - PyClass_Type, а тип класса нового стиля - либо PyType_Type, либо пользовательский метакласс.

Между тем нет прямого эквивалента PyInstance_NewRaw для классов нового стиля. Или, скорее, прямой эквивалентный вызов его слота tp_alloc, а затем добавление dict-will даст вам нефункциональный класс. Вы могли бы попытаться дублировать всю другую подходящую работу, но это будет сложно. В качестве альтернативы вы можете использовать tp_new, но это сделает неправильную вещь, если в классе (или любой из его баз) есть пользовательская функция __new__. См. Отклоненные исправления от #5180 для некоторых идей.

Но на самом деле то, что вы пытаетесь сделать, вероятно, не является хорошей идеей в первую очередь. Может быть, если вы объясните, почему это требование, и что вы пытаетесь сделать, был бы лучший способ сделать это.


Если цель состоит в том, чтобы строить объекты, создавая новый неинициализированный экземпляр класса, а затем скопировать над его _dict__ из инициализированную прототипа, есть гораздо проще решение, я думаю, что будет работать для вас:

__class__ - записываемый атрибут. Так (показывая его в Python, С API является в основном то же самое, только намного более подробный, и я, вероятно, завинтить refcounting где-то):

class NewStyleDummy(object): 
    pass 
def make_instance(cls, instance_dict): 
    if isinstance(cls, types.ClassType): 
     obj = do_old_style_thing(cls) 
    else: 
     obj = NewStyleDummy() 
     obj.__class__ = cls 
    obj.__dict__ = instance_dict 
    return obj 

Новый объект будет экземпляром cls - в частности, он будет иметь тот же словарь класса, в том числе MRO, метакласса и т.д.

Это не будет работать, если cls имеет метакласса, что требуется для его строительства, или метод пользовательского __new__ или __slots__ ... но потом ваш дизайн копирования по __dict__ в любом случае не имеет смысла. Я считаю, что в любом случае, когда что-то может работать, это простое решение будет работать.


Вызов cls.__new__ кажется хорошим решением в первом, но на самом деле это не так. Позвольте мне объяснить предысторию.

Когда вы сделаете это:

foo = Foo(1, 2) 

(где Foo новый стиль класса), он преобразуется в нечто вроде этого псевдокода:

foo = Foo.__new__(1, 2) 
if isinstance(foo, Foo): 
    foo.__init__(1, 2) 

Проблема заключается в том, что, если Foo или одна из его оснований определила метод __new__, он будет ожидать получения аргументов от вызова конструктора, как и метод __init__.

Как вы объяснили в своем вопросе, вы не знаете аргументы вызова конструктора - на самом деле это основная причина, по которой вы не можете вызвать обычный метод __init__. Таким образом, вы также не можете позвонить __new__.

Базовая реализация __new__ принимает и игнорирует любые аргументы, которые она дала. Итак, если ни один из ваших классов не имеет переопределения __new__ или __metaclass__, вам это удастся, из-за причуды в object.__new__ (квитанция, которая, кстати, работает по-разному в Python 3.x). Но это те же самые случаи, с которыми может справиться предыдущее решение, и это решение работает по гораздо более очевидной причине.

Путь другой: предыдущее решение зависит от того, кто не определяет __new__, потому что он никогда не вызывает __new__. Это решение зависит от того, что никто не определяет __new__, потому что он вызывает __new__ с неправильными аргументами.

+0

Во-первых, большое спасибо за ваши предложения. Ссылка на предоставленную вами проблему дала мне несколько советов. Как я понял, все объекты разделяют по умолчанию '__new__', который создает экземпляры данного класса без вызова' __init__'. Я смог получить то, что кажется работоспособным, используя что-то эквивалентное 'new_x = klass .__ new __ (klass)'. –

+0

@ThiagoSilva: Ну, вы должны передать аргументы конструктора, которых у вас нет - на '__new__'. Если никто не переопределяет '__new__' или не задает' __metaclass__', они игнорируются, поэтому это не проблема. Но если вы не хотите поддерживать '__new__' или' __metaclass__', вы можете использовать более простое решение только для установки '__class__'. – abarnert

+0

Фактически, он работал без вызова '__init__': если' x' является экземпляром класса нового класса 'X' с' __init__' arity> 0, 'x .__ class __.__ new __ (x .__ class __)' created экземпляр 'X', не вызывая соответствующий' __init__' –

 Смежные вопросы

  • Нет связанных вопросов^_^