Twoolie recently accepted my pull request #31, which modifies the output of str()
, repr()
, pretty_tree()
and tag_info()
of NBT Tag objects, but raised some concern:
the whole point of pretty_tree
is to do this sort of "meaningful" printing. I'd feel more comfortable if you kept the output of pretty_tree
the same. perhaps move the old logic over to str
if you are going to override repr
?
The reason to make this change was because I was confused by the following output:
>>> f = nbt.NBTFile("bigtest.nbt")
>>> print(f)
11 Entries
>>> print(repr(f))
11 Entries
It left me wondering "what type of object is f? And what are these 11 entries?"
Given the concern raised above, let me poll here what the preferred output is.
TAG objects have four methods that return a string:
__str__()
__repr__()
tag_info()
pretty_tree()
Recent Changes
Here's a quick side-by-side comparison of the changes in output. The full output of pretty_tree() is listed at the bottom of this post.
repr() before commit #31 | repr() after commit #31 |
>>> repr(mynbt)
'11 Entries'
|
>>> repr(mynbt)
"<TAG_Compound('Level') at 0x10e9a5fd0>"
|
str() before commit #31 | str() after commit #31 |
>>> print(f["byteTest"])
127
>>> print(f["intTest"])
2147483647
>>> print(f["floatTest"])
0.4982314705848694
>>> print(f["stringTest"])
HELLO WORLD ร
รร!
>>> print(f["byteArrayTest"])
[7 bytes]
>>> print(f["intArrayTest"])
[7 ints]
>>> print(f["listTest"])
2 entries of type TAG_Long
>>> print(f["nested compound test"])
3 Entries
|
>>> print(f["byteTest"])
127
>>> print(f["intTest"])
2147483647
>>> print(f["floatTest"])
0.4982314705848694
>>> print(f["stringTest"])
HELLO WORLD ร
รร!
>>> print(f["byteArrayTest"])
[0, 62, 34, 16, 8, 10, 22]
>>> print(f["intArrayTest"])
[0, 62, 34, 16, 8, 10, 22]
>>> print(f["listTest"])
[TAG_Long: 11, TAG_Long: 12]
>>> print(f["nested compound test"])
{TAG_Compound('ham'): {2 Entries},
TAG_String('name'): Compound tag #1,
TAG_Long('created-on'): 1264099775885}
|
tag_info() before commit #31 | tag_info() after commit #31 |
TAG_Byte("byteTest"): 127
TAG_Short("shortTest"): 32767
TAG_Int("intTest"): 2147483647
TAG_Long("longTest"): 9223372036854775807
TAG_Float("floatTest"): 0.4982314705848694
TAG_Double("doubleTest"): 0.4931287132182315
TAG_String("stringTest"): HELLO WORLD ร
รร!
TAG_Byte_Array("byteArrayTest"): [1000 bytes]
TAG_Int_Array("intArrayTest"): [256 ints]
TAG_List("listTest"): 5 entries of type TAG_Long
TAG_Compound("nested compound test"): 2 Entries
|
TAG_Byte("byteTest"): 127
TAG_Short("shortTest"): 32767
TAG_Int("intTest"): 2147483647
TAG_Long("longTest"): 9223372036854775807
TAG_Float("floatTest"): 0.4982314705848694
TAG_Double("doubleTest"): 0.4931287132182315
TAG_String("stringTest"): HELLO WORLD ร
รร!
TAG_Byte_Array("byteArrayTest"): [1000 byte(s)]
TAG_Int_Array("intArrayTest"): [256 int(s)]
TAG_List("listTest"): [5 TAG_Long(s)]
TAG_Compound("nested compound test"): {2 Entries}
|
pretty_tree() before commit #31 | pretty_tree() after commit #31 |
TAG_Compound("Level"): 3 Entries
{
TAG_Byte_Array("Biomes"): [256 bytes]
TAG_Int_Array("HeightMap"): [60 ints][66,
66, 65, 65, 65, 67, 67, 67, 67, 67, 67,
67, 67, 67, 67, 67, 67, 104, 67, 67, 65,
65, 67, 67, 67, 67, 67, 67, 67, 67, 67,
67, 67, 104, 104, 67, 67, 67, 67, 67, 67,
67, 67, 67, 67, 67, 67, 67, 67, 67, 67,
67, 67, 67, 67, 67, 67, 67, 67, 67]
TAG_List("Pos"): 3 entries of type TAG_Double
{
TAG_Double: -142.961140196
TAG_Double: 67.0
TAG_Double: 85.4952413138
}
}
|
TAG_Compound("Level"): 3 Entries
{
TAG_Byte_Array("Biomes"): [256 byte(s)]
TAG_Int_Array("HeightMap"): [60 int(s)]
TAG_List("Pos"): [3 TAG_Double(s)]
{
TAG_Double: -142.961140196
TAG_Double: 67.0
TAG_Double: 85.4952413138
}
}
|
Observe that the output of tag_info()
and pretty_tree()
is only slightly changed.
TAG_Int_Array.pretty_tree()
now mimics TAG_Byte_Array.pretty_tree()
- Naming is more consistent. Everything is <number> <type>(s).
- brackets are more consistent. Previously only the values of arrays got square brackets. List and compound values now also have brackets.
The most important changes are to str()
and repr()
.
The Python manual has the following requirements for the str() and repr() functions:
repr: should return a string that is acceptable to eval(). If this is not possible, a string enclosed in angle brackets that contains the name of the type of the object the address of the object.
str: return an โinformalโ string representation of an object. The return value must be a string object (apparently a byte string for Python 2 and a Unicode string for Python 3)
Questions
- What should be the output of
__repr__()
?
a. 11 Entries
(previous result; goes against Python guidelines on __repr__
)
b. <TAG_Compound('Level') at 0x10e9a5fd0>
(current solution)
c. Recursive nesting of initialisation string (Python recommended): TAG_Compound(name='Level', value=[ TAG_Long(name='longTest', value=9223372036854775807), TAG_Short(name='shortTest', value=32767), TAG_String(name='stringTest', value=u'HELLO WORLD THIS IS A TEST STRING ร
รร!'), TAG_Float(name='floatTest', value=0.4982314705848694), TAG_Int(name='intTest', value=2147483647), TAG_Compound(name='nested compound test', value=[ TAG_Compound(name='ham', value=[ TAG_String(name='name', value='Hampus'), TAG_Float(name='value', value=0.75) ]) TAG_Compound(name='egg',value=[ TAG_String(name='name', value='Eggbert'), TAG_Float(name='value', value=0.5) ]) ]) TAG_List(name='listTest (long)', value=[ TAG_Long(value=11), TAG_Long(value=12), TAG_Long(value=13), TAG_Long(value=14), TAG_Long(value=15) ]) TAG_List(name='listTest (compound)', value=[ TAG_Compound(value=[ TAG_String(name='name', value='Compound tag #0'), TAG_Long(name='created-on', value=1264099775885) ]) TAG_Compound(value=[ TAG_String(name='name', value='Compound tag #1'), TAG_Long(name='created-on', value=1264099775885) ]) ]) TAG_Byte('byteTest', value=127), TAG_Byte_Array(name='byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))', value=[0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48, 0, 62, 34, 16, 8, 10, 22, 44, 76, 18, 70, 32, 4, 86, 78, 80, 92, 14, 46, 88, 40, 2, 74, 56, 48, 50, 62, 84, 16, 58, 10, 72, 44, 26, 18, 20, 32, 54, 86, 28, 80, 42, 14, 96, 88, 90, 2, 24, 56, 98, 50, 12, 84, 66, 58, 60, 72, 94, 26, 68, 20, 82, 54, 36, 28, 30, 42, 64, 96, 38, 90, 52, 24, 6, 98, 0, 12, 34, 66, 8, 60, 22, 94, 76, 68, 70, 82, 4, 36, 78, 30, 92, 64, 46, 38, 40, 52, 74, 6, 48]), TAG_Double(name='doubleTest', value=0.4931287132182315) ]) ])
- What should be the output of str() for collection TAGs?
For single value entities, like TAG_Numeric and TAG_String, I think it should
just return str(self.value). For collection TAGs, it can be either one of:
a. 11 Entries
(previous result)
b. TAG_Compound("Level"): 11 Entries
(same as tag_info)
c. non-recursive nesting of all entries (current solution): {TAG_Long('longTest'): 9223372036854775807, TAG_Short('shortTest'): 32767, TAG_String('stringTest'): HELLO WORLD THIS IS A TEST STRING ร
รร!, TAG_Float('floatTest'): 0.4982314705848694, TAG_Int('intTest'): 2147483647, TAG_Compound('nested compound test'): {2 Entries}, TAG_List('listTest (long)'): [5 TAG_Long(s)], TAG_List('listTest (compound)'): [2 TAG_Compound(s)], TAG_Byte('byteTest'): 127, TAG_Byte_Array('byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))'): [1000 byte(s)], TAG_Double('doubleTest'): 0.4931287132182315}
d. As c, but with infinite nesting. Kind like the solution 1c bove.
3. What should be the output of collection values after tag_info() (and thus pretty_tree())?
I suspect we all agree on TAG_Long('created-on'): 1264099775885
for TAG_Long. However
for collection values in TAG_List, TAG_Compound, TAG_Byte_Array and TAG_Int_Array it is less
obvious.
a. without [] and {}, inconsistent names (previous solution, exactly as in original NBT.txt specification)
TAG_Byte_Array('byteArrayTest'): [1000 bytes] TAG_List("listTest (long)"): 5 entries of type TAG_Long TAG_Compound("Level"): 11 Entries
b. with [] and {}, consistent names (current solution)
TAG_Byte_Array('byteArrayTest'): [1000 byte(s)] TAG_List('listTest (long)'): [5 TAG_Long(s)] TAG_Compound('Level'): {11 Entries}
c. without [] and {}, consistent names
TAG_Byte_Array('byteArrayTest'): 1000 byte(s) TAG_List('listTest (long)'): 5 TAG_Long(s) TAG_Compound('Level'): 11 Entries
d. with [] and {}, inconsistent names
TAG_Byte_Array('byteArrayTest'): [1000 bytes] TAG_List("listTest (long)"): [5 entries of type TAG_Long] TAG_Compound("Level"): {11 Entries}
4. What should be the output of string values in tag_info() (and thus pretty_tree())?
a. TAG_String("stringTest"): HELLO WORLD THIS IS A TEST STRING ร
รร!
(current implementation)
b. TAG_String("stringTest"): u'HELLO WORLD THIS IS A TEST STRING ร
รร!'
(more clear what it is)
5. What should be the repr()
string for a NBTFile object?
NBTFile is a subclass of a TAG_Compound, and instances are presented as if they where
TAG_Compounds. This may go against Python guidelines for __repr__()
.
(I personally don't mind the current solution, but a change is fine to, since I probably
use str()
instead of repr()
, and str()
will continue to behave as TAG_Compound.)
a. <TAG_Compound('Level') at 0x10e9a5fd0>
(current solution)
b. <NBTFile('tests/bigtest.nbt') at 0x10e9a5fd0>
c. NBTFile('tests/bigtest.nbt')
6. How should str() deal with non-ascii characters in Python 2?
str(nbt.NBTFile("tests/bigtest.nbt"))
may yield a UnicodeEncodeError, if a TAG_String contains non-ascii characters, such as in the example. Python 3 handles this gracefully, but Python 2 does not. This mimics exisiting behaviour. In Python 2, str(u'ยฟwhรฅt?')
also raises a UnicodeEncodeError.
a. return str(self.value)
(current solution, mimics Python behaviour, but may raise UnicodeEncodeError)
b. return unicode(self.value)
(Python 3 solution, but may not be what users expect from str() in Python 2)
c. return self.value.encode('utf-8')
(makes assumptions about encoding, which may be incorrect)
d. return self.value.encode(encoding)
with encoding
based on sys.stdout.encoding
, locale.getpreferredencoding()
, sys.getdefaultencoding()
or some other magic (mimics print function)