Генерация и применение различий в Python - PullRequest
27 голосов
/ 22 февраля 2010

Есть ли в python нестандартный способ генерировать список различий между двумя текстами, а затем применять этот diff к одному файлу, чтобы позже получить другой?

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

Ответы [ 6 ]

26 голосов
/ 22 февраля 2010

Вы смотрели на diff-match-patch от Google? Очевидно, Google Docs использует этот набор алгоритмов. Он включает в себя не только модуль diff, но также и модуль патчей, поэтому вы можете сгенерировать самый новый файл из старых файлов и diffs.

Включена версия Python.

http://code.google.com/p/google-diff-match-patch/

10 голосов
/ 22 февраля 2010

difflib.unified_diff хочет, чтобы вы хотели? Вот пример здесь .

2 голосов
/ 05 декабря 2016

Я реализовал чистую функцию python для применения патчей diff для восстановления любой из входных строк, надеюсь, кто-то найдет это полезным. Он использует синтаксический анализ формата Unified diff .

import re

_hdr_pat = re.compile("^@@ -(\d+),?(\d+)? \+(\d+),?(\d+)? @@$")

def apply_patch(s,patch,revert=False):
  """
  Apply unified diff patch to string s to recover newer string.
  If revert is True, treat s as the newer string, recover older string.
  """
  s = s.splitlines(True)
  p = patch.splitlines(True)
  t = ''
  i = sl = 0
  (midx,sign) = (1,'+') if not revert else (3,'-')
  while i < len(p) and p[i].startswith(("---","+++")): i += 1 # skip header lines
  while i < len(p):
    m = _hdr_pat.match(p[i])
    if not m: raise Exception("Cannot process diff")
    i += 1
    l = int(m.group(midx))-1 + (m.group(midx+1) == '0')
    t += ''.join(s[sl:l])
    sl = l
    while i < len(p) and p[i][0] != '@':
      if i+1 < len(p) and p[i+1][0] == '\\': line = p[i][:-1]; i += 2
      else: line = p[i]; i += 1
      if len(line) > 0:
        if line[0] == sign or line[0] == ' ': t += line[1:]
        sl += (line[0] != sign)
  t += ''.join(s[sl:])
  return t

Если есть строки заголовка ("--- ...\n","+++ ...\n"), он пропускает их. Если у нас есть единая строка различий diffstr, представляющая разницу между oldstr и newstr:

# recreate `newstr` from `oldstr`+patch
newstr = apply_patch(oldstr, diffstr)
# recreate `oldstr` from `newstr`+patch
oldstr = apply_patch(newstr, diffstr, True)

В Python вы можете сгенерировать унифицированный diff из двух строк, используя difflib (часть стандартной библиотеки):

import difflib
_no_eol = "\ No newline at end of file"

def make_patch(a,b):
  """
  Get unified string diff between two strings. Trims top two lines.
  Returns empty string if strings are identical.
  """
  diffs = difflib.unified_diff(a.splitlines(True),b.splitlines(True),n=0)
  try: _,_ = next(diffs),next(diffs)
  except StopIteration: pass
  return ''.join([d if d[-1] == '\n' else d+'\n'+_no_eol+'\n' for d in diffs])

В Unix: diff -U0 a.txt b.txt

Код находится здесь на GitHub вместе с тестами с использованием ASCII и случайных символов Юникода: https://gist.github.com/noporpoise/16e731849eb1231e86d78f9dfeca3abc

2 голосов
/ 22 февраля 2010

AFAIK большинство алгоритмов различий используют простое совпадение Longest Common Subsequence , чтобы найти общую часть между двумя текстами, и то, что осталось, считается разницей. Не должно быть слишком сложно написать собственный алгоритм динамического программирования, чтобы достичь этого в python, страница википедии выше также предоставляет этот алгоритм.

1 голос
/ 22 февраля 2010

Должно ли это быть решение Python?
Моими первыми мыслями относительно решения было бы использовать либо систему контроля версий (Subversion, Git и т. Д.), Либо утилиты diff / patch, которые являются стандартными для системы Unix или являются частью cygwin для система на базе Windows.

0 голосов
/ 09 марта 2016

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

diff = difflib.unified_diff(old_file, new_file, lineterm='')
    lines = list(diff)[2:]
    # linesT = list(diff)[0:3]
    print (lines[0])
    added = [lineA for lineA in lines if lineA[0] == '+']


    with open("output.txt", "w") as fh1:
     for line in added:
       fh1.write(line)
    print '+',added
    removed = [lineB for lineB in lines if lineB[0] == '-']
    with open("output.txt", "a") as fh1:
     for line in removed:
       fh1.write(line)
    print '-',removed 

Используйте это в своем коде для сохранения только разностного вывода!

...