Repository: scikit-hep/awkward

Issue:
Type of strings and bytes inconsistent iterating manually or in .tolist()
```python
check = ak.Array([{"x": "b"}, {"x": "b"}, {"x": "a"}, {"x": "b"}])
print(check.layout)

print(ak.tolist(check))
print(ak.tolist(check.layout))
```

results in:
```
<RecordArray>
    <type>{"x": string}</type>
    <field index="0" key="x">
        <ListOffsetArray64>
            <type>string</type>
            <offsets><Index64 i="[0 1 2 3 4]" offset="0" at="0x7fb342145600"/></offsets>
            <content><NumpyArray format="B" shape="4" data="0x 62626162" at="0x7fb342034800">
                <type>utf8</type>
            </NumpyArray></content>
        </ListOffsetArray64>
    </field>
</RecordArray>
[{'x': [98]}, {'x': [98]}, {'x': [97]}, {'x': [98]}]
[{'x': [98]}, {'x': [98]}, {'x': [97]}, {'x': [98]}]
```

```python
check = ak.Array([{"x": b"b"}, {"x": b"b"}, {"x": b"a"}, {"x": b"b"}])
print(check.layout)

print(ak.tolist(check))
print(ak.tolist(check.layout))
```

results in:
```
<RecordArray>
    <type>{"x": bytes}</type>
    <field index="0" key="x">
        <ListOffsetArray64>
            <type>bytes</type>
            <offsets><Index64 i="[0 1 2 3 4]" offset="0" at="0x7fb3431d6e00"/></offsets>
            <content><NumpyArray format="B" shape="4" data="0x 62626162" at="0x7fb34314e200">
                <type>byte</type>
            </NumpyArray></content>
        </ListOffsetArray64>
    </field>
</RecordArray>
[{'x': [98]}, {'x': [98]}, {'x': [97]}, {'x': [98]}]
[{'x': [98]}, {'x': [98]}, {'x': [97]}, {'x': [98]}]
```

However:
```python
for out in check:
    print(out["x"])
```

results in (similar for strings):
```
b'b'
b'b'
b'a'
b'b'
```

but:
```python
for out in check.layout:
    print(out["x"])
```

results in
```
<NumpyArray format="B" shape="1" data="0x 62" at="0x7fb3439c8200">
    <type>byte</type>
</NumpyArray>
<NumpyArray format="B" shape="1" data="0x 62" at="0x7fb3439c8200">
    <type>byte</type>
</NumpyArray>
<NumpyArray format="B" shape="1" data="0x 61" at="0x7fb3439c8200">
    <type>byte</type>
</NumpyArray>
<NumpyArray format="B" shape="1" data="0x 62" at="0x7fb3439c8200">
    <type>byte</type>
</NumpyArray>
```

It looks like the underlying behavior is correct but the handling is inconsistent?


Discussion:
The shortest reproducer is:
```python
check = ak.Array([{"x": b"b"}, {"x": b"b"}, {"x": b"a"}, {"x": b"b"}])
print(check)
print(ak.tolist(check))
print(ak.tolist(ak.fromiter(ak.tolist(check))))
```

which gives:
```
[{x: b'b'}, {x: b'b'}, {x: b'a'}, {x: b'b'}]
[{'x': [98]}, {'x': [98]}, {'x': [97]}, {'x': [98]}]
[{'x': [98]}, {'x': [98]}, {'x': [97]}, {'x': [98]}]
```

Resolving patch:
diff --git a/awkward1/highlevel.py b/awkward1/highlevel.py
--- a/awkward1/highlevel.py
+++ b/awkward1/highlevel.py
@@ -22,9 +22,7 @@ def __init__(self, data, type=None, namespace=None):
 
         self.namespace = namespace
 
-        if type is None:
-            self._type = None
-        else:
+        if type is not None:
             if not isinstance(type, awkward1.layout.ArrayType):
                 raise TypeError("type must be an awkward1.layout.ArrayType")
             if type.length > len(layout):
@@ -37,7 +35,6 @@ def __init__(self, data, type=None, namespace=None):
                     raise TypeError("type.parameters['__class__'] = {0} must be a subclass of awkward1.Array".format(repr(type.parameters["__class__"])))
                 self.__class__ = cls
             layout = layout.astype(type.type)
-            self._type = type
 
         self.layout = layout
 
@@ -64,13 +61,10 @@ def namespace(self, namespace):
 
     @property
     def type(self):
-        if self._type is None:
-            return awkward1.layout.ArrayType(self._layout.type, len(self._layout))
-        else:
-            return self._type
+        return awkward1.layout.ArrayType(self._layout.type, len(self._layout))
 
     def __len__(self):
-        return len(self.layout)
+        return len(self._layout)
 
     def __iter__(self):
         for x in self.layout:
@@ -172,7 +166,8 @@ def namespace(self, namespace):
 
     @property
     def type(self):
-        return awkward1.layout.ArrayType(self._fillablearray.type, len(self._fillablearray))
+        tmp = self._fillablearray.snapshot()
+        return awkward1.layout.ArrayType(tmp.type, len(tmp))
 
     def __len__(self):
         return len(self._fillablearray)
diff --git a/awkward1/operations/convert.py b/awkward1/operations/convert.py
--- a/awkward1/operations/convert.py
+++ b/awkward1/operations/convert.py
@@ -42,11 +42,17 @@ def tolist(array):
         return array
 
     elif isinstance(array, awkward1.behavior.string.CharBehavior):
-        if array.layout.type.parameters.get("encoding") is None:
+        if array.layout.parameters.get("encoding") is None:
             return array.__bytes__()
         else:
             return array.__str__()
 
+    elif awkward1.operations.describe.parameters(array).get("__class__") == "char":
+        if awkward1.operations.describe.parameters(array).get("encoding") is None:
+            return awkward1.behavior.string.CharBehavior(array).__bytes__()
+        else:
+            return awkward1.behavior.string.CharBehavior(array).__str__()
+
     elif isinstance(array, awkward1.highlevel.Array):
         return [tolist(x) for x in array]
 
diff --git a/awkward1/operations/describe.py b/awkward1/operations/describe.py
--- a/awkward1/operations/describe.py
+++ b/awkward1/operations/describe.py
@@ -6,6 +6,22 @@
 
 import awkward1.layout
 
+def parameters(array):
+    if isinstance(array, (awkward1.highlevel.Array, awkward1.highlevel.Record)):
+        return array.layout.parameters
+
+    elif isinstance(array, (awkward1.layout.Content, awkward1.layout.Record)):
+        return array.parameters
+
+    elif isinstance(array, awkward1.highlevel.FillableArray):
+        return array.snapshot().layout.parameters
+
+    elif isinstance(array, awkward1.layout.FillableArray):
+        return array.snapshot().parameters
+
+    else:
+        return {}
+
 def typeof(array):
     if array is None:
         return awkward1.layout.UnknownType()


Test patch:
diff --git a/tests/test_issue043.py b/tests/test_issue043.py
new file mode 100644
--- /dev/null
+++ b/tests/test_issue043.py
@@ -0,0 +1,15 @@
+# BSD 3-Clause License; see https://github.com/jpivarski/awkward-1.0/blob/master/LICENSE
+
+import sys
+
+import pytest
+import numpy
+
+import awkward1
+
+def test():
+    array = awkward1.Array([{"x": "one"}, {"x": "two"}, {"x": "three"}])
+    assert awkward1.tolist(array) == [{"x": "one"}, {"x": "two"}, {"x": "three"}]
+    assert awkward1.tolist(awkward1.fromiter(awkward1.tolist(array))) == [{"x": "one"}, {"x": "two"}, {"x": "three"}]
+    assert awkward1.tolist(array.layout) == [{"x": "one"}, {"x": "two"}, {"x": "three"}]
+    assert awkward1.tolist(awkward1.fromiter(awkward1.tolist(array.layout))) == [{"x": "one"}, {"x": "two"}, {"x": "three"}]