Repository: numpy/numpy

Issue:
numpy.fromfile() byte offset
A major use case for the [numpy.fromfile()](https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.fromfile.html) function is in reading from binary file of some kind. Frequently, binary formats are just arrays of data with a fixed-size header. It would be useful to have an extra optional parameter for the fromfile() function which defines a byte offset for when to start reading. This parameter would default to 0, leaving it backwards-compatible with existing code.

### Reproducing code example:

<!-- A short code example that reproduces the problem/missing feature. It should be
self-contained, i.e., possible to run as-is via 'python myproblem.py' -->

```python
import numpy as np

binary_file = "sample_binary.bin"
my_array = np.fromfile(binary_file, dtype=np.uint8, offset=40)
```

Currently, the above code will not run, as the 'offset' keyword argument is not supported. In this call, the ndarray object 'my_array' would be the result of the binary file being read in, starting with an offset of 40 bytes.

It is certainly possible to [work-around](https://stackoverflow.com/questions/30124255/read-a-binary-file-using-numpy-fromfile-and-a-given-offset) the issue currently, but adding direct support for offsets in the function would make for cleaner code.


Discussion:
@danielhogg 
A simple work-around is to manually seek into the file at the required offset

```python
import numpy as np

binary_file = "sample_binary.bin"
with open(binary_file, 'rb') as f:
    f.seek(40)
    my_array = np.fromfile(f, dtype=np.uint8)
```
 Edit: having an _offset_ argument would allow for more explicit control

This seems like a good idea. I think the default should be `None`, i.e., the file pointer will not be changed (and is thus at the start when one passes in a string, and wherever a filehandle is when that is passed in).

I think it would involve relatively simple changes in `core/src/multiarray/multiarraymodule.c` - likely just after [creating a duplicate filehandle](https://github.com/numpy/numpy/blob/master/numpy/core/src/multiarray/multiarraymodule.c#L2085) - this part already is tracking where the original starting position is, and using that position in the actual reading.

One possible complication: for binary files, the offset is very obvious, but this is less true for text files. One may want to allow it only for binary files, and refer to `loadtxt` for text files.
@danielhogg 

:man_facepalming:

> It is certainly possible to [work-around](https://stackoverflow.com/questions/30124255/read-a-binary-file-using-numpy-fromfile-and-a-given-offset) the issue currently, but adding direct support for offsets in the function would make for cleaner code.

I was too tired last night and completely missed your example.

Resolving patch:
diff --git a/numpy/core/_add_newdocs.py b/numpy/core/_add_newdocs.py
--- a/numpy/core/_add_newdocs.py
+++ b/numpy/core/_add_newdocs.py
@@ -1142,7 +1142,7 @@
 
 add_newdoc('numpy.core.multiarray', 'fromfile',
     """
-    fromfile(file, dtype=float, count=-1, sep='')
+    fromfile(file, dtype=float, count=-1, sep='', offset=0)
 
     Construct an array from data in a text or binary file.
 
@@ -1167,6 +1167,11 @@
         Spaces (" ") in the separator match zero or more whitespace characters.
         A separator consisting only of spaces must match at least one
         whitespace.
+    offset : int
+        The offset (in bytes) from the file's current position. Defaults to 0.
+        Only permitted for binary files.
+
+        .. versionadded:: 1.17.0
 
     See also
     --------


Test patch:
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -4767,6 +4767,36 @@ def test_load_object_array_fromfile(self):
         assert_raises_regex(ValueError, "Cannot read into object array",
                             np.fromfile, self.filename, dtype=object)
 
+    def test_fromfile_offset(self):
+        with open(self.filename, 'wb') as f:
+            self.x.tofile(f)
+
+        with open(self.filename, 'rb') as f:
+            y = np.fromfile(f, dtype=self.dtype, offset=0)
+            assert_array_equal(y, self.x.flat)
+
+        with open(self.filename, 'rb') as f:
+            count_items = len(self.x.flat) // 8
+            offset_items = len(self.x.flat) // 4
+            offset_bytes = self.dtype.itemsize * offset_items
+            y = np.fromfile(f, dtype=self.dtype, count=count_items, offset=offset_bytes)
+            assert_array_equal(y, self.x.flat[offset_items:offset_items+count_items])
+
+            # subsequent seeks should stack
+            offset_bytes = self.dtype.itemsize
+            z = np.fromfile(f, dtype=self.dtype, offset=offset_bytes)
+            assert_array_equal(z, self.x.flat[offset_items+count_items+1:])
+        
+        with open(self.filename, 'wb') as f:
+            self.x.tofile(f, sep=",")
+
+        with open(self.filename, 'rb') as f:
+            assert_raises_regex(
+                    TypeError,
+                    "'offset' argument only permitted for binary files",
+                    np.fromfile, self.filename, dtype=self.dtype,
+                    sep=",", offset=1)
+
     def _check_from(self, s, value, **kw):
         if 'sep' not in kw:
             y = np.frombuffer(s, **kw)