Обновление данных файла записи


Я научился код долгое, долгое время назад.

Проблемы

Существующий подход неаккуратно и будет трудно поддерживать. Ошибок система не в ловушке и сообщения об ошибках могут быть лучше реализованы. Окончательный расчет хэш-кода происходит после файл данные записываются.

Я через несколько питона учебники некоторое время назад, но это трудно, без проблем реального мира. Я хочу использовать это в качестве обучения ООП возможности. Этот код на самом деле делает свое дело и хорошо на пути к завершению функция проверки файлов пока не реализована.

Все для обзор здесь.

Ситуация

Файлы данных создаются дорожную модель и данная программа быстро создает сценарии для тестирования вариант. Он включает перезапись два поля барьера и и один из двух обгона поля в дорогу (штанга файл). Я не понимаю, не нужно заботиться о многих других областях, но они не должны быть изменены, как некоторые предполагают сложные расчеты. Хэш-код вводится для предотвращения случайного удилище изменения файла, но они должны оставаться читабельным. Два других файлов являются частью набора данных, но изменения тривиальны - переименование файлов и замена первой строки с именем.

Так, в свойствах файла базы данных с именем, с именем и описанием на первые две строчки, затем еще восемь линий до 109 символов записи начать. Первые шесть символов в каждой записи на расстоянии (начиная с некоторого произвольного значения возле 0.0) увеличивается на 0,1 в каждой записи. Целые числа без десятичных или трейлинг-ноль. Есть два других файлов в наборе данных с различными расширениями.

Программа предоставляет услугу создания нового варианта из имеющегося набора дорожных файл. (Новый вариант может быть обновление существующего набора данных.) Проверил файл и командной строки и структуры дорожно-файл данных, проанализировала. В регистре хэш-код предоставляет проверки и обновления.

#!/usr/bin/python
# With existing road file create new overtaking lane option file(s).
#
# Copy file, insert overtaking lane and barrier line codes for specified
# chainage, replace first line with new filename, second line with description
# and update MD5 HashCode (future).
#
# eg python OptionEditor.py Demo.ROD 1.5 2.2 P Option.ROD Destination_Description
#
# By Neale Irons version 25/02/2018 (CC BY-SA 4.0)


import sys  
import os
import shutil
import tempfile
import hashlib

def main():  

    def copyFile(src, dest):
    # From https://www.pythoncentral.io/how-to-copy-a-file-in-python-with-shutil/
        try:
            shutil.copy(src, dest)
        # eg. src and dest are the same file
        except shutil.Error as e:
            print('Error: %s' % e)
        # eg. source or destination doesn't exist
        except IOError as e:
            print('Error: %s' % e.strerror)
        return

    def HashCode_register(HASHFILE, src, action):
#        with open (hashfile) as hf:
#        hash, fn = (open(HASHFILE).read().split('\n').split(' *')
        hashcode = hashlib.md5(open(src, 'rb').read()).hexdigest()
        print hashcode+ " *" + src                                  # Debug ****
        lines = open(HASHFILE).read().split('\n')
        for item in lines:
            if action == 'validate':
                if hashcode in item: # Maybe uppercase compare
                    return True
            elif action == "update":
                if src in item: # Maybe uppercase compare
                    print lines                                      # Debug - no-op
#                    if item_number_hashcode != hashcode:            # FIX THIS ***
#                        item_hashcode(update_number) = hashcode     # need write access here FIX THIS ***
#                else:
#                    write_append_HASFILE(hashcode + " *" + src)     # need write access here FIX THIS ***
#                    return True
        return False


    def replace_textfile_line(text_filename, new_text, description):
    # Replace first line with new_text.
    #
    # Adapted from ReplFHdr.py
    # By Neale Irons version 14/02/2018 (CC BY-SA 4.0)
    # import sys, os, tempfile
        with open(text_filename) as src:
            with tempfile.NamedTemporaryFile('w',
              dir=os.path.dirname(text_filename), delete=False) as dst:
                line = src.readline()               # Read first line
                dst.write(new_text + '\n')          # Replace first line
                if description:                     # If description not empty
                    line = src.readline()           # Read second line
                    dst.write(description + '\n')   # Replace second line
                shutil.copyfileobj(src, dst)        # Copy rest of file
        os.unlink(text_filename)                    # remove old version
        os.rename(dst.name, text_filename)          # rename new version
        return()


    #Constants
    ODO_STEP_KM = 0.1
    RECORD_SIZE = 109
    BARRIER_COLUMN1 = 11
    BARRIER_COLUMN2 = 16
    BARRIER_VALUE = '+' # Debug - use '+' instead of '-'
    OTLANE_COLUMN1 = 22
    OTLANE_COLUMN2 = 27
    OTLANE_VALUE = 'Y'  # Debug - use 'Y' instead of 'T'
    OTLANE_DIRECTION_CODES = "PC"
    HASHFILE = "Hashcode.MD5"
    True = not False

    # Validate command line
    if len(sys.argv) < 6 or len(sys.argv) > 7:
        format_string = ('  Usage: python {} Source.ROD Start_km End_km P|C Destination.ROD ["Destination_Description"]')
        print(format_string.format(sys.argv[0]))
        print('    Use existing road files to create new overtaking lane option and optionally insert second line description.)')
        sys.exit()

    src = sys.argv[1]
    otl_start_km = float(sys.argv[2])
    otl_end_km = float(sys.argv[3])
    otl_direction = sys.argv[4]
    dest = sys.argv[5]
    if len(sys.argv) < 6:
        description = sys.argv[6]
    else:
        description = ''

    if otl_start_km > otl_end_km:
        print("Invalid start/end overtaking lane chainage - start must be less than end. Exiting...")
        sys.exit() 

    if otl_direction not in OTLANE_DIRECTION_CODES:
        print("Direction {} is invalid, must be P or C. Exiting..."
          .format(sys.argv[4]))
        sys.exit() 


    # Validate file
    if not os.path.isfile(src):
        print("File path {} does not exist. Exiting..."
          .format(src))
        sys.exit()

    if src <> dest:
        # Validate support files
        src_name, src_ext = os.path.splitext(src)
        if not os.path.isfile(src_name+'.MLT'):
            print("Requires support file {}. Exiting..."
              .format(src_name+'.MLT'))
            sys.exit()
        if not os.path.isfile(src_name+'.OBS'):
            print("Requires support file {}. Exiting..."
              .format(src_name+'.OBS'))
            sys.exit()

    # Validate HashCode
    if not HashCode_register(HASHFILE, src, "validate"):
        print("Invalid file - failed HashCode. Exiting...")

    # Read header
    with open(src) as fp:
        # *** if fails raise "Invalid file format - first 10 lines must be file header
        for x in range(1, 10):
            line = fp.readline()
        header_length=fp.tell() # Calculate header length
        fp.seek(0,2)
        file_length = fp.tell()
    fp.close()

    if (file_length - header_length) % RECORD_SIZE != 0:
        print("Invalid file format - must be 10 lines and {} character records. Exiting..."
          .format((file_length - header_length) % RECORD_SIZE))
        sys.exit()

    nrecs = (file_length - header_length) // RECORD_SIZE
    if nrecs < 1:
        print("Invalid file format - file must contain at least one record. Exiting...")
        sys.exit()

    # dest_tmp = NamedTemporaryFile(delete=False)
    dest_tmp = src + ".tmp"
#    print("Temp file: {}".format(dest_tmp))   # Debug
    copyFile(src, dest_tmp)


    # Open file for random read/write
    with open(dest_tmp, 'r+b') as fp:
        fp.seek(header_length,0)
        odo_start_km = float((fp.read(6)))        
        fp.seek(RECORD_SIZE * -1, 2)
        odo_end_km = float((fp.read(6)))

        if otl_start_km <= odo_start_km:
            print("Invalid start overtaking lane chainage - Overtaking lane can't start before first road chainage. Exiting...")
            sys.exit()

        if otl_end_km >= odo_end_km:
            print("Invalid end overtaking lane chainage - Overtaking lane can't ends after last road chainage. Exiting...")
            sys.exit()

        if int(round((odo_end_km - odo_start_km)/ODO_STEP_KM) + 1) != nrecs:
            print("Invalid file format - incorrect number of lines between start and end distances. Exiting...")
            sys.exit()

        while otl_start_km < otl_end_km:
            fpos=int(round(header_length + ((otl_start_km - odo_start_km) / ODO_STEP_KM * RECORD_SIZE) - 1))

            fp.seek(BARRIER_COLUMN1 + fpos)
            fp.write(BARRIER_VALUE)
            fp.seek(BARRIER_COLUMN2 + fpos)
            fp.write(BARRIER_VALUE)

            if otl_direction == "P":
                fp.seek(OTLANE_COLUMN1 + fpos)
            elif otl_direction == "C":
                fp.seek(OTLANE_COLUMN2 + fpos)
            fp.write(OTLANE_VALUE)
            otl_start_km += ODO_STEP_KM

        fp.close()

    if dest <> src :
        # Create new supporting files
        dest_name, dest_ext = os.path.splitext(dest)
        copyFile(src_name+'.MLT', dest_name+'.MLT')
        replace_textfile_line(src_name+'.MLT', src_name+'.MLT', '')
        copyFile(src_name+'.OBS', dest_name+'.OBS')        

    replace_textfile_line(dest_tmp, dest, description)
    HashCode_register(HASHFILE, src, "update")                          # Not working yet ****    

    from shutil import move
    move(dest_tmp, dest)

if __name__ == '__main__':
    main()

Запись фрагмента данных (не так много происходит в этом примере):

   0.8    -1   -1    F    F    9    9
   0.9    -1   -1    F    F   10  201
     1    -1   -1    F    F   11  202
   1.1    -1   -1    F    F   12   12
   1.2     1   -1    F    F   13   13
   1.3     1   -1    F    F   14   14
   1.4     1   -1    F    F   15   15
   1.5    -1    1    F    F   16   16
   1.6    -1    1    F    F   17   17
   1.7    -1    1    F    F   18   18
   1.8    -1   -1    F    F   19   19
   1.9    -1   -1    F    F   20   20
     2    -1   -1    F    F   21   21
   2.1    -1   -1    F    F   22   22
   2.2    -1   -1    F    F   23   23
   2.3    -1   -1    F    F   24   24

Хэш-код.Алгоритм MD5:

8d443f2e93a3f0b67f442e4f1d5a4d6d *md5.exe
eb574b236133e60c989c6f472f07827b *md5sum.exe
c1f62b08d050f2a30790c5b2f3d29f5c *Demo.ROD
9f036e40755daaa5a5fc141d3cbfbb99 *nGetPid.exe


Комментарии
1 ответ

#!/usr/bin/python

Рассмотрите возможность использования #! /usr/bin/env python вместо (или python3). Затем вы можете управлять импортной зависимости с venv или, лучше, conda env update. Стадион conda будет корректировать путь, чтобы указать на соответствующий интерпретатор Python, который имеет необходимые библиотеки pypi, которые неизбежно добавляются в проект.

# By Neale Irons version 25/02/2018 (CC BY-SA 4.0)

Ты, наверное, хотел использовать словосочетание "Авторское право", если вы хотите отстаивать права ИС и изменять их с помощью творческих объединений.

def main():

В main функция полностью слишком долго. Ограничивать себя в один экран на функцию. Как минимум, продвигать какие-либо вспомогательные функции, которые могут быть полезны для других функций для вызова или повторного использования.

def copyFile(src, dest):

Пожалуйста, Пеп-8 именовании, и называть это copy_file.

    try:
shutil.copy(src, dest)

Джо Пейн был просто исключения, иллюстрирующие, что один может хотите поймать. Вот, непонятно, что вы действительно хотите, чтобы поймать их, учитывая, что вы на самом деле не "ручка" ошибка, вы просто проглотите ее после того, излучающего шум в stdout. Если copy_file просто псевдоним для copy, это было бы нормально. Но здесь, мне кажется, что copy_file на самом деле хуже, это удается сделать медвежью услугу для абонента, при глотании исключения. Обычный пост-состояние будет "этот метод гарантирует конечный файл существует после возвращения", но как написан пост-условием является пустое множество, метод не дает никаких гарантий корректности вообще. Это делает вещи более трудным для программистов содержание читать любой код вызова несколько месяцев вниз по дороге.

def HashCode_register(HASHFILE, src, action):

Ок, теперь ты просто сходишь с ума дело. Использовать в нижнем регистре с подчеркиваниями, пожалуйста. Да, я знаю, что ты проходишь в явной константы в вызывающий код. Но определение функции должны использовать нижний регистр для формального параметра.

#        with open (hashfile) as hf:

Мне нравится with! Почему вы решили взять на себя ответственность за выдачу close()и потом не закрыть hf?

    print hashcode+ " *" + src

Вы, кажется, используя вместо python2. Я призываю вас использовать питон3 вместо. Это делает некоторые вещи проще, например ввода/вывода кодировать / декодировать правильно. Запустить flake8 *.py по вашим источникам, и следовать его советам относительно пустое пространство вокруг + оператора.

    lines = open(HASHFILE).read().split('\n')

Это равносильно .readlines() функция. Но было бы более естественным (и менее интенсивный памяти) сформулировать так:

    with open (hashfile) as hf:
for line in hf:
line = line.rstrip()
...

В цикле вы пишете:

    for item in lines:

Это немного странно. Обычная идиома будет:

    for line in lines:

В общем, если у вас есть множественное число повторяемое xsпройдемся с for x in xs.

        if action == 'validate': ...
elif action == "update":

Есть только два значения, которая предполагает вы можете выбрать, чтобы пройти в логическое. Если три значения необходимы, рекомендуется использовать перечисление: https://docs.python.org/3/library/enum.html

Ок, я прочитал функцию, и я до сих пор не знаю, что он делает. Пожалуйста, напишите строкой документации пояснив, что обещание это делает о его логическое возвращаемое значение. "Обновить" дела, учитывая нынешнюю не комментировать код, по-видимому, постоянные ложные функции.

В replace_textfile_lineудалите return() линии.

True = not False

Это не смешно. Делать не переопределить встроенные собственные значения, вычисленные значения. И даже не думай о назначении 3.2 к математике.пи как Индиана законодатели раньше.

if len(sys.argv) < 6 or len(sys.argv) > 7:

Командная строка представляется достаточно сложной, чтобы оправдать использование библиотеки: https://docs.python.org/3/library/argparse.html плюс, тогда вы получите --help бесплатно!

    format_string = ('  Usage: python {} Source.ROD Start_km End_km P|C Destination.ROD ["Destination_Description"]')

Идентификатор, который вы искали: usage = ...

if not os.path.isfile(src):

Это правильно. Но, подумайте, позволяя open сделать проверку для вас. Если файл не может быть прочитан, мы узнаем достаточно скоро. Те же замечания для последующих проверок файлов.

if src <> dest:

Это правильно, но странно. Пожалуйста, используйте современный != оператора.

if not HashCode_register(HASHFILE, src, "validate"):
print("Invalid file - failed HashCode. Exiting...")

Я не верю тебе. Я не вижу немедленного выхода. Рекомендуется включить эту функцию в один, что вызывает неустранимое исключение при поиске файла, который не проверяет.

    ...
header_length=fp.tell()

Эта логика запутанная. Пожалуйста, напишите вспомогательную функцию для него. Если читать весь файл является приемлемым, то это может быть достаточно:

    header_length = len(''.join(fp.readlines()[:10]))
file_length = os.path.getsize(src)

В with обработчик-это хороший стиль - продолжать использовать его.

fp.close()

fp уже закрытые на данный момент, благодаря with.

if (file_length - header_length) % RECORD_SIZE != 0:

Это хорошо, насколько это идет, но я бы предпочел увидеть более полезным диагностическим, например

lines = fp.readlines()
for line in lines[:10]:
if len(line) != RECORD_SIZE: # then show some helpful diagnostic

Ой, подождите, я вижу все это, должно соответствовать. Так что используйте for line in lines:. Кроме того, nrec может быть len(lines).

    fp.seek(RECORD_SIZE * -1, 2)

Ты заставляешь меня нервничать. Используя lines[i] и lines[j] могут быть более надежными, в случае коротких или длинных линий закралась входного файла. Вы, кажется, полагаете, что каждый 109-й символ будет символом новой строки, но вы не проверить.

        fp.seek(BARRIER_COLUMN1 + fpos)
fp.write(BARRIER_VALUE)

Может быть, у вас есть гигантский файл и импортировать его починить на месте. Но ваш код будет более проверяемым / чтения / поддерживаемых, если это чтение строк из исходного файла и пишет модифицированного линии на дест файл, возможно с переименовать файл на успех. Вы использовали этот узор очень хорошо в предыдущем фрагменте кода. Я обеспокоен возможностью молча тлетворного файл не проверен. Текущий код может работать отлично, но после того, как месяцы проходят, вместе с персоналом, кому-то придется столкнуться с задачей обслуживания, которая сложнее, чем она должна быть. Ведь иногда форматы файла изменения, и фиксированный размер записи 109 не может длиться до конца времен.

if dest <> src :

Пожалуйста, используйте !=и обратите внимание на flake8 когда он говорит вам, чтобы удалить пробел перед двоеточием.

from shutil import move

Пожалуйста, держите ваш импорта в верхней части файла. Идентификатор универсален настолько, что может быть яснее сформулировать как import shutil а потом shutil.move(...)

Держать на него! Заставить людей читать ваш код будет иметь хорошее влияние на четкость код, который вы напишете в будущем.

5
ответ дан 26 февраля 2018 в 03:02 Источник Поделиться