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 various formats, including HTML and context and unified diffs -- formats produced by tools like diff and git diff.
مقایسهها با استفاده از الگوریتم تطبیقی انجام میشوند که در SequenceMatcher پیادهسازی شده است — کلاسی انعطافپذیر برای مقایسهی جفتهایی از دنبالههای هر نوعی، نه فقط متن، مشروط بر اینکه عناصر دنباله hashable باشند.
هیوریستیک زباله (Junk heuristic)¶
difflib از یک اکتشافی junk استفاده میکند: برخی آیتمها بهعنوان junk تلقی میشوند و هنگام جستوجوی شباهتها نادیده گرفته میشوند. در حالت ایدهآل، اینها آیتمهای بیاهمیت یا رایج هستند، مانند سطرهای خالی یا فضای سفید.
این روش اکتشافی میتواند سرعت الگوریتم را افزایش دهد (زیرا تعداد ترکیبهای ممکن را کاهش میدهد) و میتواند نتایجی تولید کند که برای انسان قابلفهمتر هستند (معمولاً شکستن در فضای خالی). اما همچنین میتواند باعث ایجاد موارد آسیبشناختی شود:
آیتمهای هرزی که بهصورت نامناسب انتخاب شدهاند، میتوانند منجر به نتیجهای بهطور غیرمنتظرهای بزرگ (اما همچنان صحیح) شوند.
روش اکتشافی پیشفرض نامتقارن است: هنگام تعیین اینکه چه چیزی آشغال محسوب میشود، فقط دنبالهی دوم بررسی میشود؛ بنابراین مقایسهی A با B میتواند نتایج متفاوتی نسبت به مقایسهی B با A و معکوس کردن نتیجه بدهد.
بهطور پیشفرض، اگر طول دومین دنباله ورودی حداقل ۲۰۰ آیتم باشد، آیتمهایی که بیش از ۱٪ آن را تشکیل میدهند، آشغال در نظر گرفته میشوند.
Depending on your data, you should consider turning this heuristic off
(setting SequenceMatcher's autojunk argument to False)
or tuning it (using the isjunk argument, perhaps to one of the
predefined functions).
الگوریتم difflib¶
الگوریتم استفادهشده در SequenceMatcher از الگوریتمی که در اواخر دههی ۱۹۸۰ توسط رتکلیف و اوبرزهلپ با نام اغراقآمیز «تطبیق الگوی گشتالت (gestalt pattern matching)» منتشر شد، قدیمیتر است و کمی پیشرفتهتر از آن است. ایده این است که طولانیترین زیردنبالهی پیوستهی مشترک بین هر دو ورودی پیدا شود، سپس بخشهای دنبالهها در سمت چپ و راست زیردنبالهی منطبق بهصورت بازگشتی پردازش شوند.
همچنین ملاحظه نمائید
- تطبیق الگو: رویکرد گشتالت
بحثی دربارهی یک الگوریتم مشابه توسط John W. Ratcliff و D. E. Metzener. این مطلب در ژوئیهی ۱۹۸۸ در مجلهی Dr. Dobb's Journal منتشر شد.
بهعنوان گسترشی بر الگوریتم Ratcliff و Obershelp، difflib به دنبال طولانیترین زیردنبالهی پیوستهی عاری از هرز میگردد. برای جزئیات، بخش هیوریستیک زباله (Junk heuristic) را ببینید.
زمانسنجی
الگوریتم پایهی Ratcliff-Obershelp در بدترین حالت دارای زمان مکعبی و در حالت مورد انتظار دارای زمان مربعی است. الگوریتم difflib در بدترین حالت دارای زمان مربعی است و رفتار آن در حالت مورد انتظار بهشکل پیچیدهای به تعداد عناصر مشترک دنبالهها وابسته است؛ زمان بهترین حالت خطی است.
تولید تفاوت (diff)¶
- class difflib.Differ¶
این یک کلاس برای مقایسهی دنبالههایی از سطرهای متن و تولید تفاوتها یا دلتاهای قابلخواندن برای انسان است. Differ از
SequenceMatcherهم برای مقایسهی دنبالههایی از سطرهای و هم برای مقایسهی دنبالههایی از نویسهها درون سطرهای مشابه (تقریباً منطبق) استفاده میکند.هر خط از دلتای
Differبا کدی دو نویسهای آغاز میشود:کد
معنی
'- 'خط مختص دنبالهی ۱
'+ 'خط یکتا برای دنباله ۲
' 'خط مشترک بین هر دو دنباله
'? 'خط در هیچیک از دنبالههای ورودی وجود ندارد
سطرهایی که با '
?' شروع میشوند، تلاش میکنند توجه را به تفاوتهای درونخطی جلب کنند، و در هیچیک از دنبالههای ورودی وجود نداشتند. این سطرهای ممکن است در صورتی گیجکننده باشند که دنبالهها حاوی نویسههای فضای خالی مانند فاصلهها، تبها یا سطرهای جدید باشند.توجه داشته باشید که دلتاهای تولیدشده توسط
Differادعایی ندارند که تفاوتهای کمینه باشند. برعکس، تفاوتهای کمینه اغلب برای انسانها برخلاف شهود هستند، زیرا در هر جایی که ممکن باشد همگام میشوند، گاهی در تطابقهای تصادفی که ۱۰۰ صفحه با یکدیگر فاصله دارند. محدود کردن نقاط همگامسازی به تطابقهای متوالی، تا حدی مفهوم محلی بودن را حفظ میکند، هرچند گاهی به بهای تولید یک تفاوت طولانیتر تمام میشود.کلاس
Differاین سازنده را دارد:- __init__(linejunk=None, charjunk=None)¶
پارامترهای کلیدواژهای اختیاری linejunk و charjunk برای توابع فیلتر (یا
None) هستند:linejunk: تابعی که تنها یک آرگومان رشتهای میپذیرد و اگر رشته هرز باشد، مقدار true را برمیگرداند. مقدار پیشفرض
Noneاست، به این معنا که هیچ خطی هرز در نظر گرفته نمیشود.charjunk: تابعی که یک آرگومان تکنویسهای (رشتهای به طول ۱) میپذیرد و اگر نویسه هرز باشد، مقدار درست برمیگرداند. مقدار پیشفرض
Noneاست، یعنی هیچ نویسهای هرز در نظر گرفته نمیشود.این توابع پالایش زباله (junk-filtering) سرعت تطبیق برای یافتن تفاوتها را افزایش میدهند و باعث نمیشوند هیچ خط یا نویسهی متفاوتی نادیده گرفته شود. برای توضیح، شرح پارامتر isjunk متد
find_longest_match()را بخوانید.
از اشیای
Differاز طریق یک متد واحد استفاده میشود (دلتاها تولید میشوند):- compare(a, b)¶
دو دنباله از سطرها را مقایسه میکند و دلتا (دنبالهای از سطرهای) را تولید میکند.
هر دنباله باید شامل رشتههای تکسطری جداگانهای باشد که به خط جدید ختم میشوند. چنین دنبالههایی را میتوان از متد
readlines()اشیاء شبهپرونده بهدست آورد. دلتای تولیدشده نیز از رشتههای ختمشده به خط جدید تشکیل شده است، که آمادهاند بههمینصورت از طریق متدwritelines()یک شیء شبهپرونده چاپ شوند.
- class difflib.HtmlDiff¶
از این کلاس میتوان برای ایجاد یک جدول HTML (یا یک پرونده HTML کامل شامل جدول) استفاده کرد که مقایسهی کنار هم و خطبهخط متن را با برجستهسازی تغییرات بینخطی و درونخطی نشان میدهد. این جدول را میتوان در حالت تفاوت کامل یا زمینهای تولید کرد.
هشدار
سطرهای جدید پایانی پیش از diff حذف میشوند، بنابراین نتیجه ممکن است ناقص باشد. برای جزئیات gh-71896 را ببینید.
سازندهی این کلاس عبارت است از:
- __init__(tabsize=8, wrapcolumn=None, linejunk=None, charjunk=IS_CHARACTER_JUNK)¶
نمونهای از
HtmlDiffرا مقداردهی اولیه میکند.tabsize یک آرگومان کلیدواژهای اختیاری برای تعیین فاصلهگذاری تب است و مقدار پیشفرض آن
8است.wrapcolumn یک کلیدواژه اختیاری برای مشخص کردن شماره ستونی است که سطرهای در آن شکسته و به خط بعد منتقل میشوند، و بهطور پیشفرض
Noneاست که در این حالت سطرها به خط بعد منتقل نمیشوند.linejunk و charjunk آرگومانهای کلیدواژهای اختیاری هستند که به
ndiff()ارسال میشوند (توسطHtmlDiffبرای تولید تفاوتهای HTML بهصورت کنار هم استفاده میشود). برای مقادیر پیشفرض و توضیحات آرگومانها، مستنداتndiff()را ببینید.
متدهای زیر عمومی هستند:
- make_file(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5, *, charset='utf-8')¶
fromlines و tolines (فهرستهایی از رشتهها) را مقایسه میکند و رشتهای را برمیگرداند که یک پرونده HTML کامل حاوی جدولی است که تفاوتها را بهصورت خطبهخط نشان میدهد و تغییرات بینخطی و درونخطی در آن برجسته شدهاند.
fromdesc و todesc آرگومانهای کلیدواژهای اختیاری هستند که برای مشخص کردن رشتههای سرآیند ستون پرونده مبدأ/مقصد استفاده میشوند (مقدار پیشفرض هر دو یک رشته خالی است).
context و numlines هر دو آرگومان کلیدواژهای اختیاری هستند. هنگامی که باید تفاوتهای همراه با زمینه نمایش داده شوند، context را روی
Trueتنظیم کنید، در غیر این صورت مقدار پیشفرضFalseاست تا پروندههای کامل نمایش داده شوند. مقدار پیشفرض numlines برابر5است. وقتی context برابرTrueاست، numlines تعداد سطرهای زمینهای را که تفاوتهای برجستهشده را در بر میگیرند، کنترل میکند. وقتی context برابرFalseاست، numlines تعداد سطرهایی را که هنگام استفاده از ابرپیوندهای «next» پیش از یک تفاوت برجستهشده نمایش داده میشوند، کنترل میکند (تنظیم آن روی صفر باعث میشود ابرپیوندهای «next» تفاوت برجستهشده بعدی را بدون هیچ زمینه پیشین در بالای مرورگر قرار دهند).توجه
fromdesc و todesc بهعنوان HTML خنثینشده تفسیر میشوند و باید هنگام دریافت ورودی از منابع نامطمئن، بهدرستی خنثی شوند.
تغییر یافته در نسخهی 3.5: آرگومان فقط کلیدواژهای charset اضافه شد. مجموعهنویسه پیشفرض سند HTML از
'ISO-8859-1'به'utf-8'تغییر کرد.
- make_table(fromlines, tolines, fromdesc='', todesc='', context=False, numlines=5)¶
fromlines و tolines (فهرستهایی از رشتهها) را مقایسه میکند و رشتهای را برمیگرداند که یک جدول HTML کامل است و تفاوتهای خطبهخط را با تغییرات بینخطی و درونخطی برجستهشده نشان میدهد.
آرگومانهای این متد همان آرگومانهای متد
make_file()هستند.
- difflib.context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n')¶
a و b (فهرستهایی از رشتهها) را مقایسه میکند؛ دلتایی (یک تولیدگر که سطرهای دلتا را تولید میکند) را در قالب context diff برمیگرداند.
دیفهای زمینهای (context diffs) روشی فشرده برای نمایش تنها سطرهایی هستند که تغییر کردهاند، بهعلاوه چند خط زمینه. تغییرها به سبک پیش/پس نمایش داده میشوند. تعداد سطرهای زمینه توسط n تنظیم میشود که مقدار پیشفرض آن ۳ است.
بهطور پیشفرض، سطرهای کنترلی تفاوت (diff) (آنهایی که
***یا---دارند) با یک نویسهی خط جدید در انتها ایجاد میشوند. این موضوع مفید است تا ورودیهای ایجادشده ازio.IOBase.readlines()به تفاوتهایی منجر شوند که برای استفاده باio.IOBase.writelines()مناسب باشند، زیرا هم ورودیها و هم خروجیها دارای نویسهی خط جدید در انتها هستند.برای ورودیهایی که خط جدید پایانی ندارند، آرگومان lineterm را روی
""تنظیم کنید تا خروجی بهطور یکنواخت فاقد خط جدید باشد.قالب diff زمینهای (context diff) معمولاً دارای سرآیندی برای نام پروندهها و زمانهای تغییر است. هر یک یا همهی این موارد را میتوان با استفاده از رشتههایی برای fromfile، tofile، fromfiledate و tofiledate مشخص کرد. زمانهای تغییر معمولاً در قالب 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 دنبالهای است که تطابقهای نزدیک برای آن مطلوب است (معمولاً یک رشته)، و possibilities فهرستی از دنبالههاست که word با آنها تطبیق داده میشود (معمولاً فهرستی از رشتهها).
آرگومان اختیاری n (پیشفرض
3) حداکثر تعداد تطابقهای نزدیکی است که بازگردانده میشود؛ n باید بزرگتر از0باشد.آرگومان اختیاری cutoff (پیشفرض
0.6) یک عدد اعشاری در بازه [۰، ۱] است. گزینههایی که امتیاز شباهت آنها به word حداقل این میزان نباشد، نادیده گرفته میشوند.بهترین تطابقها (حداکثر 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)¶
a و b (فهرستهایی از رشتهها) را مقایسه میکند؛ یک دلتا به سبک
Differرا برمیگرداند (یک تولیدگر که سطرهای دلتا را تولید میکند).پارامترهای کلیدواژهای اختیاری linejunk و charjunk توابع پالایشگر (یا
None) هستند:linejunk: تابعی که تنها یک آرگومان رشتهای را میپذیرد، و اگر رشته هرز باشد مقدار درست، و در غیر این صورت مقدار نادرست را برمیگرداند. مقدار پیشفرض
Noneاست. همچنین یک تابع در سطح ماژولIS_LINE_JUNK()وجود دارد که سطرهایی را که نویسههای قابل مشاهده ندارند، بهجز حداکثر یک نویسه هش ('#')، فیلتر میکند -- با این حال، کلاس زیربناییSequenceMatcherتحلیلی پویا انجام میدهد تا مشخص کند کدام سطرهای آنقدر پرتکرار هستند که نویز محسوب میشوند، و این معمولاً بهتر از استفاده از این تابع عمل میکند.charjunk: تابعی که یک نویسه (رشتهای به طول ۱) میپذیرد، و اگر نویسه هرز (junk) باشد، مقداری برمیگرداند، و در غیر این صورت false برمیگرداند. مقدار پیشفرض، تابع
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)¶
یکی از دو دنبالهای را که دلتایی (delta) تولید کردهاند، برمیگرداند.
با داشتن یک دنباله تولیدشده توسط
Differ.compare()یاndiff()، سطرهایی را که از پرونده ۱ یا ۲ آمدهاند (پارامتر 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')¶
a و b (فهرستهایی از رشتهها) را مقایسه میکند؛ یک دلتا (یک تولیدگر که سطرهای دلتا را تولید میکند) را در قالب unified diff بازمیگرداند.
تفاوتهای یکپارچه (unified diffs) روشی فشرده برای نمایش تنها سطرهایی که تغییر کردهاند بههمراه چند خط زمینه هستند. تغییرات بهصورت درونخطی نمایش داده میشوند (بهجای بلوکهای جداگانهی قبل/بعد). تعداد سطرهای زمینه با n تنظیم میشود که مقدار پیشفرض آن ۳ است.
بهطور پیشفرض، سطرهای کنترلی diff (آنهایی که دارای
---،+++یا@@هستند) با یک خط جدید در پایان ایجاد میشوند. این موضوع مفید است تا ورودیهایی که ازio.IOBase.readlines()ایجاد شدهاند، منجر به diffهایی شوند که برای استفاده باio.IOBase.writelines()مناسب هستند، زیرا هم ورودیها و هم خروجیها دارای خط جدید در پایان هستند.برای ورودیهایی که خط جدید پایانی ندارند، آرگومان lineterm را روی
""تنظیم کنید تا خروجی بهطور یکنواخت فاقد خط جدید باشد.قالب unified diff معمولاً دارای سرآیندی برای نام پروندهها و زمانهای تغییر است. هرکدام یا همهی این موارد را میتوان با استفاده از رشتههایی برای fromfile، tofile، fromfiledate و tofiledate مشخص کرد. زمانهای تغییر معمولاً در قالب 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')¶
a و b (فهرستهایی از اشیای bytes) را با استفاده از dfunc مقایسه میکند؛ دنبالهای از سطرهای دلتا (که آنها نیز bytes هستند) را در قالبی که dfunc برمیگرداند تولید میکند. dfunc باید یک شیء فراخوانیپذیر باشد، معمولاً یا
unified_diff()یاcontext_diff().به شما امکان میدهد دادههایی با کدگذاری ناشناخته یا ناهماهنگ را مقایسه کنید. همهی ورودیها بهجز n باید اشیای bytes باشند، نه str. با تبدیل بدون اتلاف همهی ورودیها (بهجز n) به str و فراخوانی
dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm)کار میکند. سپس خروجی dfunc دوباره به bytes تبدیل میشود، بنابراین سطرهای دلتایی که دریافت میکنید، همان کدگذاریهای ناشناخته/ناهماهنگ a و b را دارند.اضافه شده در نسخهی 3.5.
توابع تعریف زباله¶
اشیای SequenceMatcher¶
- class difflib.SequenceMatcher(isjunk=None, a='', b='', autojunk=True)¶
آرگومان اختیاری isjunk باید
None(پیشفرض) یا تابعی با یک آرگومان باشد که عنصری از یک دنباله را میگیرد و اگر و تنها اگر آن عنصر «زائد» باشد و باید نادیده گرفته شود، مقدار درست برمیگرداند. پاس دادنNoneبرای isjunk معادل پاس دادنlambda x: Falseاست؛ به عبارت دیگر، هیچ عنصری نادیده گرفته نمیشود. برای مثال، این را پاس دهید:lambda x: x in " \t"
اگر سطرها را بهعنوان دنبالههایی از نویسهها مقایسه میکنید و نمیخواهید بر اساس فاصلهها یا تبهای سخت همگامسازی کنید.
آرگومانهای اختیاری a و b دنبالههایی هستند که با هم مقایسه میشوند؛ مقدار پیشفرض هر دو، رشتههای خالی است. عناصر هر دو دنباله باید hashable باشند.
میتوانید از آرگومان اختیاری autojunk برای غیرفعال کردن هیوریستیک خودکار هرز استفاده کنید.
تغییر یافته در نسخهی 3.2: پارامتر autojunk اضافه شد.
اشیای SequenceMatcher سه ویژگی داده دارند: bjunk مجموعهای از عناصر b است که isjunk برای آنها
Trueاست؛ bpopular مجموعهای از عناصر غیرزائد است که توسط اکتشافی، محبوب در نظر گرفته میشوند (اگر غیرفعال نشده باشد)؛ b2j دیکشنری است که عناصر باقیماندهی b را به فهرستی از موقعیتهایی که در آنها ظاهر میشوند نگاشت میکند. هر سه هر زمان که b باset_seqs()یاset_seq2()بازنشانی شود، بازنشانی میشوند.اضافه شده در نسخهی 3.2: ویژگیهای bjunk و bpopular.
اشیای
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()(i, j, k)را برمیگرداند بهطوری کهa[i:i+k]برابر باb[j: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 ارائه شده باشد، ابتدا طولانیترین بلوک منطبق همانگونه که در بالا توضیح داده شد تعیین میشود، اما با این محدودیت اضافی که هیچ عنصر هرز (junk) در بلوک وجود نداشته باشد. سپس آن بلوک تا حد ممکن با تطبیق (فقط) عناصر هرز در هر دو طرف گسترش مییابد. بنابراین بلوک حاصل هرگز بر عناصر هرز منطبق نمیشود، مگر آنکه عناصر هرز یکسان بهطور تصادفی مجاور یک تطبیق معنادار باشند.
این همان مثال پیشین است، اما فاصلهها را هرز (junk) در نظر میگیریم. این امر مانع از آن میشود که
' 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)برگردانده میشود.این متد یک named tuple با قالب
Match(a, b, size)برمیگرداند.تغییر یافته در نسخهی 3.9: آرگومانهای پیشفرض اضافه شد.
- get_matching_blocks()¶
فهرستی از سهتاییها را برمیگرداند که زیردنبالههای منطبق غیرهمپوشان را توصیف میکنند. هر سهتایی به شکل
(i, j, n)است و به این معناست کهa[i:i+n] == b[j:j+n]. سهتاییها از نظر i و j بهطور یکنواخت صعودی هستند.آخرین سهتایی ساختگی است و مقدار
(len(a), len(b), 0)را دارد. این تنها سهتایی باn == 0است. اگر(i, j, n)و(i', j', n')سهتاییهای مجاور در فهرست باشند و دومی آخرین سهتایی در فهرست نباشد، آنگاهi+n < i'یاj+n < j'برقرار است؛ به عبارت دیگر، سهتاییهای مجاور همیشه بلوکهای برابرِ غیرمجاور را توصیف میکنند.>>> 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()¶
فهرستی از ۵-تاییها برمیگرداند که چگونگی تبدیل a به b را توصیف میکنند. هر ۵-تایی به شکل
(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()¶
معیاری از شباهت دنبالهها را بهصورت یک عدد اعشاری در بازهی [۰، ۱] برمیگرداند.
اگر T تعداد کل عناصر هر دو دنباله و M تعداد تطابقها باشد، این مقدار برابر با 2.0*M / T است. توجه داشته باشید که اگر دنبالهها یکسان باشند، این مقدار
1.0است و اگر هیچ اشتراکی نداشته باشند،0.0است.محاسبهی این مقدار در صورتی پرهزینه است که
get_matching_blocks()یاget_opcodes()از قبل فراخوانی نشده باشند؛ در این صورت ممکن است بخواهید ابتداquick_ratio()یاreal_quick_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
مثالها¶
مثالهای SequenceMatcher¶
این مثال دو رشته را با در نظر گرفتن فاصلهها بهعنوان «زائد» مقایسه میکند:
>>> s = SequenceMatcher(lambda x: x == " ",
... "private Thread currentThread;",
... "private volatile Thread currentThread;")
ratio() یک عدد اعشاری در بازهی [۰، ۱] برمیگرداند که شباهت دنبالهها را میسنجد. بهعنوان یک قاعده سرانگشتی، مقدار ratio() بیش از ۰٫۶ به این معناست که دنبالهها تطابقهای نزدیکی هستند:
>>> 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]
همچنین ملاحظه نمائید
تابع
get_close_matches()در این ماژول که نشان میدهد چگونه کد سادهای که بر پایهیSequenceMatcherساخته شده است، میتواند برای انجام کار مفید استفاده شود.دستور سادهی کنترل نسخه برای یک برنامهی کوچک ساختهشده با
SequenceMatcher.
مثال Differ¶
این مثال دو متن را مقایسه میکند. ابتدا متنها را آماده میکنیم، دنبالههایی از رشتههای تکسطری جداگانه که با خط جدید پایان مییابند (چنین دنبالههایی را همچنین میتوان از متد 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 یک فهرست از رشتهها است، بنابراین بیایید آن را زیبانویسی کنیم (pretty-print):
>>> 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¶
این مثال نشان میدهد که چگونه از difflib.ndiff() استفاده کنید.
"""ndiff [-q] file1 file2
or
ndiff (-r1 | -r2) < ndiff_output > file1_or_file2
Print a human-friendly file difference report to stdout. Both inter-
and intra-line differences are noted. In the second form, recreate file1
(-r1) or file2 (-r2) on stdout, from an ndiff report on stdin.
In the first form, if -q ("quiet") is not specified, the first two lines
of output are
-: file1
+: file2
Each remaining line begins with a two-letter code:
"- " line unique to file1
"+ " line unique to file2
" " line common to both files
"? " line not present in either input file
Lines beginning with "? " attempt to guide the eye to intraline
differences, and were not present in either input file. These lines can be
confusing if the source files contain tab characters.
The first file can be recovered by retaining only lines that begin with
" " or "- ", and deleting those 2-character prefixes; use ndiff with -r1.
The second file can be recovered similarly, but by retaining only " " and
"+ " lines; use ndiff with -r2; or, on Unix, the second file can be
recovered by piping the output through
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
# open a file & return the file object; gripe and return 0 if it
# couldn't be opened
def fopen(fname):
try:
return open(fname)
except IOError as detail:
return fail("couldn't open " + fname + ": " + str(detail))
# open two files & spray the diff to stdout; return false iff a problem
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
# crack args (sys.argv[1:] is normal) & compare;
# return false iff a problem
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)
# read ndiff output from stdin, and print file1 (which=='1') or
# file2 (which=='2') to stdout
def restore(which):
restored = difflib.restore(sys.stdin.readlines(), which)
sys.stdout.writelines(restored)
if __name__ == '__main__':
main(sys.argv[1:])