ذات مرة ، في أيام طلابي ، تعرضت للعض من الثعبان ، على الرغم من تأخر فترة الحضانة واتضح أنني أصبحت مبرمجًا لؤلؤي.
ومع ذلك ، في مرحلة ما ، استنفدت اللؤلؤ نفسها وقررت تناول الثعبان ، في البداية فعلت شيئًا فقط واكتشفت ما هو مطلوب لهذه المهمة ، ثم أدركت أنني بحاجة إلى نوع من المعرفة المنهجية وقرأت العديد من الكتب:
- بيل ليوبانوفيتش "بسيط بايثون. أسلوب البرمجة الحديثة "
- دان بدر "Pure Python. التفاصيل الدقيقة للبرمجة للمحترفين »
- بريت سلاتكين "أسرار Python: 59 نصيحة لكتابة كود فعال"
الأمر الذي بدا لي مناسبًا تمامًا لفهم الخفايا الأساسية للغة ، على الرغم من أنني لا أتذكر ذكر الخانات الموجودة بها ، لكنني لست متأكدًا من أن هذه ميزة ضرورية حقًا - إذا ضغطت عليها من الذاكرة ، فعلى الأرجح لن تكون هذه الطريقة كافية ، ولكن بالطبع كل هذا يتوقف على الوضع.
ونتيجة لذلك ، جمعت بعض الملاحظات حول ميزات الثعبان ، والتي ، كما يبدو لي ، يمكن أن تكون مفيدة لشخص يريد الهجرة إليها من لغات أخرى.
لاحظت أنه خلال المقابلات التي تُجرى على الثعبان غالبًا ما يطرح الناس أسئلة حول أشياء لا تتعلق بالتنمية الحقيقية ، مثل ما قد يكون مفتاح القاموس (أو ما يعنيه x = yield y
) ، أيها الرجال ، في الحياة الواقعية ، قد يكون المفتاح فقط رقم أو سلسلة ، في تلك الحالات الفريدة عندما لا يكون الأمر كذلك ، يمكنك قراءة الوثائق ومعرفة لماذا تسأل هذا؟ للعثور على ما لا يعرفه الشخص الذي أجريت معه المقابلة؟ لذا في النهاية ، سيتذكر الجميع الإجابة على هذا السؤال بالذات وسيتوقف عن العمل.
أعتبر إصدارات الثعبان أعلى من 3.5 ذات الصلة ( حان الوقت لنسيان الثعبان الثاني لفترة طويلة) منذ هذا هو الإصدار المستقر في دبيان ، مما يعني أنه في جميع الأماكن الأخرى هناك إصدارات أحدث)
نظرًا لأنني لست خبيرًا في الثعبان على الإطلاق ، آمل أن يصححوني في التعليقات إذا جمدت فجأة نوعًا من الغباء.
الكتابة
Python هي لغة مكتوبة ديناميكيًا ، أي يتحقق من مطابقة النوع في وقت التشغيل ، على سبيل المثال:
cat type.py a=5 b='5' print(a+b)
تنفيذ:
python3 type.py ... TypeError: unsupported operand type(s) for +: 'int' and 'str'
ومع ذلك ، إذا نضج مشروعك إلى الحاجة إلى الكتابة الثابتة ، فإن python يوفر مثل هذه الفرصة باستخدام محلل ثابت mypy
:
mypy type.py type.py:3: error: Unsupported operand types for + ("int" and "str")
صحيح أنه لم يتم اكتشاف جميع الأخطاء بهذه الطريقة:
cat type2.py def greeting(name): return 'Hello ' + name greeting(5)
لن يقسم mypy هنا ، ولكن سيحدث خطأ أثناء التنفيذ ، لذا فإن الإصدارات الحالية من python تدعم بناء جملة خاص لتحديد أنواع وسائط الدالات:
cat type3.py def greeting(name: str) -> str: return 'Hello ' + name greeting(5)
والآن:
mypy type3.py type3.py:4: error: Argument 1 to "greeting" has incompatible type "int"; expected "str"
المتغيرات والبيانات
لا تخزن المتغيرات في python البيانات ، ولكنها تشير إليها فقط ، ويمكن أن تكون البيانات قابلة للتغيير (قابلة للتغيير) وغير قابلة للتغيير (ثابتة).
يؤدي هذا إلى سلوك مختلف اعتمادًا على نوع البيانات في مواقف متطابقة تقريبًا ، على سبيل المثال ، مثل هذا الرمز:
x = 1 y = x x = 2 print(y)
يؤدي إلى حقيقة أن المتغيرات x
و y
تشير إلى بيانات مختلفة ، وهذا:
x = [1, 2, 3] y = x x[0] = 7 print(y)
لا ، تبقى x
و y
روابط إلى نفس القائمة (على الرغم من أنه كما هو مذكور في التعليقات ، المثال ليس ناجحًا جدًا ، لكنني لم أتوصل إلى الأفضل بعد) أنه بالمناسبة في python يمكنك التحقق من عامل التشغيل (أنا متأكد من أن منشئ جافا قد فقد نومًا جيدًا إلى الأبد من العار عندما علمت عن هذا العامل في بيثون).
على الرغم من أن الصفوف تبدو مثل قائمة ، إلا أنها نوع بيانات غير قابل للتغيير ، وهذا يعني أنه لا يمكن تغيير السلسلة نفسها ، يمكنك فقط إنشاء سلسلة جديدة ، ولكن يمكنك تعيين قيمة مختلفة للمتغير ، على الرغم من أن البيانات الأصلية لن تتغير:
>>> mystr = 'sss' >>> newstr = mystr # >>> mystr[0] = 'a' ... TypeError: 'str' object does not support item assignment >>> mystr = 'ssa' # >>> newstr # 'sss'
عند التحدث عن السلاسل ، بسبب حصانتهم ، فإن ربط قائمة كبيرة جدًا من السلاسل عن طريق إضافة أو إلحاق في حلقة قد لا يكون فعالًا جدًا (اعتمادًا على التنفيذ في مترجم / إصدار معين) ، عادة ما يوصى باستخدام طريقة الربط لمثل هذه الحالات ، والتي تتصرف قليلا غير متوقع:
>>> str_list = ['ss', 'dd', 'gg'] >>> 'XXX'.join(str_list) 'ssXXXddXXXgg' >>> str = 'hello' >>> 'XXX'.join(str) 'hXXXeXXXlXXXlXXXo'
أولاً ، يصبح الخط الذي تُسمى فيه الطريقة فاصلًا ، وليس بداية سطر جديد كما قد يعتقد المرء ، وثانيًا ، تحتاج إلى تمرير قائمة (كائن قابل للتكرار) ، وليس سطرًا منفصلًا ، لأنه أيضًا كائن قابل للتكرار وسيتم ترميزه .
نظرًا لأن المتغيرات هي روابط ، فمن الطبيعي تمامًا أن ترغب في عمل نسخة من كائن حتى لا تكسر الكائن الأصلي ، ولكن هناك مشكلة - تنسخ وظيفة النسخ مستوى واحدًا فقط ، وهو من الواضح أنه ليس ما هو متوقع من وظيفة بهذا الاسم ، لذا استخدم deepcopy
.
يمكن أن تحدث مشكلة مماثلة مع النسخ عندما يتم ضرب مجموعة في عددي ، كما هو موضح هنا .
النطاق
ربما يستحق موضوع النطاق مقالة منفصلة ، ولكن هناك إجابة جيدة لـ SO .
باختصار ، النطاق معجمي وهناك ستة مجالات للرؤية - متغيرات في نص الوظيفة ، في الإغلاق ، في الوحدة النمطية ، في جسم الفصل ، وظائف بيثون مضمنة ومتغيرات داخل القائمة وغيرها من الادراج.
هناك دقة - المتغير الافتراضي قابل للقراءة في مساحات الأسماء المتداخلة معجمًا ، ولكن التعديل يتطلب استخدام كلمات رئيسية خاصة nonlocal
global
لتعديل المتغيرات مستوى واحد أعلى أو رؤية عالمية ، على التوالي.
على سبيل المثال ، رمز مثل هذا:
x = 7 print(id(x)) def func(): print(id(x)) return x print(func())
يعمل مع متغير عالمي واحد ، وهذا المتغير:
x = 7 print(id(x)) def func(): x = 1 print(id(x)) return x print(func()) print(x)
تولد بالفعل واحدة محلية.
من وجهة نظري ، هذا ليس جيدًا جدًا ، من حيث المبدأ ، أي استخدام للمتغيرات غير المحلية في وظيفة ما هو جزء من الواجهة العامة للوظيفة ، وتوقيعها ، مما يعني أنه يجب الإعلان عنها بشكل واضح ومرئي في بداية الوظيفة. أيضًا ، الكلمات الرئيسية ليست مفيدة للغاية - الأصوات global
مثل تعريف وظيفة عالمية ، لكنها تعني في الواقع use global
.
في python لا توجد نقطة دخول إلزامية يبدأ من خلالها البرنامج ، كما هو الحال في العديد من اللغات ، يتم تنفيذ كل شيء مكتوب على مستوى الوحدة النمطية بالتسلسل ، ومع ذلك ، نظرًا لأن المتغيرات على مستوى الوحدة النمطية هي متغيرات عالمية ، من وجهة نظري ، يجب أن تكون ممارسة جيدة حصر الكود الرئيسي في الدالة main()
، متبوعًا بدعوته في نهاية الملف:
if __name__ == '__main__': main()
ستعمل هذه الحالة إذا تم استدعاء الملف كبرنامج نصي ولم يتم استيراده كوحدة نمطية.
وسيطات الدالة
يوفر Python ببساطة فرصًا أنيقة لتحديد الحجج الوظيفية - الحجج الموضعية المسماة والمجموعات الخاصة بها.
ولكن عليك أن تفهم كيف يتم تمرير الحجج - لأن في python ، جميع المتغيرات هي روابط للبيانات ، يمكنك تخمين أن النقل حسب المرجع ، ولكن هناك خصوصية - يتم تمرير الرابط نفسه بالقيمة ، أي يمكنك تعديل القيمة القابلة للتحويل حسب المرجع:
def add_element(mylist): mylist.append(3) mylist = [1,2] add_element(mylist) print(mylist)
تنفيذ:
python3 arg_modify.py [1, 2, 3]
ومع ذلك ، لا يمكنك الكتابة فوق الارتباط الأصلي في دالة:
def try_del(mylist): mylist = [] return mylist mylist = [1,2] try_del(mylist) print(mylist)
رابط المصدر على قيد الحياة ويعمل:
python3 arg_kill.py [1, 2]
يمكنك أيضًا تعيين القيم الافتراضية للوسيطات ، ولكن هناك شيء واحد غير واضح يجب تذكره: يتم حساب القيم الافتراضية مرة واحدة عند تحديد دالة ، وهذا لا يخلق أي مشاكل إذا قمت بتمرير بيانات دون تغيير كقيمة افتراضية ، وإذا قمت بتمرير بيانات متغيرة أو قيمة ديناميكية ، ستكون النتيجة غير متوقعة قليلاً:
البيانات القابلة للتغيير:
cat arg_list.py def func(arg = []): arg.append('x') return arg print(func()) print(func()) print(func())
النتيجة:
python3 arg_list.py ['x'] ['x', 'x'] ['x', 'x', 'x']
القيمة الديناميكية:
cat arg_now.py from datetime import datetime def func(arg = datetime.now()): return arg print(func()) print(func()) print(func())
نحصل على:
python3 arg_now.py 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879
عذرًا
تم إجراء OOP في python بشكل مثير للاهتمام للغاية (بعض الخصائص تستحق ذلك) وهذا موضوع كبير ، ولكن يمكن أن يعلم كل من sapiens المطلعين على OOP كل شيء (أو يجده على المحور ) الذي يريده ، لذلك لا فائدة من التكرار ، على الرغم من أنه من الجدير اشتراط أن يكون الثعبان قليلًا فلسفة مختلفة - هي أن مبرمجا أكثر ذكاء الآلات، وليس تهديدا (UPD: أكثر )، وبالتالي فإن الافتراضي الثعبان ليس من المعتاد لغيرها من معدلات الوصول اللغات: طرق خاصة تنفذها إضافة تسطير مزدوج (والذي يتغير وقت من اسم الأسلوب ليست OAPC موضوع فرصة لاستخدامها)، ومحمية تسطير واحد (لا تفعل أي شيء، انها مجرد اصطلاح التسمية).
يمكن لأولئك الذين يفتقدون الوظيفة المعتادة أن يبحثوا عن محاولات لجلب مثل هذه الفرص إلى python ، وقد قمت بدراسة اثنين من الخيارات ( lang ، python-access ) ، لكنني لم أختبرها أو أدرسها.
ناقص الوحيد من الفئات القياسية هو رمز القالب في أي من أساليب Dander ، وأنا شخصياً أحب مكتبة attrs ، فهي أكثر ثعبانية.
من الجدير بالذكر أنه نظرًا لأن جميع الكائنات في Python ، بما في ذلك الوظائف والفئات ، يمكن إنشاء الفئات ديناميكيًا (بدون استخدام eval
) بواسطة دالة النوع .
كما أنه يستحق القراءة عن metaclasses ( على Habr ) والوصف ( Habr ).
الخصوصية التي تستحق التذكر هي أن سمات الفئة والكائن ليست هي نفسها ، في حالة السمات الثابتة ، لا يسبب هذا مشاكل لأن السمات هي "التظليل" - يتم إنشاء سمات الكائن الذي يحمل نفس الاسم تلقائيًا ، ولكن في حالة السمات القابلة للتغيير ، يمكنك لا تحصل على ما كان متوقعًا:
cat class_attr.py class MyClass: storage = [7,] def __init__(self, number): self.number = number obj = MyClass(1) obj2 = MyClass(2) obj.number = 5 obj.storage.append(8) print(obj2.storage, obj2.number)
نحصل على:
python3 class_attr.py [7, 8] 2
كما ترون - قاموا بتغيير obj
، وتغيير storage
في obj2
أيضًا. هذه السمة (على عكس number
) لا تنتمي إلى المثيل ، ولكن إلى الفئة.
الثوابت
كما هو الحال في معدلات الوصول ، لا تحاول الثعبان الحد من المطور ، وبالتالي ، من المستحيل تحديد متغير سلمي محمي من التعديل بالطريقة القياسية ، هناك ببساطة اتفاق على أن المتغيرات التي تحمل اسمًا في الحالة العليا يجب اعتبارها ثوابت.
من ناحية أخرى ، تحتوي Python على هياكل بيانات غير قابلة للتغيير مثل tuple ، لذلك إذا كنت ترغب في جعل بعض البنية العالمية مثل التكوين غير قابل للتغيير ولا تريد تبعيات إضافية ، فإن nametuple هو خيار جيد ، على الرغم من أنه يتطلب المزيد من الجهد لوصف الأنواع ، لذلك يعجبني التنفيذ البديل للهيكل غير الثابت مع تدوين النقطة - Box (انظر معلمةقيبةقي المجمدة).
حسنًا ، إذا كنت تريد ثوابت قياسية ، فيمكنك تنفيذ التحكم في الوصول في مرحلة "التجميع" أي يتحقق من خلال mypy ، المثال والتفاصيل .
.sort () مقابل Sorted ()
هناك طريقتان لفرز قائمة في الثعبان. الطريقة الأولى هي طريقة .sort()
التي تعدل القائمة الأصلية ولا تُرجع شيئًا (بلا) أي لا يمكن القيام بذلك:
my_list = my_list.sort()
والثاني هو دالة Sorted sorted()
، التي sorted()
قائمة جديدة ويمكن أن تعمل مع جميع الكائنات القابلة للتكرار. من يريد المزيد من المعلومات يجب أن يبدأ بـ SO .
مكتبة قياسية
عادةً ، تشتمل مكتبة الثعبان القياسية على حلول ممتازة للمشكلات الشائعة ، ولكن الأمر يستحق أن تكون حرجًا ، لأن هناك شذوذات كافية. صحيح ، يحدث أيضًا أن ما يبدو غريباً للوهلة الأولى يتضح أنه الحل الأفضل ، فأنت تحتاج فقط إلى معرفة جميع الشروط (انظر أدناه لمعرفة النطاق) ، ولكن لا تزال هناك غرائب.
على سبيل المثال ، الوحدة النمطية غير الواضحة التي تأتي مع المجموعة لا علاقة لها بالثعبان وصفات جافا ، لذلك ، كما يقول مؤلف الثعبان : "Eveybody يستخدم py.test ...". على الرغم من أنها مثيرة للاهتمام للغاية ، وإن لم تكن مناسبة دائمًا ، إلا أن وحدة doctest تأتي كمعيار.
لا تحتوي وحدة urllib المزودة على واجهة جميلة مثل وحدة طلبات الطرف الثالث.
نفس القصة مع الوحدة النمطية لتحليل معلمات سطر الأوامر - الجدولة المجمعة هي عرض لـ OOP للدماغ ، ويبدو أن وحدة docopt مجرد حل ذكي - التوثيق الذاتي النهائي! على الرغم من الشائعات ، على الرغم من docopt والنقر ، لا يزال هناك مكانة.
مع المصحح أيضًا - كما أفهمها ، قلة من الناس يستخدمون pdb المضمّن في الحزمة ، هناك العديد من البدائل ، ولكن يبدو أن غالبية المطورين يستخدمون ipdb ، والذي ، من وجهة نظري ، هو الأكثر ملاءمة للاستخدام من خلال وحدة غلاف التصحيح .
إنها تسمح بدلاً من import ipdb;ipdb.set_trace()
بكتابة import debug
ببساطة ، كما أنها تضيف وحدة مشاهدة لسهولة فحص الكائنات.
لاستبدال وحدة التسلسل القياسية ، يتم عمل المخلل ، بالمناسبة ، تجدر الإشارة إلى أن هذه الوحدات ليست مناسبة لتبادل البيانات في الأنظمة الخارجية منذ استعادة الأشياء التعسفية التي يتم تلقيها من مصدر غير خاضع للرقابة غير آمنة ، في مثل هذه الحالات هناك json (لـ REST) و gRPC (لـ RPC).
لاستبدال الوحدة النمطية القياسية لمعالجة التعبير العادي ، قم بإعادة إنشاء وحدة regex مع جميع أنواع العناصر الإضافية الإضافية ، مثل فئات الأحرف ala \p{Cyrillic}
.
بالمناسبة ، لم يظهر شيء لثعبان مصحح ممتع للتعبير المعتاد مشابه لشعير اللؤلؤ .
فيما يلي مثال آخر - قام شخص بعمل وحدة نمطية في مكانه لإصلاح انحناء وعدم اكتمال واجهة برمجة التطبيقات لوحدة إدخال الملفات القياسية في الجزء الموجود من تحرير الملف.
حسنًا ، أفكر في مثل هذه الحالات كثيرًا ، نظرًا لأنني صادفت أكثر من حالة واحدة ، لذا كن حذرًا ولا تنسى النظر في جميع أنواع القوائم الرائعة الرائعة ، أعتقد أن أخصائي التغذية الجيد لديه أنف لقياس مدى عقلانية الحل ، وهذا بالمناسبة موضوع لمناقشة أخرى - وفقًا لمشاعري (بالطبع ، لا توجد إحصائيات حول هذا الموضوع ولا يبدو أن ذلك ممكن) في عالم الثعبان ، مستوى المتخصصين أعلى من المتوسط ، لأنه غالبًا ما يتم كتابة برامج جيدة في الثعبان ، اكتب في التعليقات ما تعتقده حول هذا.
التزامن والمنافسة
توفر Python فرصًا كبيرة للبرمجة المتوازية والتنافسية ، ولكن ليس بدون ميزات.
إذا كنت بحاجة إلى التوازي ، وهذا يحدث عندما تتطلب مهامك الحساب ، فيجب عليك الانتباه إلى وحدة المعالجة المتعددة .
وإذا كانت مهامك تحتوي على الكثير من توقعات IO ، فإن python يوفر الكثير من الخيارات للاختيار من بينها ، من سلاسل المحادثات و gevent إلى asyncio .
تبدو جميع هذه الخيارات مناسبة تمامًا للاستخدام (على الرغم من أن الخيوط تتطلب المزيد من الموارد) ، ولكن هناك شعور بأن asyncio يضغط ببطء على الباقي ، بما في ذلك بفضل جميع أنواع الأشياء الجيدة مثل uvloop .
إذا لم يلاحظ أحد - في الثعبان ، فإن المواضيع لا تتعلق بالتوازي ، فأنا لست مؤهلاً بما يكفي للتحدث عن GIL جيدًا ، ولكن هناك ما يكفي من المواد حول هذا الموضوع ، لذلك لا توجد مثل هذه الحاجة ، الشيء الرئيسي الذي يجب تذكره هو أن الخيوط في الثعبان (بتعبير أدق في CPython) تتصرف بشكل مختلف عن لغات البرمجة الأخرى - فهي تعمل على قلب واحد فقط ، مما يعني أنها غير مناسبة للحالات التي تحتاج فيها إلى توازي حقيقي ، ومع ذلك ، يتوقف تنفيذ مؤشر الترابط مؤقتًا عند انتظار الإدخال / الإخراج ، بحيث يمكن استخدامها للتنافس.
الشذوذ الأخرى
في python ، a = a + b
لا يعادل دائمًا a += b
:
a = [1] a = a + (2,3) TypeError: can only concatenate list (not "tuple") to list a += (2,3) a [1, 2, 3]
أقوم بإرساله إلى SO للحصول على التفاصيل ، حتى أجد الوقت لمعرفة سبب ذلك ، بمعنى سبب قيامهم بذلك ، مثل هذا مرة أخرى حول قابلية التغيير.
الشذوذ ليست الشذوذ
للوهلة الأولى ، بدا غريباً بالنسبة لي أن نوع النطاق لا يشمل الحدود الصحيحة ، ولكن بعد ذلك أخبرني شخص لطيف أن أجهل أين أحتاج للتعلم واتضح أن كل شيء كان منطقيًا تمامًا.
موضوع كبير منفصل هو التقريب (على الرغم من أن هذه المشكلة شائعة في جميع لغات البرمجة تقريبًا) ، بالإضافة إلى استخدام التقريب كما تريد ، باستثناء أن كل شخص درس في دورة الرياضيات المدرسية ، لذلك لا تزال مشاكل تمثيل أرقام الفاصلة العائمة متراكبة عليها ، وأشير إلى مقال مفصل.
بشكل تقريبي ، بدلاً من الخوارزمية النصف دائرية المعتادة للرياضيات المدرسية ، يتم استخدام خوارزمية نصف متساوية ، مما يقلل من احتمال التشويه في التحليل الإحصائي وبالتالي ينصح به معيار IEEE 754.
أيضا ، لم أستطع أن أفهم لماذا -22//10=-3
، ثم أشار شخص لطيف آخر إلى أن هذا يتبع حتمًا من التعريف الرياضي نفسه ، والذي بموجبه لا يمكن أن يكون الباقي سلبيًا ، مما يؤدي إلى مثل هذا السلوك غير المعتاد أرقام سلبية.
يشجع! الآن هذا شيء غريب مرة أخرى ولا أفهم أي شيء ، انظر هذا الموضوع .
تصحيح التعبير العادي
وهنا اتضح أنه في عالم الثعبان لا توجد أداة لتصحيح أخطاء التعبيرات العادية بشكل تفاعلي تشبه وحدة اللؤلؤ الممتازة Regexp :: Debugger ( عرض فيديو ) ، وبالطبع هناك مجموعة من الأدوات عبر الإنترنت ، هناك نوع من الحلول الخاصة بـ Windows ، ولكن بالنسبة لي ليس هذا ، ربما يجدر استخدام أداة شريط اللؤلؤ ، لأن ريكس الثعبان لا يختلف كثيرًا عن شريط اللؤلؤ ، سأكتب تعليمات لأولئك الذين لا يمتلكون شريط اللؤلؤ:
sudo apt install cpanminus cpanm Regexp::Debugger perl -I ~/perl5/lib/perl5/ -E "use Regexp::Debugger; 'ababc' =~ /(a|b) b+ c/x"
أعتقد أنه حتى الشخص غير المألوف للؤلؤة سيفهم أين يلزم إدخال الخط ، وأين هو التعبير العادي ، x
هو علم مشابه لثعبان re.VERBOSE.
اضغط s
وانتقل عبر التعبير العادي ، وهو وصف تفصيلي للأوامر المتاحة في الوثائق .
التوثيق
هناك وظيفة مساعدة في python ، والتي تسمح لك بالحصول على مساعدة في أي وظيفة محملة (مأخوذة من مستندها) ، يتم تمرير اسم الوظيفة كمعلمة:
$ python3 >>> help(help)
ولكن هذه ليست دائمًا طريقة ملائمة وغالبًا ما تكون أكثر ملاءمة لاستخدام أداة pydoc:
pydoc3 urllib.parse.urlparse
الأداة تسمح لك بالبحث عن طريق الكلمات الرئيسية وحتى بدء خادم محلي بوثائق html ، لكنني لم أختبر هذا الأخير.