'Asynchronously read a number of bytes.
If a ``streaming_callback`` is given, it will be called with chunks
of data as they become available, and the final result will be empty.
Otherwise, the result is all the data that was read.
If a callback is given, it will be run with the data as an argument;
if not, this method returns a `.Future`.
If ``partial`` is true, the callback is run as soon as we have
any bytes to return (but never more than ``num_bytes``)
.. versionchanged:: 4.0
Added the ``partial`` argument.  The callback argument is now
optional and a `.Future` will be returned if it is omitted.'

def read_bytes(self, num_bytes, callback=None, streaming_callback=None, partial=False):

    future = self._set_read_callback(callback)
    assert isinstance(num_bytes, numbers.Integral)
    self._read_bytes = num_bytes
    self._read_partial = partial
    self._streaming_callback = stack_context.wrap(streaming_callback)
    try:
        self._try_inline_read()
    except:
        if (future is not None):
            future.add_done_callback((lambda f: f.exception()))
        raise
    return future
