Skip to content

Commit bc977f4

Browse files
gh-118150: difflib: expose autojunk flag from SequenceMatcher to public methods and functions (GH-153959)
1 parent aaae15c commit bc977f4

5 files changed

Lines changed: 200 additions & 25 deletions

File tree

Doc/library/difflib.rst

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ Diff generation
128128

129129
The :class:`Differ` class has this constructor:
130130

131-
.. method:: __init__(linejunk=None, charjunk=None)
131+
.. method:: __init__(linejunk=None, charjunk=None, autojunk=True)
132132

133133
Optional keyword parameters *linejunk* and *charjunk* are for filter functions
134134
(or ``None``):
@@ -147,6 +147,14 @@ Diff generation
147147
:meth:`~SequenceMatcher.find_longest_match` method's *isjunk*
148148
parameter for an explanation.
149149

150+
Setting the optional *autojunk* argument to ``False`` will turn
151+
:ref:`automatic junk heuristic <difflib-junk>` off.
152+
153+
.. versionchanged:: 3.16
154+
Added keyword-only *autojunk* parameter.
155+
156+
157+
150158
:class:`Differ` objects are used (deltas generated) via a single method:
151159

152160

@@ -161,6 +169,8 @@ Diff generation
161169
printed as-is via the :meth:`~io.IOBase.writelines` method of a
162170
file-like object.
163171

172+
173+
164174
.. class:: HtmlDiff
165175

166176
This class can be used to create an HTML table (or a complete HTML file
@@ -176,7 +186,7 @@ Diff generation
176186
The constructor for this class is:
177187

178188

179-
.. method:: __init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)
189+
.. method:: __init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK, *, autojunk=True)
180190

181191
Initializes instance of :class:`HtmlDiff`.
182192

@@ -187,8 +197,15 @@ Diff generation
187197
broken and wrapped, defaults to ``None`` where lines are not wrapped.
188198

189199
*linejunk* and *charjunk* are optional keyword arguments passed into :func:`ndiff`
190-
(used by :class:`HtmlDiff` to generate the side by side HTML differences). See
191-
:func:`ndiff` documentation for argument default values and descriptions.
200+
(used by :class:`HtmlDiff` to generate the side by side HTML differences).
201+
See :func:`ndiff` documentation for argument default values and descriptions.
202+
203+
Setting the optional *autojunk* argument to ``False`` will turn
204+
:ref:`automatic junk heuristic <difflib-junk>` off.
205+
206+
.. versionchanged:: 3.16
207+
Added keyword-only *autojunk* parameter.
208+
192209

193210
The following methods are public:
194211

@@ -231,7 +248,7 @@ Diff generation
231248

232249

233250

234-
.. function:: context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')
251+
.. function:: context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n', *, autojunk=True)
235252

236253
Compare *a* and *b* (lists of strings); return a delta (a :term:`generator`
237254
generating the delta lines) in context diff format.
@@ -277,8 +294,14 @@ Diff generation
277294

278295
See :ref:`difflib-interface` for a more detailed example.
279296

297+
Setting the optional *autojunk* argument to ``False`` will turn
298+
:ref:`automatic junk heuristic <difflib-junk>` off.
299+
300+
.. versionchanged:: 3.16
301+
Added keyword-only *autojunk* parameter.
280302

281-
.. function:: get_close_matches(word, possibilities, n=3, cutoff=0.6)
303+
304+
.. function:: get_close_matches(word, possibilities, n=3, cutoff=0.6, *, autojunk=True)
282305

283306
Return a list of the best "good enough" matches. *word* is a sequence for which
284307
close matches are desired (typically a string), and *possibilities* is a list of
@@ -290,6 +313,9 @@ Diff generation
290313
Optional argument *cutoff* (default ``0.6``) is a float in the range [0, 1].
291314
Possibilities that don't score at least that similar to *word* are ignored.
292315

316+
Setting the optional *autojunk* argument to ``False`` will turn
317+
:ref:`automatic junk heuristic <difflib-junk>` off.
318+
293319
The best (no more than *n*) matches among the possibilities are returned in a
294320
list, sorted by similarity score, most similar first.
295321

@@ -303,8 +329,11 @@ Diff generation
303329
>>> get_close_matches('accept', keyword.kwlist)
304330
['except']
305331

332+
.. versionchanged:: 3.16
333+
Added keyword-only *autojunk* parameter.
334+
306335

307-
.. function:: ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK)
336+
.. function:: ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK, *, autojunk=True)
308337

309338
Compare *a* and *b* (lists of strings); return a :class:`Differ`\ -style
310339
delta (a :term:`generator` generating the delta lines).
@@ -325,6 +354,11 @@ Diff generation
325354
function :func:`IS_CHARACTER_JUNK`, which filters out whitespace characters (a
326355
blank or tab; it's a bad idea to include newline in this!).
327356

357+
Setting the optional *autojunk* argument to ``False`` will turn
358+
:ref:`automatic junk heuristic <difflib-junk>` off.
359+
360+
Example:
361+
328362
>>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True),
329363
... 'ore\ntree\nemu\n'.splitlines(keepends=True))
330364
>>> print(''.join(diff), end="")
@@ -338,6 +372,9 @@ Diff generation
338372
+ tree
339373
+ emu
340374

375+
.. versionchanged:: 3.16
376+
Added keyword-only *autojunk* parameter.
377+
341378

342379
.. function:: restore(sequence, which)
343380

@@ -362,7 +399,7 @@ Diff generation
362399
emu
363400

364401

365-
.. function:: unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n', *, color=False)
402+
.. function:: unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n', *, autojunk=True, color=False)
366403

367404
Compare *a* and *b* (lists of strings); return a delta (a :term:`generator`
368405
generating the delta lines) in unified diff format.
@@ -410,6 +447,12 @@ Diff generation
410447
.. versionchanged:: 3.15
411448
Added the *color* parameter.
412449

450+
Setting the optional *autojunk* argument to ``False`` will turn
451+
:ref:`automatic junk heuristic <difflib-junk>` off.
452+
453+
.. versionchanged:: 3.16
454+
Added keyword-only *autojunk* parameter.
455+
413456

414457
.. function:: diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')
415458

Doc/whatsnew/3.16.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,37 @@ curses
304304
(Contributed by Serhiy Storchaka in :gh:`133031`.)
305305

306306

307+
ctypes
308+
------
309+
310+
* Add :func:`ctypes.util.struct` for generating :class:`~ctypes.Structure` types
311+
from an annotation-based syntax, similar to how the :mod:`dataclasses` module
312+
is used.
313+
(Contributed by Peter Bierma in :gh:`104533`.)
314+
* Add :func:`ctypes.util.wrap_dll_function` for generating function pointers
315+
through a function signature.
316+
(Contributed by Peter Bierma in :gh:`153903`.)
317+
318+
319+
concurrent.futures
320+
------------------
321+
322+
* The iterator returned by :meth:`concurrent.futures.Executor.map` is no longer
323+
automatically closed if a function call raises an exception.
324+
Use method :meth:`!close` to explicitly close the iterator.
325+
(Contributed by xzmeng and Serhiy Storchaka in :gh:`108518`.)
326+
327+
328+
difflib
329+
-------
330+
331+
* Expose optional ``autojunk`` parameter from :class:`difflib.SequenceMatcher`
332+
to public functions and class methods in :mod:`difflib`,
333+
allowing to modify behavior of automatic junk heuristic in this module
334+
in higher public class methods and functions.
335+
(Contributed by Tomasz Kazimierczak in :gh:`118150`)
336+
337+
307338
encodings
308339
---------
309340

Lib/difflib.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ def real_quick_ratio(self):
664664
__class_getitem__ = classmethod(GenericAlias)
665665

666666

667-
def get_close_matches(word, possibilities, n=3, cutoff=0.6):
667+
def get_close_matches(word, possibilities, n=3, cutoff=0.6, *, autojunk=True):
668668
"""Use SequenceMatcher to return list of the best "good enough" matches.
669669
670670
word is a sequence for which close matches are desired (typically a
@@ -698,7 +698,7 @@ def get_close_matches(word, possibilities, n=3, cutoff=0.6):
698698
if not 0.0 <= cutoff <= 1.0:
699699
raise ValueError("cutoff must be in [0.0, 1.0]: %r" % (cutoff,))
700700
result = []
701-
s = SequenceMatcher()
701+
s = SequenceMatcher(autojunk=autojunk)
702702
s.set_seq2(word)
703703
for x in possibilities:
704704
s.set_seq1(x)
@@ -810,7 +810,7 @@ class Differ:
810810
+ 5. Flat is better than nested.
811811
"""
812812

813-
def __init__(self, linejunk=None, charjunk=None):
813+
def __init__(self, linejunk=None, charjunk=None, *, autojunk=True):
814814
"""
815815
Construct a text differencer, with optional filters.
816816
@@ -828,10 +828,13 @@ def __init__(self, linejunk=None, charjunk=None):
828828
module-level function `IS_CHARACTER_JUNK` may be used to filter out
829829
whitespace characters (a blank or tab; **note**: bad idea to include
830830
newline in this!). Use of IS_CHARACTER_JUNK is recommended.
831+
- `autojunk`: automatic junk diff heuristic
832+
(refer to :class:`SequenceMatcher` for specifics).
831833
"""
832834

833835
self.linejunk = linejunk
834836
self.charjunk = charjunk
837+
self.autojunk = autojunk
835838

836839
def compare(self, a, b):
837840
r"""
@@ -859,7 +862,7 @@ def compare(self, a, b):
859862
+ emu
860863
"""
861864

862-
cruncher = SequenceMatcher(self.linejunk, a, b)
865+
cruncher = SequenceMatcher(self.linejunk, a, b, autojunk=self.autojunk)
863866
for tag, alo, ahi, blo, bhi in cruncher.get_opcodes():
864867
if tag == 'replace':
865868
g = self._fancy_replace(a, alo, ahi, b, blo, bhi)
@@ -920,7 +923,7 @@ def _fancy_replace(self, a, alo, ahi, b, blo, bhi):
920923
# Later, more pathological cases prompted removing recursion
921924
# entirely.
922925
cutoff = 0.74999
923-
cruncher = SequenceMatcher(self.charjunk)
926+
cruncher = SequenceMatcher(self.charjunk, autojunk=self.autojunk)
924927
crqr = cruncher.real_quick_ratio
925928
cqr = cruncher.quick_ratio
926929
cr = cruncher.ratio
@@ -1099,7 +1102,7 @@ def _format_range_unified(start, stop):
10991102
return '{},{}'.format(beginning, length)
11001103

11011104
def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
1102-
tofiledate='', n=3, lineterm='\n', *, color=False):
1105+
tofiledate='', n=3, lineterm='\n', *, autojunk=True, color=False):
11031106
r"""
11041107
Compare two sequences of lines; generate the delta as a unified diff.
11051108
@@ -1120,6 +1123,9 @@ def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
11201123
'git diff --color'. Even if enabled, it can be
11211124
controlled using environment variables such as 'NO_COLOR'.
11221125
1126+
Set `autojunk` to False if you don't want automated junk heuristic.
1127+
See details in :class:`SequenceMatcher.
1128+
11231129
The unidiff format normally has a header for filenames and modification
11241130
times. Any or all of these may be specified using strings for
11251131
'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
@@ -1150,7 +1156,7 @@ def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
11501156

11511157
_check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm)
11521158
started = False
1153-
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
1159+
for group in SequenceMatcher(None, a, b, autojunk=autojunk).get_grouped_opcodes(n):
11541160
if not started:
11551161
started = True
11561162
fromdate = '\t{}'.format(fromfiledate) if fromfiledate else ''
@@ -1193,7 +1199,7 @@ def _format_range_context(start, stop):
11931199

11941200
# See http://www.unix.org/single_unix_specification/
11951201
def context_diff(a, b, fromfile='', tofile='',
1196-
fromfiledate='', tofiledate='', n=3, lineterm='\n'):
1202+
fromfiledate='', tofiledate='', n=3, lineterm='\n', *, autojunk=True):
11971203
r"""
11981204
Compare two sequences of lines; generate the delta as a context diff.
11991205
@@ -1216,6 +1222,10 @@ def context_diff(a, b, fromfile='', tofile='',
12161222
The modification times are normally expressed in the ISO 8601 format.
12171223
If not specified, the strings default to blanks.
12181224
1225+
The kwarg `autojunk` sets up automated junk heuristic with
1226+
:class:`SequenceMatcher`, which is used under the hood in this function.
1227+
See documentation of :class:`SequenceMatcher` for details.
1228+
12191229
Example:
12201230
12211231
>>> print(''.join(context_diff('one\ntwo\nthree\nfour\n'.splitlines(True),
@@ -1239,7 +1249,7 @@ def context_diff(a, b, fromfile='', tofile='',
12391249
_check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm)
12401250
prefix = dict(insert='+ ', delete='- ', replace='! ', equal=' ')
12411251
started = False
1242-
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
1252+
for group in SequenceMatcher(None, a, b, autojunk=autojunk).get_grouped_opcodes(n):
12431253
if not started:
12441254
started = True
12451255
fromdate = '\t{}'.format(fromfiledate) if fromfiledate else ''
@@ -1321,7 +1331,7 @@ def decode(s):
13211331
for line in lines:
13221332
yield line.encode('ascii', 'surrogateescape')
13231333

1324-
def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK):
1334+
def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK, *, autojunk=True):
13251335
r"""
13261336
Compare `a` and `b` (lists of strings); return a `Differ`-style delta.
13271337
@@ -1339,6 +1349,8 @@ def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK):
13391349
whitespace characters (a blank or tab; note: it's a bad idea to
13401350
include newline in this!).
13411351
1352+
- autojunk: automatic junk heuristic - refer to :class:`SequenceMatcher` for details
1353+
13421354
Tools/scripts/ndiff.py is a command-line front-end to this function.
13431355
13441356
Example:
@@ -1356,10 +1368,10 @@ def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK):
13561368
+ tree
13571369
+ emu
13581370
"""
1359-
return Differ(linejunk, charjunk).compare(a, b)
1371+
return Differ(linejunk, charjunk, autojunk=autojunk).compare(a, b)
13601372

13611373
def _mdiff(fromlines, tolines, context=None, linejunk=None,
1362-
charjunk=IS_CHARACTER_JUNK):
1374+
charjunk=IS_CHARACTER_JUNK, *, autojunk=True):
13631375
r"""Returns generator yielding marked up from/to side by side differences.
13641376
13651377
Arguments:
@@ -1369,6 +1381,7 @@ def _mdiff(fromlines, tolines, context=None, linejunk=None,
13691381
if None, all from/to text lines will be generated.
13701382
linejunk -- passed on to ndiff (see ndiff documentation)
13711383
charjunk -- passed on to ndiff (see ndiff documentation)
1384+
autojunk -- passed on to ndiff (see ndiff documentation)
13721385
13731386
This function returns an iterator which returns a tuple:
13741387
(from line tuple, to line tuple, boolean flag)
@@ -1398,7 +1411,7 @@ def _mdiff(fromlines, tolines, context=None, linejunk=None,
13981411
change_re = re.compile(r'(\++|\-+|\^+)')
13991412

14001413
# create the difference iterator to generate the differences
1401-
diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk)
1414+
diff_lines_iterator = ndiff(fromlines, tolines, linejunk, charjunk, autojunk=autojunk)
14021415

14031416
def _make_line(lines, format_key, side, num_lines=[0,0]):
14041417
"""Returns line of text with user's change markup and line formatting.
@@ -1738,21 +1751,22 @@ class HtmlDiff(object):
17381751
_default_prefix = 0
17391752

17401753
def __init__(self,tabsize=8,wrapcolumn=None,linejunk=None,
1741-
charjunk=IS_CHARACTER_JUNK):
1754+
charjunk=IS_CHARACTER_JUNK, *, autojunk=True):
17421755
"""HtmlDiff instance initializer
17431756
17441757
Arguments:
17451758
tabsize -- tab stop spacing, defaults to 8.
17461759
wrapcolumn -- column number where lines are broken and wrapped,
17471760
defaults to None where lines are not wrapped.
1748-
linejunk,charjunk -- keyword arguments passed into ndiff() (used by
1761+
linejunk, charjunk, autojunk -- keyword arguments passed into ndiff() (used by
17491762
HtmlDiff() to generate the side by side HTML differences). See
17501763
ndiff() documentation for argument default values and descriptions.
17511764
"""
17521765
self._tabsize = tabsize
17531766
self._wrapcolumn = wrapcolumn
17541767
self._linejunk = linejunk
17551768
self._charjunk = charjunk
1769+
self._autojunk = autojunk
17561770

17571771
def make_file(self, fromlines, tolines, fromdesc='', todesc='',
17581772
context=False, numlines=5, *, charset='utf-8'):
@@ -2026,7 +2040,7 @@ def make_table(self,fromlines,tolines,fromdesc='',todesc='',context=False,
20262040
else:
20272041
context_lines = None
20282042
diffs = _mdiff(fromlines,tolines,context_lines,linejunk=self._linejunk,
2029-
charjunk=self._charjunk)
2043+
charjunk=self._charjunk, autojunk=self._autojunk)
20302044

20312045
# set up iterator to wrap lines that exceed desired width
20322046
if self._wrapcolumn:

0 commit comments

Comments
 (0)