web-dev-qa-db-ja.com

pythonオブジェクトグラフを辞書に再帰的に変換する

単純なオブジェクトグラフから辞書にデータを変換しようとしています。タイプ情報やメソッドは必要ありません。また、オブジェクトに変換し直す必要もありません。

オブジェクトのフィールドから辞書を作成することに関するこの質問 を見つけましたが、再帰的には実行しません。

Pythonは比較的新しいので、私のソリューションが醜い、非Pythonである、不明瞭な方法で壊れている、または単なる古いNIHである可能性があるのではないかと心配しています。

私の最初の試みは、リストとディクショナリで試すまではうまくいくように見えました。渡されたオブジェクトに内部ディクショナリがあるかどうかを確認するだけで、そうでない場合は、インスタンスチェックをすべて行うよりも、値として扱う方が簡単なようでした。 )。私の以前の試みも、オブジェクトのリストに再帰しませんでした:

def todict(obj):
    if hasattr(obj, "__iter__"):
        return [todict(v) for v in obj]
    Elif hasattr(obj, "__dict__"):
        return dict([(key, todict(value)) 
            for key, value in obj.__dict__.iteritems() 
            if not callable(value) and not key.startswith('_')])
    else:
        return obj

これはうまく機能しているようで、例外は必要ありませんが、ここでケースがあるかどうかはまだわかりませんが、どこに落ちるのかわかりません。

任意の提案をいただければ幸いです。

35
Shabbyrobe

私自身の試みと、AnuragUniyalとLennartRegebroの回答から導き出された手がかりの融合は、私にとって最も効果的です。

def todict(obj, classkey=None):
    if isinstance(obj, dict):
        data = {}
        for (k, v) in obj.items():
            data[k] = todict(v, classkey)
        return data
    Elif hasattr(obj, "_ast"):
        return todict(obj._ast())
    Elif hasattr(obj, "__iter__") and not isinstance(obj, str):
        return [todict(v, classkey) for v in obj]
    Elif hasattr(obj, "__dict__"):
        data = dict([(key, todict(value, classkey)) 
            for key, value in obj.__dict__.items() 
            if not callable(value) and not key.startswith('_')])
        if classkey is not None and hasattr(obj, "__class__"):
            data[classkey] = obj.__class__.__name__
        return data
    else:
        return obj
46
Shabbyrobe

オブジェクトをJSONに再帰的に変換する1行のコード。

import json

def get_json(object):
  return json.loads(
    json.dumps(object, default=lambda o: getattr(o, '__dict__', str(o)))
  )

object = SomeClass()
print("Json = ", get_json(object))
18
Archit Dwivedi

ベースストリングまたはオブジェクトをチェックする目的が何であるかわかりませんか?また、dictは、そのような呼び出し可能オブジェクトを指す属性がない限り、呼び出し可能オブジェクトを含みませんが、その場合、そのオブジェクトの一部ではありませんか?

したがって、さまざまなタイプと値をチェックする代わりに、todictでオブジェクトを変換し、例外が発生した場合は、元の値を使用します。

todictは、objにdictがない場合にのみ例外を発生させます。例:.

class A(object):
    def __init__(self):
        self.a1 = 1

class B(object):
    def __init__(self):
        self.b1 = 1
        self.b2 = 2
        self.o1 = A()

    def func1(self):
        pass

def todict(obj):
    data = {}
    for key, value in obj.__dict__.iteritems():
        try:
            data[key] = todict(value)
        except AttributeError:
            data[key] = value
    return data

b = B()
print todict(b)

印刷{'b1':1、 'b2':2、 'o1':{'a1':1}}考慮すべき他のケースがあるかもしれませんが、それは良いスタートかもしれません

特殊なケースオブジェクトがスロットを使用している場合、取得できませんdict例:.

class A(object):
    __slots__ = ["a1"]
    def __init__(self):
        self.a1 = 1

スロットの場合の修正は、dictを直接使用する代わりにdir()を使用することです。

7
Anurag Uniyal

この答えは数年遅すぎることに気づきましたが、@ Shabbyrobeによる元のソリューションに対するPython 3.3+互換性のある変更であり、一般的にうまく機能しているので、共有する価値があるかもしれないと思いました私:

import collections
try:
  # Python 2.7+
  basestring
except NameError:
  # Python 3.3+
  basestring = str 

def todict(obj):
  """ 
  Recursively convert a Python object graph to sequences (lists)
  and mappings (dicts) of primitives (bool, int, float, string, ...)
  """
  if isinstance(obj, basestring):
    return obj 
  Elif isinstance(obj, dict):
    return dict((key, todict(val)) for key, val in obj.items())
  Elif isinstance(obj, collections.Iterable):
    return [todict(val) for val in obj]
  Elif hasattr(obj, '__dict__'):
    return todict(vars(obj))
  Elif hasattr(obj, '__slots__'):
    return todict(dict((name, getattr(obj, name)) for name in getattr(obj, '__slots__')))
  return obj

たとえば、呼び出し可能な属性に関心がない場合は、辞書の理解でそれらを取り除くことができます。

Elif isinstance(obj, dict):
  return dict((key, todict(val)) for key, val in obj.items() if not callable(val))
3
hbristow

Pythonでは、メタクラスなど、オブジェクトの動作をわずかに異なるものにする方法はたくさんあり、getattrをオーバーライドできるため、「魔法の」属性を使用できません。シースルーdictなど。要するに、どの方法を使用しても、一般的なケースで100%完全な全体像が得られる可能性は低いです。

したがって、答えは次のとおりです。現在のユースケースで機能する場合、コードは正しいです。 ;-)

やや一般的なコードを作成するには、次のようにします。

import types
def todict(obj):
    # Functions, methods and None have no further info of interest.
    if obj is None or isinstance(subobj, (types.FunctionType, types.MethodType))
        return obj

    try: # If it's an iterable, return all the contents
        return [todict(x) for x in iter(obj)]
    except TypeError:
        pass

    try: # If it's a dictionary, recurse over it:
        result = {}
        for key in obj:
            result[key] = todict(obj)
        return result
    except TypeError:
        pass

    # It's neither a list nor a dict, so it's a normal object.
    # Get everything from dir and __dict__. That should be most things we can get hold of.
    attrs = set(dir(obj))
    try:
        attrs.update(obj.__dict__.keys())
    except AttributeError:
        pass

    result = {}
    for attr in attrs:
        result[attr] = todict(getattr(obj, attr, None))
    return result            

そんな感じ。このコードはいますが、テストされていません。 getattrをオーバーライドした場合、これはまだカバーされていません。カバーされておらず、カバーできない可能性があるケースは他にもたくさんあると思います。 :)

2
Lennart Regebro

これを行うには時間がかかりますが簡単な方法は、jsonpickleを使用してオブジェクトをJSON文字列に変換し、次に_json.loads_を使用してオブジェクトをpython辞書:

dict = json.loads(jsonpickle.encode( obj, unpicklable=False ))

1
Tom

namedtuplesで機能するように、Shabbyrobeの回答を少し更新しました。

def obj2dict(obj, classkey=None):
    if isinstance(obj, dict):
        data = {}
        for (k, v) in obj.items():
            data[k] = obj2dict(v, classkey)
        return data
    Elif hasattr(obj, "_asdict"):
        return obj2dict(obj._asdict())
    Elif hasattr(obj, "_ast"):
        return obj2dict(obj._ast())
    Elif hasattr(obj, "__iter__"):
        return [obj2dict(v, classkey) for v in obj]
    Elif hasattr(obj, "__dict__"):
        data = dict([(key, obj2dict(value, classkey))
                     for key, value in obj.__dict__.iteritems()
                     if not callable(value) and not key.startswith('_')])
        if classkey is not None and hasattr(obj, "__class__"):
            data[classkey] = obj.__class__.__name__
        return data
    else:
        return obj
0
oroszgy

すべての解決策を調べたところ、@ hbristowの答えは私が探していたものに最も近いものでした。 enum.Enumエラーが発生し、RecursionError: maximum recursion depth exceededでオブジェクトを並べ替えて、__slots__を定義するオブジェクトよりも優先されるため、__dict__処理を追加しました。

def todict(obj):
  """
  Recursively convert a Python object graph to sequences (lists)
  and mappings (dicts) of primitives (bool, int, float, string, ...)
  """
  if isinstance(obj, str):
    return obj
  Elif isinstance(obj, enum.Enum):
    return str(obj)
  Elif isinstance(obj, dict):
    return dict((key, todict(val)) for key, val in obj.items())
  Elif isinstance(obj, collections.Iterable):
    return [todict(val) for val in obj]
  Elif hasattr(obj, '__slots__'):
    return todict(dict((name, getattr(obj, name)) for name in getattr(obj, '__slots__')))
  Elif hasattr(obj, '__dict__'):
    return todict(vars(obj))
  return obj
0
Will
def list_object_to_dict(lst):
    return_list = []
    for l in lst:
        return_list.append(object_to_dict(l))
    return return_list

def object_to_dict(object):
    dict = vars(object)
    for k,v in dict.items():
        if type(v).__name__ not in ['list', 'dict', 'str', 'int', 'float']:
                dict[k] = object_to_dict(v)
        if type(v) is list:
            dict[k] = list_object_to_dict(v)
    return dict
0