Как я могу ввести указанные c аргументы в argparse? - PullRequest
0 голосов
/ 22 января 2020

Я нашел этот код в Интернете, который может анализировать текстовый файл определенного типа c, как показано ниже:

# RELION; version 3.0

data_images

loop_ 
_rlnCoordinateX #1 
_rlnCoordinateY #2 
_rlnHelicalTubeID #3 
_rlnAngleTiltPrior #4 
_rlnAnglePsiPrior #5 
_rlnHelicalTrackLength #6 
_rlnAnglePsiFlipRatio #7 
_rlnImageName #8 
_rlnMicrographName #9 
_rlnMagnification #10 
_rlnDetectorPixelSize #11 
_rlnCtfMaxResolution #12 
_rlnCtfFigureOfMerit #13 
_rlnVoltage #14 
_rlnDefocusU #15 
_rlnDefocusV #16 
_rlnDefocusAngle #17 
_rlnSphericalAberration #18 
_rlnCtfBfactor #19 
_rlnCtfScalefactor #20 
_rlnPhaseShift #21 
_rlnAmplitudeContrast #22 
_rlnOriginX #23 
_rlnOriginY #24 
 3041.398896  3692.419723            1    90.000000    63.534898     0.000000     0.500000 000001@Extract/job011/Movies/Microtubules_02563.mrcs MotionCorr/job003/Movies/Microtubules_02563.mrc 10000.000000     5.480000     5.830000     0.124704   300.000000  7457.819824  6964.129883    33.520000     2.700000     0.000000     1.000000     0.000000     0.100000     0.031176 2.475269e-32 
 3068.235643  3638.511334            1    90.000000    63.534898    60.218978     0.500000 000002@Extract/job011/Movies/Microtubules_02563.mrcs MotionCorr/job003/Movies/Microtubules_02563.mrc 10000.000000     5.480000     5.830000     0.124704   300.000000  7457.819824  6964.129883    33.520000     2.700000     0.000000     1.000000     0.000000     0.100000     0.000000     0.000000 
 3095.072390  3584.602946            1    90.000000    63.534898   120.437956     0.500000 000003@Extract/job011/Movies/Microtubules_02563.mrcs MotionCorr/job003/Movies/Microtubules_02563.mrc 10000.000000     5.480000     5.830000     0.124704   300.000000  7457.819824  6964.129883    33.520000     2.700000     0.000000     1.000000     0.000000     0.100000     0.000000     0.000000 
 3121.909136  3530.694558            1    90.000000    63.534898   180.656934     0.500000 000004@Extract/job011/Movies/Microtubules_02563.mrcs MotionCorr/job003/Movies/Microtubules_02563.mrc 10000.000000     5.480000     5.830000     0.124704   300.000000  7457.819824  6964.129883    33.520000     2.700000     0.000000     1.000000     0.000000     0.100000     0.000000     0.000000 

Код (два класса и несколько строк в конце для их вызова) :

import os
import sys
import argparse
from collections import OrderedDict, namedtuple


class Column:
    def __init__(self, name, type=None):
        self._name = name
        # Get the type from the LABELS dict, assume str by default
        self._type = type

    def __str__(self):
        return self._name

    def __cmp__(self, other):
        return self._name == str(other)


class Table:
    """
    Class to hold and manipulate tabular data for EM processing programs.
    """
    def __init__(self, **kwargs):
        self.clear()

        if 'fileName' in kwargs:
            if 'columns' in kwargs:
                raise Exception("Please provide either 'columns' or 'fileName',"
                                " but not both.")
            fileName = kwargs.get('fileName')
            tableName = kwargs.get('tableName', None)
            self.read(fileName, tableName)
        elif 'columns' in kwargs:
            self._createColums(kwargs['columns'])

    def clear(self):
        self.Row = None
        self._columns = OrderedDict()
        self._rows = []

    def clearRows(self):
        """ Remove all the rows from the table, but keep its columns. """
        self._rows = []

    def addRow(self, *args, **kwargs):
        self._rows.append(self.Row(*args, **kwargs))

    def readStar(self, inputFile, tableName=None):
        """
        :param inputFile: Provide the input file from where to read the data.
            The file pointer will be moved until the last data line of the
            requested table.
        :return:
        """
        self.clear()
        dataStr = 'data_%s' % (tableName or '')

        self._findDataLine(inputFile, dataStr)

        # Find first column line and parse all columns
        line, foundLoop = self._findLabelLine(inputFile)
        colNames = []
        values = []

        while line.startswith('_'):
            parts = line.split()
            colNames.append(parts[0][1:])
            if not foundLoop:
                values.append(parts[1])
            line = inputFile.readline().strip()

        self._createColums(colNames)

        if not foundLoop:
            self.addRow(*values)
        else:
            # Parse all data lines
            while line:
                self.addRow(*line.split())
                line = inputFile.readline().strip()

    def read(self, fileName, tableName=None):
        with open(fileName) as f:
            self.readStar(f, tableName)

    def writeStar(self, outputFile, tableName=None, singleRow=False):
        """
        Write a Table in Star format to the given file.
        :param outputFile: File handler that should be already opened and
            in the position to write.
        :param tableName: The name of the table to write.
        :param singleRow: If True, don't write loop_, just label - value pairs.
        """
        outputFile.write("\ndata_%s\n\n" % (tableName or ''))

        if self.size() == 0:
            return

        if singleRow:
            m = max([len(c) for c in self._columns.keys()]) + 5
            lineFormat = "_{:<%d} {:>10}\n" % m
            row = self._rows[0]
            for col, value in row._asdict().iteritems():
                outputFile.write(lineFormat.format(col, value))
            outputFile.write('\n\n')
            return

        outputFile.write("loop_\n")

        # Write column names
        for col in self._columns.values():
            outputFile.write("_%s \n" % col)

        # Take a hint for the columns width from the first row

        widths = [len(str(v)) for v in self._rows[0]]
        # Check middle and last row, just in case ;)
        for index in [len(self)//2, -1]:
            for i, v in enumerate(self._rows[index]):
                w = len(str(v))
                if w > widths[i]:
                    widths[i] = w

        lineFormat = " ".join("{:>%d} " % (w + 1) for w in widths)

        # Write data rows
        for row in self._rows:
            outputFile.write(lineFormat.format(*row))
            outputFile.write('\n')

        outputFile.write('\n')

    def write(self, output_star, tableName=None):
        with open(output_star, 'w') as output_file:
            self.writeStar(output_file, tableName)

    def printStar(self, tableName=None):
        self.writeStar(sys.stdout, tableName)

    def size(self):
        return len(self._rows)

    def getColumns(self):
        return self._columns.values()

    def getColumnValues(self, colName):
        """
        Return the values of a given column
        :param colName: The name of an existing column to retrieve values.
        :return: A list with all values of that column.
        """
        if colName not in self._columns:
            raise Exception("Not existing column: %s" % colName)
        return [getattr(row, colName) for row in self._rows]

    def __len__(self):
        return self.size()

    def __iter__(self):
        for item in self._rows:
            yield item

    def __getitem__(self, item):
        return self._rows[item]

    def __setitem__(self, key, value):
        self._rows[key] = value

    # --------- Internal implementation methods ------------------------

    def _addColumn(self, nameOrTuple):
        """
        :param nameOrTuple: This parameter should be either a string or
            a tuple (string, type).
        """
        if isinstance(nameOrTuple, str):
            col = Column(nameOrTuple)
        elif isinstance(nameOrTuple, tuple):
            col = Column(nameOrTuple[0], nameOrTuple[1])
        else:
            raise Exception("Invalid input as column, "
                            "should be either string or tuple.")
        self._columns[str(col)] = col

    def _createColums(self, columnList):
        self.clear()
        for col in columnList:
            self._addColumn(col)
        self._createRowClass()

    def _createRowClass(self):
        self.Row = namedtuple('Row', [str(c) for c in self._columns])

    def _findDataLine(self, inputFile, dataStr):
        """ Raise an exception if the desired data string is not found.
        Move the line pointer after the desired line if found.
        """
        line = inputFile.readline()
        while line:
            if line.startswith(dataStr):
                return line
            line = inputFile.readline()

        raise Exception("%s block was not found")

    def _findLabelLine(self, inputFile):
        line = ''
        foundLoop = False

        l = inputFile.readline()
        while l:
            if l.startswith('_'):
                line = l
                break
            elif l.startswith('loop_'):
                foundLoop = True
            l = inputFile.readline()

        return line.strip(), foundLoop




if __name__ == '__main__':

    parser = argparse.ArgumentParser(
        description="Script to manipulate metadata files.")

    add = parser.add_argument  # shortcut
    add("input", help="Input metadata filename. ", nargs='?', default="")
    add("output",
        help="Output metadata filename, if no provided, print to stdout. ",
        nargs='?', default="")

    add("-l", "--limit", type=int, default=0,
        help="Limit the number of rows processed, useful for testing. ")

    #add("-v", "--verbosity", action="count", default=0)

    args = parser.parse_args()

    if '@' in args.input:
        tableName, fileName = args.input.split('@')
    else:
        tableName, fileName = None, args.input

    if not os.path.exists(fileName):
        raise Exception("Input file '%s' does not exists. " % fileName)

    tableIn = Table(fileName=fileName, tableName=tableName)




    # Create another table with same columns
    tableOut = Table(columns=[str(c) for c in tableIn.getColumns()])

    limit = args.limit

    for i, row in enumerate(tableIn):
        if limit > 0 and i == limit:
            break

        tableOut.addRow(*row)

    if args.output:
        tableOut.write(args.output, tableName)
    else:
        tableOut.printStar(tableName)

Проблема и несколько вопросов: я хочу использовать этот код, чтобы изменить значение определенного столбца и переписать вышеуказанный текстовый файл (Ps, это тривиальный скрипт python, но я хочу выполнить sh эту задачу, используя приведенный выше объектно-ориентированный код).

У меня также есть несколько вопросов по поводу этого кода. Единственным аргументом, который я предоставляю этому сценарию, является текстовый файл, но я вижу эту строку здесь, если args.output: tableOut.write (args.output, tableName), который подразумевает, что возможны другие аргументы, что приведет к переписыванию файла, вместо того, чтобы просто напечатать его, как будет выполняться код, если вы просто запустите его в приведенном выше текстовом файле. Кроме того, если '@' в args.input :, какова цель этого условия, зачем мне вводить @ в аргументах этого сценария ?! (код не поставляется с документацией)

Моя попытка решения (так как я не практикую объектно-ориентированную python): я подумал, что, возможно, я смогу добавить эту функцию в класс Table и затем добавьте следующую строку в окончательный сценарий, чтобы изменить определенное значение столбца на входные значения от пользователя.

    def _changeColumnsValue(self, colName, colValue):
        if colName not in self._columns:
            raise Exception("Not existing column: %s" % colName)
        for row in self._rows:
            setattr(row, colName, colValue) 


    change_value  = input('Which column would you like to change, and to what value? Example input: _rlnVoltage 200')
    tableOut._changeColumnsValue(change_value.split()[0], change_value.split()[1])

1 Ответ

0 голосов
/ 22 января 2020

Справка для parser (можно получить с помощью '-h'):

In [3]: parser.print_help()                                                                      
usage: ipython3 [-h] [-l LIMIT] [input] [output]

Script to manipulate metadata files.

positional arguments:
  input                 Input metadata filename.
  output                Output metadata filename, if no provided, print to
                        stdout.

optional arguments:
  -h, --help            show this help message and exit
  -l LIMIT, --limit LIMIT
                        Limit the number of rows processed, useful for
                        testing.

Если аргументов нет, и args.input, и args.output получают значение по умолчанию - пустую строку.

В одной заданной строке она присваивается input; если две строки, вторая присваивается output.

Вот и все для части argparse.

Что касается @, очевидно, вы можете указать две строки:

tableName@fileName
filename

Я предполагаю, что вы получите ошибку Input file '%s' does not exists, если вы не предоставите строку input.

Смысл этого зависит от функции Table .

if args.output:
    tableOut.write(args.output, tableName)
else:
    tableOut.printStar(tableName)

Если output не является значением по умолчанию, "", передайте эту строку в write, в противном случае используйте printStar.

Я могу представить добавление change_value аргумент parser

add('--change_value', nargs=2, help='column and value that you want to change')

и более поздних

if args.change_value is not None:
      col_name, col_value = args.change_value
      tableOut._changeColumnsValue(col_name, col_value)
...