ずっと苦しい、これ。
要はこれである:
d = {1: {'a': 1, 'b': 2}, 2: 22, 3: 33}
would look rather ugly, since indent would affect the appearance of dicts with depth greater than 1 as well:
1 MY_DCT = {
2 1: { 'a': 1,
3 'b': 2},
4 # ^
5 # |
6 # ugly
7 2: 22,
8 3: 33}
この例は pprint.pformat そのものの出力ではなく pformat の出力を利用した出力ね。自作の例の方がいいか。
まず「データ」を表現したいオブジェクト。何に収納したいかは色々で例えば namedtuple なんぞもいいが、今は単に object として:
1 class Target(object):
2 def __init__(self, target, debug):
3 self.Target = target
4 self.UseDebugLibrary = debug
5
6 class Project(object):
7 def __init__(self):
8 self.confs = {
9 ("Release", "x64"): {
10 "Target": Target("hogeX64", False),
11 },
12 ("Debug", "x64"): {
13 "Target": Target("hogeX64_d", True),
14 }
15 }
現実にはこれの何十倍も複雑で、深い階層を持っていると思って欲しい。当然何もしなければ:
1 >>> p = Project()
2 >>> p
3 <__main__.Project object at 0x0000000002BA7E48>
となる。で、「所詮データ収めの場所」の repr 表現が「とってもぷりちぃ」であることは不正じゃないので、これは __repr__
にぷりちぃ表現をブチ込むのはアリだ。少なくとも「アイデンティティ」はデータの中身が主張するのだ。
そういうわけで __repr__
を pformat で書きたいわけだが:
1 class _Base(object):
2 def __repr__(self):
3 from pprint import pformat
4 return pformat(self.__dict__)
5
6 class Target(_Base):
7 def __init__(self, target, debug):
8 self.Target = target
9 self.UseDebugLibrary = debug
10
11 class Project(_Base):
12 def __init__(self):
13 self.confs = {
14 ("Release", "x64"): {
15 "Target": Target("hogeX64", False),
16 },
17 ("Debug", "x64"): {
18 "Target": Target("hogeX64_d", True),
19 }
20 }
まさしく:
1 >>> p = Project()
2 >>> p
3 {'confs': {("Debug", "x64"): {'Target': {'Target': 'hogeX64_d', 'UseDebugLibrary': True}},
4 ("Release", "x64"): {'Target': {'Target': 'hogeX64', 'UseDebugLibrary': False}}}}
引用した stackoverflow のコメントが言う通りなのだが「indent」をどう制御しても、pformat は辞書を延々「折り返さずに」フォーマットする。要するに皆これが大嫌いで「python pprint alternative」という検索を試みる。
そして答えは十中八九「json.dumps を使えぃ」である。ワタシもこれまではイケるならそうしてたんだけれど、今回例にしたのがまさに「json がダメ」なパターン。
2つ問題があるのだが、一つは「プロパティ名は文字列なんだゼ」問題。要するに json の「辞書的なもの」はプロパティセットであって、「キー的なもの」は文字列しか許されない。ので、tuple をキーに使っている今回の例ではそのままでは使えない。
もう一つはどちらかといえば「ダメなので頑張ってこねくりまわそう」とする場合に起こる問題。つまり、「json が読めるような文字列化を施してから json に渡す」としたいわけだが、面倒だから、と eval を介在させようとして起こる。まぁ eval はほんとは使いたくないわけだからいいんだけどさ…。真似して欲しくはないからコードはここには書かないが、要するに「真偽値」が python は True/False、json/javascript は true/false であることが問題になる。(例のように構造がネストしている場合。)
で、Indentation of pformat() output で提案されているものでいいもんはないかなぁ、と思ったが、まぁこれ:
1 def f(obj_name, given_dct):
2 """
3 Converts given dct (body) to a pretty formatted string.
4 Resulting string used for file writing.
5
6 Args:
7 obj_name: (str) name of the dict
8 Returns:
9 (str)
10 """
11
12 string = pp.pformat(given_dct, width=1)[1:]
13
14 new_str = ''
15 for num, line in enumerate(string.split('\n')):
16 if num == 0:
17 # (pprint module always inserts one less whitespace for first line)
18 # (indent=1 is default, giving everything one extra whitespace)
19 new_str += ' '*4 + line + '\n'
20 else:
21 new_str += ' '*3 + line + '\n'
22
23 return obj_name + ' = {\n' + new_str
24
25
26 s = f(obj_name='MY_DCT', given_dct=d)
はそれほど悪くはなかった。ワタシの例だとこんな:
1 class _Base(object):
2 def __repr__(self):
3 from pprint import pformat
4 pfs = pformat(self.__dict__, width=1)[1:]
5 new_str = ''
6 for num, line in enumerate(pfs.split('\n')):
7 if num == 0:
8 # (pprint module always inserts one less whitespace for first line)
9 # (indent=1 is default, giving everything one extra whitespace)
10 new_str += ' ' * 4 + line + '\n'
11 else:
12 new_str += ' ' * 3 + line + '\n'
13 return '{\n' + new_str
14
15 class Target(_Base):
16 def __init__(self, target, debug):
17 self.Target = target
18 self.UseDebugLibrary = debug
19
20 class Project(_Base):
21 def __init__(self):
22 self.confs = {
23 ("Release", "x64"): {
24 "Target": Target("hogeX64", False),
25 },
26 ("Debug", "x64"): {
27 "Target": Target("hogeX64_d", True),
28 }
29 }
1 >>> p = Project()
2 >>> p
3 {
4 'confs': {('Debug', 'x64'): {'Target': {
5 'Target': 'hogeX64_d',
6 'UseDebugLibrary': True}
7 },
8 ('Release', 'x64'): {'Target': {
9 'Target': 'hogeX64',
10 'UseDebugLibrary': False}
11 }}}
12
13 >>>
不満がないわけではないが、オリジナルの数億ミリ倍くらいはマシだ。このままでは誤解しかねないインデントではあるので、製品品質にはちと危ないが、少なくとも「(目を凝らせば)読める、読めるぞぉ」なレベルにまでは来てる。
もう一度想像してみて欲しいが、現実の例はあと5~10階層くらいは深い。だって複雑な xml が元になってるものだからな。そうなるとオリジナルではコンソールの横幅を溢れようが気にせず3行にも4行にもなろうが折り返さないので、とても読めたもんじゃなく、「かなりプリント(pretty print)」とはよく言ったもんだ、と思う、ほんとに。それから考えりゃ、ひとまずこの回答は随分救世主だ。(なお、そうであってもこれの一番の問題は、pprint は pure python 版しかないため、場合によっては性能が危ういこと。)