<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def memory_read(self, addr, num_units, zone=None, nbits=None):
 """Reads memory from a target system or specific memory zone. The optional ``zone`` specifies a memory zone to access to read from, e.g. ``IDATA``, ``DDATA``, or ``CODE``. The given number of bits, if provided, must be either ``8``, ``16``, or ``32``. If not provided, always reads ``num_units`` bytes. Args: self (JLink):
 the ``JLink`` instance addr (int):
 start address to read from num_units (int):
 number of units to read zone (str):
 optional memory zone name to access nbits (int):
 number of bits to use for each unit Returns: List of units read from the target system. Raises: JLinkException: if memory could not be read. ValueError: if ``nbits`` is not ``None``, and not in ``8``, ``16``, or ``32``. """

        buf_size = num_units
        buf = None
        access = 0

        if nbits is None:
            buf = (ctypes.c_uint8 * buf_size)()
            access = 0
        elif nbits == 8:
            buf = (ctypes.c_uint8 * buf_size)()
            access = 1
        elif nbits == 16:
            buf = (ctypes.c_uint16 * buf_size)()
            access = 2
            buf_size = buf_size * access
        elif nbits == 32:
            buf = (ctypes.c_uint32 * buf_size)()
            access = 4
            buf_size = buf_size * access
        else:
            raise ValueError('Given bit size is invalid: %s' % nbits)

        args = [addr, buf_size, buf, access]

        method = self._dll.JLINKARM_ReadMemEx
        if zone is not None:
            method = self._dll.JLINKARM_ReadMemZonedEx
            args.append(zone.encode())

        units_read = method(*args)
        if units_read < 0:
            raise errors.JLinkReadException(units_read)

        return buf[:units_read]