difflib — 델타 계산을 위한 도우미

소스 코드: Lib/difflib.py


This module provides classes and functions for comparing sequences. Most of them compare sequences of text lines (for example lists of strings, or file objects) and produce diffs – reports on the differences. Diffs can be produced in in various formats, including HTML and context and unified diffs – formats produced by tools like diff and git diff.

Comparisons are done using a matching algorithm implemented in SequenceMatcher – a flexible class for comparing pairs of sequences of any type, not just text, so long as the sequence elements are hashable.

Junk heuristic

difflib uses a junk heuristic: some items are deemed to be junk, and ignored when searching for similarities. Ideally, these are uninteresting or common items, such as blank lines or whitespace.

This heuristic can speed the algorithm up (because it reduces the number of possible combinations) and it can produce results that are more understandable for humans (typically breaking on whitespace). But it can also cause pathological cases:

  • Inappropriately chosen junk items can cause an unexpectedly large (but still correct) result.

  • The default heuristic is asymmetric: only the second sequence is inspected when determining what is considered junk, so comparing A to B can give different results than comparing B to A and reversing the result.

By default, if the second input sequence is at least 200 items long, items that account for more than 1% it are considered junk.

Depending on your data, you should consider turning this heuristic off (setting SequenceMatcher’s autojunk argument to to False) or tuning it (using the isjunk argument, perhaps to one of the predefined functions).

The difflib algorithm

The algorithm used in SequenceMatcher predates, and is a little fancier than, an algorithm published in the late 1980s by Ratcliff and Obershelp under the hyperbolic name “gestalt pattern matching.” The idea is to find the longest contiguous subsequence common to both inputs, then recursively handle the pieces of the sequences to the left and to the right of the matching subsequence.

더 보기

Pattern Matching: The Gestalt Approach

Discussion of a similar algorithm by John W. Ratcliff and D. E. Metzener. This was published in Dr. Dobb’s Journal in July, 1988.

As an extension to the Ratcliff and Obershelp algorithm, difflib searches for the longest junk-free contiguous subsequence. See the Junk heuristic section for details.

CPython 구현 상세: Timing

The basic Ratcliff-Obershelp algorithm is cubic time in the worst case and quadratic time in the expected case. difflib’s algorithm is quadratic time for the worst case and has expected-case behavior dependent in a complicated way on how many elements the sequences have in common; best case time is linear.

Diff generation

class difflib.Differ

이것은 텍스트 줄의 시퀀스를 비교하고, 사람이 읽을 수 있는 차이 또는 델타를 생성하는 클래스입니다. Differ는 줄의 시퀀스를 비교하고, 유사한 (거의 일치하는) 줄 내의 문자 시퀀스를 비교하는데 SequenceMatcher를 사용합니다.

Differ 델타의 각 줄은 2자 코드로 시작합니다:

코드

'- '

시퀀스 1에만 있는 줄

'+ '

시퀀스 2에만 있는 줄

'  '

두 시퀀스에 공통인 줄

'? '

두 입력 시퀀스에 없는 줄

?'로 시작하는 줄은, 시선을 줄 내의 차이로 유도하려고 시도하며, 두 입력 시퀀스 어디에도 나타나지 않습니다. 이 줄은 시퀀스에 스페이스, 탭 또는 줄 넘김과 같은 공백 문자가 포함되면 혼동을 줄 수 있습니다.

Note that Differ-generated deltas make no claim to be minimal diffs. To the contrary, minimal diffs are often counter-intuitive for humans, because they synch up anywhere possible, sometimes at accidental matches 100 pages apart. Restricting synch points to contiguous matches preserves some notion of locality, at the occasional cost of producing a longer diff.

Differ 클래스에는 다음과 같은 생성자가 있습니다:

__init__(linejunk=None, charjunk=None)

선택적 키워드 매개 변수 linejunkcharjunk는 필터 함수(또는 None)를 위한 것입니다:

linejunk: 단일 문자열 인자를 받아들이고 문자열이 정크면 참을 반환하는 함수입니다. 기본값은 None이며, 이는 어떤 줄도 정크로 간주하지 않음을 의미합니다.

charjunk: 문자(길이 1의 문자열)를 받아들이고, 문자가 정크면 참을 반환하는 함수입니다. 기본값은 None이며, 이는 어떤 문자도 정크로 간주하지 않음을 의미합니다.

이러한 정크 필터링 함수는 차이점을 찾기 위한 일치 속도를 높이고 차이가 나는 줄이나 문자를 무시하지 않습니다. 설명이 필요하면 find_longest_match() 메서드의 isjunk 매개 변수에 대한 설명을 읽으십시오.

Differ 객체는 단일 메서드를 통해 사용됩니다 (델타가 만들어집니다):

compare(a, b)

줄의 시퀀스 두 개를 비교하고, 델타(줄의 시퀀스)를 만듭니다.

Each sequence must contain individual single-line strings ending with newlines. Such sequences can be obtained from the readlines() method of file-like objects. The generated delta also consists of newline-terminated strings, ready to be printed as-is via the writelines() method of a file-like object.

class difflib.HtmlDiff

이 클래스는 HTML 표를 (또는 표를 포함하는 완전한 HTML 파일을) 만드는 데 사용할 수 있습니다. 이 HTML은 줄 간과 줄 내의 변경을 강조하면서, 텍스트를 나란히 줄 단위로 비교하여 보여줍니다. 표는 전체 또는 문맥 차이 모드로 생성될 수 있습니다.

경고

The trailing newlines get stripped before the diff, so the result can be incomplete. See gh-71896 for details.

이 클래스의 생성자는 다음과 같습니다:

__init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)

HtmlDiff의 인스턴스를 초기화합니다.

tabsize는 탭 간격을 지정하는 선택적 키워드 인자이며 기본값은 8입니다.

wrapcolumn는 줄이 자동 줄 넘김 되는 열 번호를 지정하는 선택적 키워드로, 줄을 자동 줄 넘김 하지 않는 None이 기본값입니다.

linejunkcharjunkndiff()(HtmlDiff가 나란히 배치된 HTML 차이를 만드는 데 사용됩니다)로 전달되는 선택적 키워드 인자입니다. 인자 기본값과 설명은 ndiff() 설명서를 참조하십시오.

다음과 같은 메서드가 공개됩니다:

make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')

fromlinestolines(문자열의 리스트)를 비교하고, 줄 간 및 줄 내부의 변경을 강조하면서, 줄 단위로 차이를 보여주는 표를 포함하는 완전한 HTML 파일을 문자열로 반환합니다.

fromdesctodesc는 from/to 파일 열 헤더 문자열을 지정하는 선택적 키워드 인자입니다 (기본값은 모두 빈 문자열입니다).

contextnumlines는 모두 선택적 키워드 인자입니다. 문맥 차이를 표시하려면 contextTrue로 설정하십시오, 그렇지 않으면 기본값은 전체 파일을 표시하는 False입니다. numlines의 기본값은 5입니다. contextTrue 일 때, numlines는 차이 하이라이트를 둘러싸는 문맥 줄의 수를 제어합니다. contextFalsenumlines는 “next” 하이퍼 링크를 사용할 때 차이 하이라이트 앞에 표시되는 줄 수를 제어합니다 (0으로 설정하면 “next” 하이퍼 링크가 다음 차이 하이라이트를 아무런 선행 문맥 줄 없이 브라우저의 맨 위에 놓도록 합니다).

참고

fromdesctodesc는 이스케이프 되지 않은 HTML로 해석되며 신뢰할 수 없는 소스로부터 입력을 받는 동안 적절히 이스케이프 되어야 합니다.

버전 3.5에서 변경: charset 키워드 전용 인자가 추가되었습니다. HTML 문서의 기본 문자 집합이 'ISO-8859-1'에서 'utf-8'로 변경되었습니다.

make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)

fromlinestolines(문자열의 리스트)를 비교하고, 줄 간 및 줄 내부의 변경을 강조하면서, 줄 단위로 차이를 보여주는 완전한 HTML 표를 문자열로 반환합니다.

이 메서드의 인자는 make_file() 메서드의 인자와 같습니다.

difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

ab(문자열의 리스트)를 비교합니다; 델타(델타 줄을 생성하는 제너레이터)를 문맥 diff 형식으로 반환합니다.

문맥 diff는 단지 변경된 줄과 몇 줄의 문맥만을 더해서 표시하는 간결한 방법입니다. 변경 사항은 이전/이후 스타일로 표시됩니다. 문맥 줄의 수는 n에 의해 설정되며 기본값은 3입니다.

기본적으로, diff 제어 줄(***---가 포함된 것)은 끝에 줄 넘김을 붙여 만들어집니다. 이것은 io.IOBase.readlines()로 만들어진 입력이 io.IOBase.writelines()와 함께 사용하기에 적합한 diff를 생성하도록 하는 데 유용합니다. 왜냐하면, 입력과 출력 모두 끝에 줄 넘김이 있기 때문입니다.

끝에 줄 넘김이 없는 입력이면, lineterm 인자를 ""로 설정해서 출력에 일관되게 줄 넘김이 포함되지 않게 하십시오.

문맥 diff 형식에는 일반적으로 파일명과 수정 시간에 대한 헤더가 있습니다. 이들 중 일부 또는 전부는 fromfile, tofile, fromfiledatetofiledate에 문자열을 사용하여 지정될 수 있습니다. 수정 시간은 일반적으로 ISO 8601 형식으로 표현됩니다. 지정하지 않으면, 문자열들의 기본값은 빈 문자열입니다.

>>> import sys
>>> from difflib import *
>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(context_diff(s1, s2, fromfile='before.py',
...                        tofile='after.py'))
*** before.py
--- after.py
***************
*** 1,4 ****
! bacon
! eggs
! ham
  guido
--- 1,4 ----
! python
! eggy
! hamster
  guido

더욱 자세한 예제는 difflib의 명령 줄 인터페이스를 참조하십시오.

difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)

최상의 “충분히 좋은” 일치의 리스트를 반환합니다. word는 근접 일치가 목표로 하는 시퀀스(일반적으로 문자열)며, possibilitiesword와 일치시킬 시퀀스의 리스트입니다 (일반적으로 문자열의 리스트).

선택적 인자 n(기본값 3)은 반환할 근접 일치의 최대 개수입니다; n0보다 커야 합니다.

선택적 인자 cutoff(기본값 0.6)는 [0, 1] 범위의 float입니다. word와의 유사성 점수가 이 값보다 적은 possibilities는 무시됩니다.

possibilities 중에서 가장 좋은 (최대 n 개의) 일치가 리스트로 반환되는데, 유사성 점수로 정렬되어 있고 가장 유사한 것이 먼저 나옵니다.

>>> get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'])
['apple', 'ape']
>>> import keyword
>>> get_close_matches('wheel', keyword.kwlist)
['while']
>>> get_close_matches('pineapple', keyword.kwlist)
[]
>>> get_close_matches('accept', keyword.kwlist)
['except']
difflib.ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)

ab(문자열의 리스트)를 비교합니다; Differ-스타일 델타(델타 줄을 생성하는 제너레이터)를 반환합니다.

선택적 키워드 매개 변수 linejunkcharjunk는 필터링 함수(또는 None)입니다:

linejunk: A function that accepts a single string argument, and returns true if the string is junk, or false if not. The default is None. There is also a module-level function IS_LINE_JUNK(), which filters out lines without visible characters, except for at most one hash character ('#') – however the underlying SequenceMatcher class does a dynamic analysis of which lines are so frequent as to constitute noise, and this usually works better than using this function.

charjunk: 문자(길이 1의 문자열)를 받아들이고, 문자가 정크면 참을 반환하고, 그렇지 않으면 거짓을 반환하는 함수입니다. 기본값은 모듈 수준의 함수 IS_CHARACTER_JUNK()인데, 공백 문자(스페이스나 탭; 줄 넘김 문자를 포함하는 것은 좋은 생각이 아닙니다)를 걸러냅니다.

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> print(''.join(diff), end="")
- one
?  ^
+ ore
?  ^
- two
- three
?  -
+ tree
+ emu
difflib.restore(sequence, which)

델타를 만든 두 시퀀스 중 하나를 반환합니다.

Differ.compare()ndiff()로 만들어진 sequence가 주어지면, 파일 1이나 2(매개 변수 which)에서 원래 제공되었던 줄을 추출하고, 줄 접두어를 제거합니다.

예:

>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
...              'ore\ntree\nemu\n'.splitlines(keepends=True))
>>> diff = list(diff) # materialize the generated delta into a list
>>> print(''.join(restore(diff, 1)), end="")
one
two
three
>>> print(''.join(restore(diff, 2)), end="")
ore
tree
emu
difflib.unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')

ab(문자열의 리스트)를 비교합니다; 델타(델타 줄을 생성하는 제너레이터)를 통합 diff 형식으로 반환합니다.

통합(unified) diff는 단지 변경된 줄과 몇 줄의 문맥만을 더해서 표시하는 간결한 방법입니다. 변경 사항은 (별도의 이전/이후 블록 대신) 인라인 스타일로 표시됩니다. 문맥 줄의 수는 n에 의해 설정되며 기본값은 3입니다.

기본적으로, diff 제어 줄(---, +++ 또는 @@가 포함된 것)은 끝에 줄 넘김을 붙여 만들어집니다. 이것은 io.IOBase.readlines()로 만들어진 입력이 io.IOBase.writelines()와 함께 사용하기에 적합한 diff를 생성하도록 하는 데 유용합니다. 왜냐하면, 입력과 출력 모두 끝에 줄 넘김이 있기 때문입니다.

끝에 줄 넘김이 없는 입력이면, lineterm 인자를 ""로 설정해서 출력에 일관되게 줄 넘김이 포함되지 않게 하십시오.

통합(unified) diff 형식에는 일반적으로 파일명과 수정 시간에 대한 헤더가 있습니다. 이들 중 일부 또는 전부는 fromfile, tofile, fromfiledatetofiledate에 문자열을 사용하여 지정될 수 있습니다. 수정 시간은 일반적으로 ISO 8601 형식으로 표현됩니다. 지정하지 않으면, 문자열들의 기본값은 빈 문자열입니다.

>>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n']
>>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n']
>>> sys.stdout.writelines(unified_diff(s1, s2, fromfile='before.py', tofile='after.py'))
--- before.py
+++ after.py
@@ -1,4 +1,4 @@
-bacon
-eggs
-ham
+python
+eggy
+hamster
 guido

더욱 자세한 예제는 difflib의 명령 줄 인터페이스를 참조하십시오.

difflib.diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')

ab(바이트열 객체의 리스트)를 dfunc를 사용하여 비교합니다; dfunc가 반환하는 형식으로 델타 줄(역시 바이트열)의 시퀀스를 산출합니다. dfunc는 콜러블이어야하며, 보통 unified_diff()context_diff()입니다.

알 수 없거나 일관성 없는 인코딩의 데이터를 비교할 수 있게 합니다. n를 제외한 모든 입력은 바이트열 객체여야 합니다, str이 아닙니다. 모든 입력(n 제외)을 str로 무손실 변환하고, dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)를 호출하는 방식으로 작동합니다. dfunc의 출력은 다시 바이트로 변환되므로, 여러분이 얻는 델타 줄은 ab 처럼 알 수 없고/일관성 없는 인코딩을 갖습니다.

Added in version 3.5.

Junk definition functions

difflib.IS_LINE_JUNK(line)

무시할 수 있는 줄이면 True를 반환합니다. line이 빈 줄이거나 하나의 '#'를 포함하면, 줄 line은 무시할 수 있습니다, 그렇지 않으면 무시할 수 없습니다. 이전 버전의 ndiff()에서 매개 변수 linejunk의 기본값으로 사용되었습니다.

difflib.IS_CHARACTER_JUNK(ch)

무시할 수 있는 문자면 True를 반환합니다. ch가 스페이스나 탭이면 문자 ch는 무시할 수 있습니다, 그렇지 않으면 무시할 수 없습니다. ndiff()에서 매개 변수 charjunk의 기본값으로 사용됩니다.

SequenceMatcher objects

class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)

선택 인자 isjunkNone(기본값)이거나, 시퀀스 요소를 받아서 요소가 “정크” 이고, 무시되어야 하는 경우에만 참을 반환하는 하나의 인자 함수여야 합니다. isjunkNone을 전달하는 것은, lambda x: False를 전달하는 것과 같습니다; 즉, 아무 요소도 무시하지 않습니다. 예를 들어,:

lambda x: x in " \t"

줄을 문자의 시퀀스로 비교하고, 스페이스와 탭을 무시하고 싶으면, 위와 같은 것을 전달하면 됩니다.

선택적 인자 ab는 비교할 시퀀스입니다; 둘 다 빈 문자열이 기본값입니다. 두 시퀀스의 요소는 모두 해시 가능해야 합니다.

선택적 인자 autojunk는 자동 정크 휴리스틱을 비활성화하는 데 사용할 수 있습니다.

버전 3.2에서 변경: autojunk 매개 변수를 추가했습니다.

SequenceMatcher 객체는 세 개의 데이터 어트리뷰트를 갖습니다: bjunkisjunkTrueb 요소의 집합입니다; bpopular는 휴리스틱(비활성화하지 않았다면)에서 흔하다고 판단되는 정크가 아닌 요소의 집합입니다; b2jb의 나머지 요소를 그들이 나타난 위치의 리스트로 매핑하는 dict입니다. bset_seqs()set_seq2()로 재설정 될 때마다 세 개 모두 재설정됩니다.

Added in version 3.2: bjunkbpopular 어트리뷰트

SequenceMatcher 객체에는 다음과 같은 메서드가 있습니다:

set_seqs(a, b)

비교할 두 시퀀스를 설정합니다.

SequenceMatcher는 두 번째 시퀀스에 대한 자세한 정보를 계산하고 캐시 하므로, 많은 시퀀스에 대해 하나의 시퀀스를 비교하려면, set_seq2()를 사용하여 자주 사용되는 시퀀스를 한 번 설정하고, set_seq1()를 다른 시퀀스 각각에 대해 한 번 반복적으로 호출하십시오.

set_seq1(a)

비교할 첫 번째 시퀀스를 설정합니다. 비교할 두 번째 시퀀스는 변경되지 않습니다.

set_seq2(b)

비교할 두 번째 시퀀스를 설정합니다. 비교할 첫 번째 시퀀스는 변경되지 않습니다.

find_longest_match(alo=0, ahi=None, blo=0, bhi=None)

a[alo:ahi]b[blo:bhi]에서 가장 긴 일치 블록을 찾습니다.

isjunk가 생략되거나 None 이면, find_longest_match()a[i:i+k]b[j:j+k]와 같은 (i, j, k)를 반환하는데, 여기서 alo <= i <= i+k <= ahi 이고 blo <= j <= j+k <= bhi 입니다. 이 조건을 만족시키는 모든 (i', j', k')에 대해, 추가 조건 k >= k', i <= i'i == i'j <= j' 도 만족합니다. 즉, 모든 최대 일치 블록 중에서 a에서 가장 먼저 시작하는 블록을 반환하고, a에서 가장 먼저 시작하는 모든 최대 일치 블록 중에서 b에서 가장 먼저 시작하는 블록을 반환합니다.

>>> s = SequenceMatcher(None, " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=0, b=4, size=5)

isjunk가 제공되면, 먼저 가장 긴 일치 블록이 상기와 같이 결정되지만, 정크 요소가 블록에 나타나지 않아야 한다는 추가 제약이 있습니다. 그런 다음 그 블록의 좌우에서 정크 요소만 일치시켜 가능한 한 최대로 확장합니다. 그래서 결과 블록은 흥미로운 일치와 인접하게 같은 정크가 등장할 때를 제외하고는, 정크와 일치하지 않습니다.

여기에 이전과 같은 예가 있지만, 스페이스를 정크로 간주합니다. 이렇게 하면 ' abcd'가 두 번째 시퀀스의 끝에 있는 ' abcd'와 직접 일치하지 않게 됩니다. 대신 'abcd' 만 일치 할 수 있으며, 두 번째 시퀀스에서 가장 왼쪽의 'abcd'와 일치합니다:

>>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd")
>>> s.find_longest_match(0, 5, 0, 9)
Match(a=1, b=0, size=4)

일치하는 블록이 없으면 (alo, blo, 0)를 반환합니다.

이 메서드는 네임드 튜플 Match(a, b, size)를 반환합니다.

버전 3.9에서 변경: 인자 기본값이 추가되었습니다.

get_matching_blocks()

중첩하지 않는 일치하는 서브 시퀀스를 기술하는 3-튜플의 리스트를 반환합니다. 각 3-튜플은 (i, j, n) 형식이며, a[i:i+n] == b[j:j+n]를 뜻합니다. 3-튜플은 ij에 대해 단조 증가합니다.

마지막 3-튜플은 더미이며, (len(a), len(b), 0) 값을 가집니다. n == 0 인 유일한 3-튜플입니다. (i, j, n)(i', j', n')가 리스트에서 인접한 3-튜플이고, 두 번째가 리스트의 마지막 3-튜플이 아니면 i+n < i' 또는 j+n < j'입니다; 즉, 인접 3-튜플은 항상 인접하지 않은 같은 블록을 나타냅니다.

>>> s = SequenceMatcher(None, "abxcd", "abcd")
>>> s.get_matching_blocks()
[Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)]
get_opcodes()

ab로 변환하는 방법을 설명하는 5-튜플의 리스트를 반환합니다. 각 튜플은 (tag, i1, i2, j1, j2) 형식입니다. 첫 번째 튜플은 i1 == j1 == 0 이고, 나머지 튜플에서는 i1이 이전 튜플의 i2와 같고, 마찬가지로 j1은 이전 j2와 같습니다.

tag 값은 문자열이고, 이런 의미입니다:

'replace'

a[i1:i2]b[j1:j2]로 치환해야 합니다.

'delete'

a[i1:i2]를 삭제해야 합니다. 이때 j1 == j2 임을 유의하십시오.

'insert'

b[j1:j2]a[i1:i1]에 삽입해야 합니다. 이때 i1 == i2 임을 유의하십시오.

'equal'

a[i1:i2] == b[j1:j2] (서브 시퀀스가 같습니다).

예를 들면:

>>> a = "qabxcd"
>>> b = "abycdf"
>>> s = SequenceMatcher(None, a, b)
>>> for tag, i1, i2, j1, j2 in s.get_opcodes():
...     print('{:7}   a[{}:{}] --> b[{}:{}] {!r:>8} --> {!r}'.format(
...         tag, i1, i2, j1, j2, a[i1:i2], b[j1:j2]))
delete    a[0:1] --> b[0:0]      'q' --> ''
equal     a[1:3] --> b[0:2]     'ab' --> 'ab'
replace   a[3:4] --> b[2:3]      'x' --> 'y'
equal     a[4:6] --> b[3:5]     'cd' --> 'cd'
insert    a[6:6] --> b[5:6]       '' --> 'f'
get_grouped_opcodes(n=3)

최대 n 줄의 문맥을 갖는 그룹의 제너레이터를 반환합니다.

get_opcodes()에서 반환된 그룹으로 출발해서, 이 메서드는 더 작은 변경 클러스터로 나누고, 변경 사항이 없는 중간 범위를 제거합니다.

그룹은 get_opcodes()와 같은 형식으로 반환됩니다.

ratio()

[0, 1]의 범위의 float로 시퀀스 유사성 척도를 돌려줍니다.

T가 두 시퀀스의 요소의 총 개수이고, M은 일치 개수일 때, 척도는 2.0*M / T입니다. 시퀀스가 같으면 1.0이고, 공통 요소가 없으면 0.0입니다.

get_matching_blocks()get_opcodes()가 아직 호출되지 않았으면, 계산하는 데 비용이 많이 듭니다. 이럴 때, quick_ratio()real_quick_ratio()를 먼저 시도하여 상한값을 얻을 수 있습니다.

quick_ratio()

비교적 빨리 ratio()의 상한을 반환합니다.

real_quick_ratio()

아주 빨리 ratio()의 상한을 반환합니다.

총 문자 수에 대한 일치 비율을 반환하는 세 가지 메서드는 서로 다른 수준의 근삿값 때문에 다른 결과를 줄 수 있습니다. 하지만 quick_ratio()real_quick_ratio()는 항상 최소한 ratio()만큼 큰 값을 줍니다:

>>> s = SequenceMatcher(None, "abcd", "bcde")
>>> s.ratio()
0.75
>>> s.quick_ratio()
0.75
>>> s.real_quick_ratio()
1.0

Examples

SequenceMatcher examples

이 예제에서는 공백을 “정크”로 간주하여, 두 문자열을 비교합니다:

>>> s = SequenceMatcher(lambda x: x == " ",
...                     "private Thread currentThread;",
...                     "private volatile Thread currentThread;")

ratio()는 [0, 1] 범위의 float를 반환하여, 시퀀스의 유사성을 측정합니다. 경험적으로, ratio() 값이 0.6 이상이면 시퀀스가 근접하게 일치함을 뜻합니다:

>>> print(round(s.ratio(), 3))
0.866

시퀀스가 일치하는 부분에만 관심이 있다면, get_matching_blocks()가 유용합니다:

>>> for block in s.get_matching_blocks():
...     print("a[%d] and b[%d] match for %d elements" % block)
a[0] and b[0] match for 8 elements
a[8] and b[17] match for 21 elements
a[29] and b[38] match for 0 elements

get_matching_blocks()에 의해 반환된 마지막 튜플은 항상 더미인 (len(a), len(b), 0)이며, 이는 마지막 튜플 요소(일치하는 요소의 수)가 0 인 유일한 경우입니다.

첫 번째 시퀀스를 두 번째 시퀀스로 변경하는 방법을 알고 싶다면, get_opcodes()를 사용하십시오:

>>> for opcode in s.get_opcodes():
...     print("%6s a[%d:%d] b[%d:%d]" % opcode)
 equal a[0:8] b[0:8]
insert a[8:8] b[8:17]
 equal a[8:29] b[17:38]

더 보기

Differ example

이 예제는 두 개의 텍스트를 비교합니다. 먼저 텍스트를 설정하는데, 줄 넘김 문자로 끝나는 개별 단일 줄 문자열의 시퀀스입니다 (이러한 시퀀스는 파일류 객체의 readlines() 메서드로도 얻을 수 있습니다):

>>> text1 = '''  1. Beautiful is better than ugly.
...   2. Explicit is better than implicit.
...   3. Simple is better than complex.
...   4. Complex is better than complicated.
... '''.splitlines(keepends=True)
>>> len(text1)
4
>>> text1[0][-1]
'\n'
>>> text2 = '''  1. Beautiful is better than ugly.
...   3.   Simple is better than complex.
...   4. Complicated is better than complex.
...   5. Flat is better than nested.
... '''.splitlines(keepends=True)

다음으로 Differ 객체의 인스턴스를 만듭니다:

>>> d = Differ()

Differ 객체의 인스턴스를 만들 때, 줄과 문자 “정크”를 필터링하는 함수를 전달할 수 있음에 유의하십시오. 자세한 내용은 Differ() 생성자를 참조하십시오.

마지막으로, 두 개를 비교합니다:

>>> result = list(d.compare(text1, text2))

result는 문자열의 리스트이므로, 예쁜 인쇄를 해봅시다:

>>> from pprint import pprint
>>> pprint(result)
['    1. Beautiful is better than ugly.\n',
 '-   2. Explicit is better than implicit.\n',
 '-   3. Simple is better than complex.\n',
 '+   3.   Simple is better than complex.\n',
 '?     ++\n',
 '-   4. Complex is better than complicated.\n',
 '?            ^                     ---- ^\n',
 '+   4. Complicated is better than complex.\n',
 '?           ++++ ^                      ^\n',
 '+   5. Flat is better than nested.\n']

여러 줄이 포함된 하나의 문자열로 만들면 이렇게 보입니다:

>>> import sys
>>> sys.stdout.writelines(result)
    1. Beautiful is better than ugly.
-   2. Explicit is better than implicit.
-   3. Simple is better than complex.
+   3.   Simple is better than complex.
?     ++
-   4. Complex is better than complicated.
?            ^                     ---- ^
+   4. Complicated is better than complex.
?           ++++ ^                      ^
+   5. Flat is better than nested.

difflib의 명령 줄 인터페이스

이 예제는 difflib를 사용하여 diff와 유사한 유틸리티를 만드는 방법을 보여줍니다.

""" Command-line interface to difflib.py providing diffs in four formats:

* ndiff:    lists every line and highlights interline changes.
* context:  highlights clusters of changes in a before/after format.
* unified:  highlights clusters of changes in an inline format.
* html:     generates side by side comparison with change highlights.

"""

import sys, os, difflib, argparse
import datetime as dt

def file_mtime(path):
    t = dt.datetime.fromtimestamp(os.stat(path).st_mtime,
                                  dt.timezone.utc)
    return t.astimezone().isoformat()

def main():

    parser = argparse.ArgumentParser()
    parser.add_argument('-c', action='store_true', default=False,
                        help='Produce a context format diff (default)')
    parser.add_argument('-u', action='store_true', default=False,
                        help='Produce a unified format diff')
    parser.add_argument('-m', action='store_true', default=False,
                        help='Produce HTML side by side diff '
                             '(can use -c and -l in conjunction)')
    parser.add_argument('-n', action='store_true', default=False,
                        help='Produce a ndiff format diff')
    parser.add_argument('-l', '--lines', type=int, default=3,
                        help='Set number of context lines (default 3)')
    parser.add_argument('fromfile')
    parser.add_argument('tofile')
    options = parser.parse_args()

    n = options.lines
    fromfile = options.fromfile
    tofile = options.tofile

    fromdate = file_mtime(fromfile)
    todate = file_mtime(tofile)
    with open(fromfile) as ff:
        fromlines = ff.readlines()
    with open(tofile) as tf:
        tolines = tf.readlines()

    if options.u:
        diff = difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)
    elif options.n:
        diff = difflib.ndiff(fromlines, tolines)
    elif options.m:
        diff = difflib.HtmlDiff().make_file(fromlines,tolines,fromfile,tofile,context=options.c,numlines=n)
    else:
        diff = difflib.context_diff(fromlines, tolines, fromfile, tofile, fromdate, todate, n=n)

    sys.stdout.writelines(diff)

if __name__ == '__main__':
    main()

ndiff 예제

d이 예제는 difflib.ndiff()를 사용하는 방법을 보여줍니다.

"""ndiff [-q] file1 file2
    or
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2

사람이 읽기 쉬운 파일 차이 보고서를 표준 출력에 인쇄합니다.  줄 간 및 줄 내 차이점이 모두 표시됩니다.
두 번째 형식에서는, 표준 입력의 ndiff 보고서로부터
 표준 출력으로 file1 (-r1) 이나 file2 (-r2) 를 다시 생성합니다.

첫 번째 형식에서는, -q ("quiet") 가 지정되지 않으면, 출력의 처음
두 줄은 다음과 같습니다

-: file1
+: file2

나머지 각 줄은 두 글자 코드로 시작합니다:

    "- "    file1 에 고유한 줄
    "+ "    file2 에 고유한 줄
    "  "    두 파일에 공통된 줄
    "? "    두 입력 파일 어디에도 없는 줄

"? " 로 시작하는 줄은 줄 내 차이로 눈을 안내하려고 시도하고,
두 입력 파일 어디에도 존재하지 않습니다.  이 줄들은 소스 파일에
탭 문자가 있을 경우 혼란스러울 수 있습니다.

첫 번째 파일은 "  " 나 "- " 로 시작하는 줄만 유지하고, 두 문자 접두사를
삭제해서 복구할 수 있습니다; ndiff 를 -r1 과 함께 사용하세요.

두 번째 파일도 비슷하게 복구할 수 있지만, "  " 과
"+ " 줄만 유지합니다; ndiff 를 -r2 와 함께 사용하세요; 또는, 유닉스에서, 두 번째 파일은
다음과 같이 출력을 파이핑하여 복구할 수 있습니다

    sed -n '/^[+ ] /s/^..//p'
"""

__version__ = 1, 7, 0

import difflib, sys

def fail(msg):
    out = sys.stderr.write
    out(msg + "\n\n")
    out(__doc__)
    return 0

# 파일을 열고 파일 객체를 반환합니다; 열 수 없으면
# 메시지를 출력하고 0을 반환합니다
def fopen(fname):
    try:
        return open(fname)
    except IOError as detail:
        return fail("couldn't open " + fname + ": " + str(detail))

# 두 파일을 열고 표준 출력으로 diff 를 뿌립니다; 문제가 있으면 거짓을 반환합니다
def fcompare(f1name, f2name):
    f1 = fopen(f1name)
    f2 = fopen(f2name)
    if not f1 or not f2:
        return 0

    a = f1.readlines(); f1.close()
    b = f2.readlines(); f2.close()
    for line in difflib.ndiff(a, b):
        print(line, end=' ')

    return 1

# args (보통 sys.argv[1:]) 를 쪼개고 비교합니다;
# 문제가 있으면 & 문제가 있을 때만 거짓을 반환합니다

def main(args):
    import getopt
    try:
        opts, args = getopt.getopt(args, "qr:")
    except getopt.error as detail:
        return fail(str(detail))
    noisy = 1
    qseen = rseen = 0
    for opt, val in opts:
        if opt == "-q":
            qseen = 1
            noisy = 0
        elif opt == "-r":
            rseen = 1
            whichfile = val
    if qseen and rseen:
        return fail("can't specify both -q and -r")
    if rseen:
        if args:
            return fail("no args allowed with -r option")
        if whichfile in ("1", "2"):
            restore(whichfile)
            return 1
        return fail("-r value must be 1 or 2")
    if len(args) != 2:
        return fail("need 2 filename args")
    f1name, f2name = args
    if noisy:
        print('-:', f1name)
        print('+:', f2name)
    return fcompare(f1name, f2name)

# 표준 입력에서 ndiff 출력을 읽고, file1 (which=='1') 이나
# file2 (which=='2') 를 표준 출력에 인쇄합니다

def restore(which):
    restored = difflib.restore(sys.stdin.readlines(), which)
    sys.stdout.writelines(restored)

if __name__ == '__main__':
    main(sys.argv[1:])