//  [python]
def read_prop(fname, dims, typ=numpy.float64, prop=None, *, mask=None):
    """\
    Read a property from a text file into a dictionary. This is akin to
    Petrel's Import onto selection popup menu choice.

    :param fname:   File name to load property from. Currently, this must be
                    a file in the filesystem and cannot be a file-like object.
    :type  fname:   str
    :param dims:    Dimensions of the data cube, in a format compatible with
                    the return of the shape function, i.e. (nk, nj, ni)
    :type  dims:    (int, int, int)
    :param typ:     Data type of the data to be loaded
    :type  typ:     :class:`numpy.dtype`
    :param prop:    Dictionary where the keyword will be added. If this is
                    None, then a new dictionary will be created.
    :type  prop:    dict
    :param mask:    Existing mask for inactive elements; this will be
                    combined with the mask inherit in the data, if specified.
                    If you have loaded the grid, this should be the ACTNUM
                    field
    :type  mask:    Optional[:class:`numpy.ndarray`]
    :return:        Dictionary where the property being read is added as a key
    :rtype:         dict
    """
    # if output isn't given, then add to brand new dictionary
    if prop is None:
        prop = dict()

    # allocate memory
    num_cells = numpy.prod(dims)
    data = numpy.empty((num_cells,), dtype=typ)

    # read raw array of values into memory first
    with ctx.closing(_OwningLexer(os.path.expanduser(fname))) as lex:

        # read the first keyword that appears in the file
        kw = dec(lex.expect(keyword))
        if not lex.at_eof():
            lex.expect(newline)

        # read numbers from file; each read may potentially fill a different
        # length of the array. use an almost identical type of loop for
        # integers and floating-point, but avoid using test inside the loop
        # or polymorphism through function pointer, to ease optimization
        num_read = 0
        if numpy.issubdtype(typ, numpy.integer):
            while num_read < num_cells:
                count, value = lex.expect_multi_card()
                data[num_read:(num_read+count)] = value
                num_read += count
        else:
            while num_read < num_cells:
                count, value = lex.expect_numbers()
                data[num_read:(num_read+count)] = value
                num_read += count

        # verify that we got exactly the number of corners we wanted
        if num_read > num_cells:
            raise GrdEclError(lex.path, lex.pos(),
                              "Expected {0:d} numbers, but got {1:d}".format(
                                  num_cells, num_read))

        # after all the data, there should be a sentinel slash
        lex.expect(endrec)

    # reformat the data into a natural kji/bfr hypercube
    data = numpy.reshape(data, dims)

    # inherit inactive cells by missing values in the file
    blanked = numpy.isnan(data)

    # if there is either specified a mask, or there is an inherit mask, then
    # we need to generate a masked array
    if (mask is not None) or numpy.any(blanked):
        # combine the two masks into one:
        if mask is not None:
            blanked = numpy.logical_or(blanked, mask)

        # update the values to not have anything where there is now a mask
        if numpy.issubdtype(data.dtype, numpy.integer):
            data[blanked] = 0
        else:
            data[blanked] = numpy.nan

        prop[kw] = numpy.ma.array(data=data, dtype=data.dtype,
                                  mask=blanked)

    # no mask at all, seems to be a full matrix
    else:
        prop[kw] = data

    return prop