<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def merge( fname1, fname2, dfilter1=None, dfilter2=None, has_header1=True, has_header2=True, frow1=0, frow2=0, ofname=None, ocols=None, ):
 r""" Merge two comma-separated values files. Data columns from the second file are appended after data columns from the first file. Empty values in columns are used if the files have different number of rows :param fname1: Name of the first comma-separated values file, the file whose columns appear first in the output file :type fname1: FileNameExists_ :param fname2: Name of the second comma-separated values file, the file whose columns appear last in the output file :type fname2: FileNameExists_ :param dfilter1: Row and/or column filter for the first file. If None no data filtering is done on the file :type dfilter1: :ref:`CsvDataFilter` or None :param dfilter2: Row and/or column filter for the second file. If None no data filtering is done on the file :type dfilter2: :ref:`CsvDataFilter` or None :param has_header1: Flag that indicates whether the first comma-separated values file has column headers in its first line (True) or not (False) :type has_header1: boolean :param has_header2: Flag that indicates whether the second comma-separated values file has column headers in its first line (True) or not (False) :type has_header2: boolean :param frow1: First comma-separated values file first data row (starting from 1). If 0 the row where data starts is auto-detected as the first row that has a number (integer of float) in at least one of its columns :type frow1: NonNegativeInteger_ :param frow2: Second comma-separated values file first data row (starting from 1). If 0 the row where data starts is auto-detected as the first row that has a number (integer of float) in at least one of its columns :type frow2: NonNegativeInteger_ :param ofname: Name of the output comma-separated values file, the file that will contain the data from the first and second files. If None the first file is replaced "in place" :type ofname: FileName_ or None :param ocols: Column names of the output comma-separated values file. If None the column names in the first and second files are used if **has_header1** and/or **has_header2** are True. The column labels :code:`'Column [column_number]'` are used when one of the two files does not have a header, where :code:`[column_number]` is an integer representing the column number (column 0 is the leftmost column). No header is used if **has_header1** and **has_header2** are False :type ocols: list or None .. [[[cog cog.out(exobj.get_sphinx_autodoc(raised=True)) ]]] .. Auto-generated exceptions documentation for pcsv.merge.merge :raises: * OSError (File *[fname]* could not be found) * RuntimeError (Argument \`dfilter1\` is not valid) * RuntimeError (Argument \`dfilter2\` is not valid) * RuntimeError (Argument \`fname1\` is not valid) * RuntimeError (Argument \`fname2\` is not valid) * RuntimeError (Argument \`frow1\` is not valid) * RuntimeError (Argument \`frow2\` is not valid) * RuntimeError (Argument \`ocols\` is not valid) * RuntimeError (Argument \`ofname\` is not valid) * RuntimeError (Column headers are not unique in file *[fname]*) * RuntimeError (Combined columns in data files and output columns are different) * RuntimeError (File *[fname]* has no valid data) * RuntimeError (File *[fname]* is empty) * RuntimeError (Invalid column specification) * ValueError (Column *[column_identifier]* not found) .. [[[end]]] """

    # pylint: disable=R0913,R0914
    iomm_ex = pexdoc.exh.addex(
        RuntimeError, "Combined columns in data files and output columns are different"
    )
    # Read and validate file 1
    obj1 = CsvFile(fname=fname1, dfilter=dfilter1, has_header=has_header1, frow=frow1)
    # Read and validate file 2
    obj2 = CsvFile(fname=fname2, dfilter=dfilter2, has_header=has_header2, frow=frow2)
    # Assign output data structure
    ofname = fname1 if ofname is None else ofname
    cfilter1 = obj1.header() if obj1.cfilter is None else obj1.cfilter
    cfilter2 = obj2.header() if obj1.cfilter is None else obj2.cfilter
    # Create new header
    cols1 = len(cfilter1)
    cols2 = len(cfilter2)
    if (ocols is None) and has_header1 and has_header2:
        ocols = [cfilter1 + cfilter2]
    elif (ocols is None) and has_header1 and (not has_header2):
        ocols = [
            cfilter1
            + [
                "Column {0}".format(item)
                for item in range(cols1 + 1, cols1 + cols2 + 1)
            ]
        ]
    elif (ocols is None) and (not has_header1) and has_header2:
        ocols = [["Column {0}".format(item) for item in range(1, cols1 + 1)] + cfilter2]
    elif ocols is None:
        ocols = []
    else:
        iomm_ex(cols1 + cols2 != len(ocols))
        ocols = [ocols]
    # Even out rows
    delta = obj1.rows(filtered=True) - obj2.rows(filtered=True)
    data1 = obj1.data(filtered=True)
    data2 = obj2.data(filtered=True)
    if delta > 0:
        row = [cols2 * [None]]
        data2 += delta * row
    elif delta < 0:
        row = [cols1 * [None]]
        data1 += abs(delta) * row
    data = ocols
    for item1, item2 in zip(data1, data2):
        data.append(item1 + item2)
    write(fname=ofname, data=data, append=False)