получение пути к классу или пространства имен класса в python, даже если он вложенный - PullRequest
7 голосов
/ 17 декабря 2010

В настоящее время я пишу модуль сериализации в Python, который может сериализовать определенные пользователем классы. для этого мне нужно получить полное пространство имен объекта и записать его в файл. Затем я могу использовать эту строку для воссоздания объекта.

например, предположим, что у нас есть следующая структура класса в файле с именем A.py

class B:
    class C:
        pass

теперь с предположением, что my_klass_string является строкой "A::B::C"

klasses = my_klass_string.split("::")
if globals().has_key(klasses[0]):   
    klass = globals()[klasses[0]]
else:
    raise TypeError, "No class defined: %s} " % klasses[0]
if len(klasses) > 1:
    for klass_string in klasses:
        if klass.__dict__.has_key(klass_string):
            klass = klass.__dict__[klass_string]
        else:
            raise TypeError, "No class defined: %s} " % klass_string            
klass_obj = klass.__new__(klass)

Я могу создать экземпляр класса C, даже если он находится в классе B в модуле A. приведенный выше код эквивалентен вызову eval(klass_obj = A.B.C.__new__(A.B.C))

примечание: Я использую __new__() здесь, потому что я воссоздаю сериализованный объект, и я не хочу инициировать объект, так как я не знаю, какие параметры принимает метод класса __init__. Я хочу создать объект без вызова init , а затем присвоить ему атрибуты позже.

любым способом я могу создать объект класса A.B.C из строки. а как мне пойти другим путем? как получить строку, описывающую полный путь к классу из экземпляра этого класса, даже если этот класс является вложенным?

Ответы [ 4 ]

6 голосов
/ 17 декабря 2010

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

Но поскольку нет никакой причины когда-либо иметь такую ​​иерархию классов, это не проблема.: -)

Кроме того, я знаю, что вы не хотите слышать это на данном этапе своей работы, но:

Кросс-платформенная сериализация - интересная тема, но делать это с объектамиподобное вряд ли будет очень полезным, поскольку в целевой системе должна быть установлена ​​точно такая же иерархия объектов.Поэтому у вас должно быть две системы, написанные на двух разных языках, которые точно эквивалентны.Это почти невозможно, и, вероятно, оно того не стоит.

Например, вы не сможете использовать какой-либо объект из стандартной библиотеки Pythons, так как его нет в Ruby.Конечным результатом является то, что вы должны создать свою собственную иерархию объектов, которая в конечном итоге использует только базовые типы, такие как строки и числа.И в этом случае ваши объекты просто стали содержимым для базовых примитивов , и тогда вы все равно можете также сериализовать все с помощью JSON или XML.

6 голосов
/ 17 декабря 2010

Вы не можете получить «полный путь к классу, заданному экземпляром класса», потому что в Python такого нет.Например, основываясь на вашем примере:

>>> class B(object):
...     class C(object):
...             pass
... 
>>> D = B.C
>>> x = D()
>>> isinstance(x, B.C)
True

Каким должен быть "путь к классу" x?D или B.C?Оба одинаково действительны, и поэтому Python не дает никаких средств отличить одно от другого.

Действительно, даже у модуля Python pickle есть проблемы с выбором объекта x:

>>> import pickle
>>> t = open('/tmp/x.pickle', 'w+b')
>>> pickle.dump(x, t)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/pickle.py", line 1362, in dump
    Pickler(file, protocol).dump(obj)
  ...
  File "/usr/lib/python2.6/pickle.py", line 748, in save_global
   (obj, module, name))
  pickle.PicklingError: Can't pickle <class '__main__.C'>: it's not found as __main__.C

Итак, в общем, я не вижу другой возможности, кроме добавления атрибута ко всем вашим классам (скажем, _class_path), и ваш код сериализации будет искать его для записи имени класса в сериализованный формат:

class A(object):
  _class_path = 'mymodule.A'
  class B(object):
    _class_path = 'mymodule.A.B'
    ...

Вы даже можете сделать это автоматически с некоторой магией метакласса (но также прочитайте другие комментарии в той же публикации SO для предупреждений, которые могут применяться, если вы выполните D=B.C выше).

Тем не менее, если вы можете ограничить свой код сериализации до (1) экземпляров классов нового стиля и (2) эти классы определены на верхнем уровне модуля, товы можете просто скопировать то, что делает pickle (функция save_global в строках 730--768 в pickle.py из Python 2.6).

Идея состоит в том, что каждый класс нового стиля определяет атрибуты __name__ и__module__, которые являются строкамиэто расширение до имени класса (как в источниках) и имени модуля (как в sys.modules);сохранив их, вы можете позже импортировать модуль и получить экземпляр класса:

__import__(module_name)
class_obj = getattr(sys.modules[module_name], class_name)
1 голос
/ 17 декабря 2010

В настоящее время я пишу модуль сериализации в Python, который может сериализовать определенные пользователем классы.

Не .Стандартная библиотека уже включает в себя один.В зависимости от того, как вы считаете, на самом деле, оно включает как минимум два (pickle и shelve).

0 голосов
/ 20 декабря 2012

Есть два способа сделать это.

Раствор 1

Первый проходит через сборщик мусора.

B -> __dict__ -> C

это код:

>>> class B(object):
    class C(object):
        pass

>>> gc.get_referrers(B.C) # last element in the list
[<attribute '__dict__' of 'C' objects>, <attribute '__weakref__' of 'C' objects>, (<class '__main__.C'>, <type 'object'>), {'__dict__': <attribute '__dict__' of 'B' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'B' objects>, 'C': <class '__main__.C'>, '__doc__': None}] 

>>> gc.get_referrers(gc.get_referrers(B.C)[-1]) # first element in this list
[<class '__main__.B'>, [<attribute '__dict__' of 'C' objects>, <attribute '__weakref__' of 'C' objects>, (<class '__main__.C'>, <type 'object'>), {'__dict__': <attribute '__dict__' of 'B' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'B' objects>, 'C': <class '__main__.C'>, '__doc__': None}]]

>>> gc.get_referrers(gc.get_referrers(B.C)[-1])[0]
<class '__main__.B'>

Алгоритм:

  1. поиск словаря класса с таким же __module__, как у C
  2. возьмите класс, используйте атрибут 'C'
  3. если этот класс является вложенным. делай 1. храбро

Решение 2

использовать исходный файл. используйте inspect для получения строк класса и сканирования вверх для поиска новых классов, которые его вкладывают.

Примечание: я не знаю чистого пути в Python 2, но Python 3 предоставляет один.

...