
Python هي لغة رائعة لتطبيقات وحدة التحكم ، وتسلط الضوء على عدد كبير من المكتبات لهذه المهام.
لكن ما المكتبات الموجودة؟ وأيهما أفضل؟ تقارن هذه المواد بين الأدوات الشائعة وليست الأدوات نفسها لعالم الكونسول وتعطي محاولة للإجابة على السؤال الثاني.
لسهولة القراءة ، تنقسم المراجعة إلى وظيفتين: الأولى تقارن بين المكتبات الستة الأكثر شعبية ، والثانية - الأقل شعبية والأكثر تحديدًا ، ولكنها لا تزال جديرة بالاهتمام.
في كل مثال ، سيتم كتابة أداة مساعدة لوحدة التحكم في مكتبة
todolib في Python 3.7 ، والتي يمكنك من خلالها إنشاء المهام وعرضها ووضع علامات عليها وحذفها. سيتم إضافة الباقي رهنا بساطة التنفيذ في إطار معين. يتم تخزين المهام نفسها في ملف json ، والتي سيتم حفظها في مكالمة منفصلة - شرط إضافي للحصول على الأمثلة.
بالإضافة إلى ذلك ، سيتم كتابة اختبار تافه لكل تطبيق. اتخذ Pytest مع التجهيزات التالية كإطار اختبار:
@pytest.fixture(autouse=True) def db(monkeypatch): """ monkeypatch , """ value = {"tasks": []} monkeypatch.setattr(todolib.TodoApp, "get_db", lambda _: value) return value @pytest.yield_fixture(autouse=True) def check(db): """ """ yield assert db["tasks"] and db["tasks"][0]["title"] == "test" # , EXPECTED = "Task 'test' created with number 1.\n"
من حيث المبدأ ، كل ما سبق سيكون كافياً لإظهار المكتبات. شفرة المصدر الكاملة متاحة في
هذا المستودع.
argparse
تتمتع
Argparse بميزة لا يمكن إنكارها - إنها موجودة في المكتبة القياسية ، ولا يصعب تعلم واجهة برمجة التطبيقات: فهناك محلل ، وهناك وسيطات ، وتتمتع الوسائط
بالكتابة ،
والإجراء ، و
dest ، و
default ،
والمساعدة . وهناك
subparser - القدرة على فصل جزء من الحجج والمنطق إلى أوامر منفصلة.
محلل
للوهلة الأولى - لا شيء غير عادي ، المحلل اللغوي يشبه المحلل اللغوي. لكن - في رأيي - ليست القراءة هي الأفضل عند مقارنتها بالمكتبات الأخرى يتم وصف الوسائط لأوامر مختلفة في مكان واحد.
شفرة المصدر def get_parser(): parser = argparse.ArgumentParser("Todo notes - argparse version") parser.add_argument( "--verbose", "-v", action="store_true", help="Enable verbose mode" ) parser.add_argument("--version", "-V", action="store_true", help="Show version") subparsers = parser.add_subparsers(title="Commands", dest="cmd") add = subparsers.add_parser("add", help="Add new task") add.add_argument("title", help="Todo title") show = subparsers.add_parser("show", help="Show tasks") show.add_argument( "--show-done", action="store_true", help="Include done tasks in the output" ) done = subparsers.add_parser("done", help="Mark task as done") done.add_argument("number", type=int, help="Task number") remove = subparsers.add_parser("remove", help="Remove task") remove.add_argument("number", type=int, help="Task number") return parser
رئيسي
وهنا نفس الشيء - لا يمكن للمحلل باستثناء الحجج التحليلية أن يفعل شيئًا أكثر ، لذلك سيتعين كتابة المنطق بشكل مستقل وفي مكان واحد. فمن ناحية - من الممكن أن تعيش ، من ناحية أخرى - كان ذلك أفضل ، لكن لم يتضح بعد كيف.
UPD: كما لاحظ foldr ، في الواقع ، يمكن أن تقوم subparsers بتعيين الوظائف عبر set_defaults (func = foo) ، أي ، argparse يسمح لك باختصار رئيسي إلى أحجام صغيرة. عش وتعلم.شفرة المصدر def main(raw_args=None): """ Argparse example entrypoint """ parser = get_parser() args = parser.parse_args(raw_args) logging.basicConfig() if args.verbose: logging.getLogger("todolib").setLevel(logging.INFO) if args.version: print(lib_version) exit(0) cmd = args.cmd if not cmd: parser.print_help() exit(1) with TodoApp.fromenv() as app: if cmd == "add": task = app.add_task(args.title) print(task, "created with number", task.number, end=".\n") elif cmd == "show": app.print_tasks(args.show_done) elif cmd == "done": task = app.task_done(args.number) print(task, "marked as done.") elif cmd == "remove": task = app.remove_task(args.number) print(task, "removed from list.")
تجريب
للتحقق من إخراج الأداة المساعدة ، يتم
استخدام أداة
تثبيت capsys ، والتي تتيح الوصول إلى النص من stdout و stderr.
def test_argparse(capsys): todo_argparse.main(["add", "test"]) out, _ = capsys.readouterr() assert out == EXPECTED
يؤدي
من المزايا - مجموعة جيدة من الميزات للتحليل ، وجود وحدة نمطية في المكتبة القياسية.
سلبيات - argparse تشارك فقط في تحليل الحجج ، وكان معظم المنطق الرئيسي في أن تكون مكتوبة من قبل نفسي. وليس من الواضح كيفية اختبار رمز الخروج في الاختبارات.
docopt
docopt عبارة عن
محلل صغير (<600 سطر ، مقارنة بـ 2500 مع argparse) ، من شأنه أن يجعلك تبتسم ، نقلاً عن وصف على GitHub. تتمثل الفكرة الرئيسية لـ docopt في وصف الواجهة حرفيًا بالنص ، على سبيل المثال ، في docstring.
على نفس جيثب ، docopt> 6700 نجم ، يتم استخدامه في ما لا يقل عن 22 ألف مشروع آخر. وهذا فقط مع تنفيذ الثعبان! تحتوي صفحة مشروع docopt على العديد من الخيارات للغات مختلفة ، من C و PHP إلى CoffeeScript وحتى R. يمكن شرح هذا النظام المتقاطع فقط من خلال ضغط الشفرة وبساطتها.
محلل
بالمقارنة مع argparse ، يعد هذا المحلل خطوة كبيرة للأمام.
"""Todo notes on docopt. Usage: todo_docopt [-v | -vv ] add <task> todo_docopt [-v | -vv ] show --show-done todo_docopt [-v | -vv ] done <number> todo_docopt [-v | -vv ] remove <number> todo_docopt -h | --help todo_docopt --version Options: -h --help Show help. -v --verbose Enable verbose mode. """
رئيسي
بشكل عام ، كل شيء هو نفسه كما هو الحال مع argparse ، ولكن الآن يمكن أن يكون للقيمة
المطولة عدة قيم (0-2) ، والوصول إلى الوسائط مختلف: لا يرجع docopt مساحة اسم ذات سمات ، ولكن مجرد قاموس ، حيث تتم الإشارة إلى اختيار أمر من خلال منطقها ، كما يتضح في:
شفرة المصدر def main(argv=None): args = docopt(__doc__, argv=argv, version=lib_version) log.setLevel(levels[args["--verbose"]]) logging.basicConfig() log.debug("Arguments: %s", args) with TodoApp.fromenv() as app: if args["add"]: task = app.add_task(args["<task>"]) print(task, "created with number", task.number, end=".\n") elif args["show"]: app.print_tasks(args["--show-done"]) elif args["done"]: task = app.task_done(args["<number>"]) print(task, "marked as done.") elif args["remove"]: task = app.remove_task(args["<number>"]) print(task, "removed from list.")
تجريب
على غرار اختبار argparse:
def test_docopt(capsys): todo_docopt.main(["add", "test"]) out, _ = capsys.readouterr() assert out == EXPECTED
يؤدي
من الفوائد - رمز أقل بكثير من المحلل اللغوي ، وسهولة الوصف وقراءة الأوامر والحجج ، المدمج في الإصدار.
سلبيات ، أولا ، نفس argparse - الكثير من المنطق بشكل
رئيسي ، لا يمكنك اختبار رمز الخروج. بالإضافة إلى ذلك ، فإن الإصدار الحالي (0.6.2) من docopt لم يستقر بعد ومن غير المرجح أن يكون على الإطلاق - كان المشروع قيد التطوير
بنشاط من عام 2012 إلى نهاية عام 2013 ، وكان الالتزام الأخير في 17 ديسمبر. والشيء الأكثر غير السارة في الوقت الحالي هو أن بعض النظاميين docopt تثير DeprecationWarning عند إجراء الاختبارات.
انقر
يختلف
Click بشكل أساسي عن argparse و docopt من خلال عدد الميزات ونهج وصف الأوامر والمعلمات من خلال المخططات ، ويقترح أن يتم فصل المنطق نفسه إلى وظائف منفصلة بدلاً من
رئيسي كبير. يدعي المؤلفون أن النقر يحتوي على الكثير من الإعدادات ، ولكن يجب أن تكون المعلمات القياسية كافية. بين الميزات ، يتم التأكيد على الأوامر المتداخلة والتحميل البطيء.
يتمتع المشروع بشعبية كبيرة: بالإضافة إلى وجود أكثر من 8100 نجم واستخدامه في ما لا يقل عن 174 ألف مشروع (!) ، لا يزال قيد التطوير: تم إصدار الإصدار 7.0 في خريف عام 2018 ، وتظهر طلبات جديدة ودمج الطلبات حتى يومنا هذا اليوم.
محلل
على صفحة الوثائق ، عثرت على decor_option decorator ، والذي يطلب التأكيد من المستخدم قبل تنفيذ الأمر. لإثبات ذلك ، تمت إضافة أمر المسح الذي يمسح قائمة المهام بأكملها.
شفرة المصدر levels = [logging.WARN, logging.INFO, logging.DEBUG] pass_app = click.make_pass_decorator(TodoApp) @click.group() @click.version_option(lib_version, prog_name="todo_click") @click.option("-v", "--verbose", count=True)
رئيسي
وهنا نلتقي بالميزة الرئيسية لـ Click - نظرًا لحقيقة أن منطق الأوامر متباعدًا وفقًا لوظائفها ، لا يوجد شيء تقريبًا يظل رئيسيًا. كما يوضح هنا قدرة المكتبة على تلقي الوسائط والمعلمات من متغيرات البيئة.
if __name__ == "__main__": cli(auto_envvar_prefix="TODO")
تجريب
في حالة Click ، ليست هناك حاجة لاعتراض sys.stdout ، نظرًا لوجود وحدة
click.testing مع عداء لمثل هذه الأشياء. ولا يقتصر
الأمر على اعتراض
CliRunner للإخراج فحسب ، بل يتيح لك أيضًا التحقق من رمز الخروج ، وهو أمر رائع أيضًا. كل هذا يسمح باختبار الأدوات المساعدة للنقر دون استخدام pytest وتجاوز الوحدة القياسية
للألم .
import click.testing def test_click(): runner = click.testing.CliRunner() result = runner.invoke(todo_click.cli, ["add", "test"]) assert result.exit_code == 0 assert result.output == EXPECTED
يؤدي
هذا مجرد جزء صغير مما يمكن أن تفعله Click. من بقية واجهة برمجة التطبيقات (API) - التحقق من القيم ، والتكامل مع الجهاز (الألوان ، و pager a la less ، وشريط التقدم ، وما إلى ذلك) ، ونتائج الاستدعاء ، والإكمال التلقائي ، وغير ذلك الكثير. يمكنك أن ترى أمثلةهم
هنا .
الايجابيات: الكثير من الأدوات لأية مناسبة ، طريقة أصلية ، ولكن في نفس الوقت مناسبة لوصف الفرق ، وسهولة الاختبار وحياة المشروع النشطة.
السلبيات: ما هي عيوب "النقر" - هذا سؤال صعب. ربما لا يعرف شيئًا عما تستطيع المكتبات التالية فعله؟
حريق
النار ليست مجرد مكتبة صغيرة (ظهرت في عام 2017) لواجهات وحدة التحكم من Google ، بل هي مكتبة لإنشاء واجهات وحدة التحكم من
أي كائن Python أو اقتباسه حرفيًا.
من بين أشياء أخرى ، ذكر أن الحريق يساعد في تطوير وتصحيح الكود ، ويساعد على تكييف الكود الموجود في CLI ، ويسهل الانتقال من bash إلى Python ، ولديه REPL الخاص به للعمل التفاعلي. يجب أن نرى؟
محلل والرئيسي
إن fire.Fire قادر حقًا على قبول أي كائن: وحدة نمطية ومثيل فئة وقاموس به أسماء أوامر ووظائف مقابلة وما إلى ذلك.
ما يهمنا هو أن Fire يسمح بنقل كائن صفي. وبالتالي ، يقبل مُنشئ الفصل الوسيطات الشائعة لجميع الأوامر ، وأساليبها وسماتها هي أوامر منفصلة. سوف نستخدم هذا:
شفرة المصدر class Commands: def __init__(self, db=None, verbose=False): level = logging.INFO if verbose else logging.WARNING logging.basicConfig(level=level) logging.getLogger("todolib").setLevel(level) self._app = todolib.TodoApp.fromenv(db) atexit.register(self._app.save) def version(self): return todolib.__version__ def add(self, task): """Add new task.""" task = self._app.add_task(task) print(task, "created with number", task.number, end=".\n") def show(self, show_done=False): """ Show current tasks. """ self._app.print_tasks(show_done) def done(self, number): """ Mark task as done. """ task = self._app.task_done(number) print(task, "marked as done.") def remove(self, number): """ Removes task from the list. """ task = self._app.remove_task(number) print(task, "removed from the list.") def main(args=None): fire.Fire(Commands, command=args)
الأعلام المضمنة
يحتوي Fire على أعلام خاصة به بناء جملة خاص (يجب تمريرها بعد "-") ، والتي تسمح لك بالبحث تحت غطاء المحلل اللغوي والتطبيق ككل:
استدعاء الأمثلة $ ./todo_fire.py show -- --trace Fire trace: 1. Initial component 2. Instantiated class "Commands" (todo_fire.py:9) 3. Accessed property "show" (todo_fire.py:25) $ ./todo_fire.py -- --verbose | head -n 12
تجريب
يشبه اختبار الوظيفة الرئيسية اختبار argparse و docopt ، لذلك لا أرى النقطة هنا.
في الوقت نفسه ، تجدر الإشارة إلى أنه نظرًا لطبيعة النار الاستقلالية ، من الممكن بنفس الدرجة اختبار فئة الأوامر على الفور.
يؤدي
النار هي أداة لا تقل إثارة للاهتمام من النقر. لا يتطلب سرد العديد من الخيارات في المحلل اللغوي ، والتكوين ضئيل للغاية ، وهناك خيارات للتصحيح ، والمكتبة نفسها
تعيش وتتطور بشكل أكثر نشاطًا من النقرة (60 ملفًا هذا الصيف).
سلبيات: يمكن أقل بكثير من النقر وغيرها من المحللون. واجهة برمجة تطبيقات غير مستقرة (الإصدار الحالي هو 0.2.1).
أسمنت
في الواقع ،
ليست Cement مكتبة CLI تمامًا ، بل إطار عمل لتطبيقات وحدة التحكم ، ولكن يُزعم أنها مناسبة للبرامج النصية والتطبيقات المعقدة ذات التكاملات المختلفة.
محلل
يبدو المحلل اللغوي في Cement غير عادي ، ولكن إذا نظرت عن كثب إلى المعلمات ، فمن السهل أن تتخيل أن argparse المألوفة تحت الغطاء. ولكن ربما يكون هذا هو الأفضل - لا حاجة لتعلم معايير جديدة.
شفرة المصدر from cement import Controller, ex class Base(Controller): class Meta: label = "base" arguments = [ ( ["-v", "--version"], {"action": "version", "version": f"todo_cement v{todolib.__version__}"}, ) ] def _default(self): """Default action if no sub-command is passed.""" self.app.args.print_help() @ex(help="Add new task", arguments=[(["task"], {"help": "Task title"})]) def add(self): title = self.app.pargs.task self.app.log.debug(f"Task title: {title!r}") task = self.app.todoobj.add_task(title) print(task, "created with number", task.number, end=".\n") @ex( help="Show current tasks", arguments=[ (["--show-done"], dict(action="store_true", help="Include done tasks")) ], ) def show(self): self.app.todoobj.print_tasks(self.app.pargs.show_done) @ex(help="Mark task as done", arguments=[(["number"], {"type": int})]) def done(self): task = self.app.todoobj.task_done(self.app.pargs.number) print(task, "marked as done.") @ex(help="Remove task from the list", arguments=[(["number"], {"type": int})]) def remove(self): task = self.app.todoobj.remove_task(self.app.pargs.number) print(task, "removed from the list.")
التطبيق والرئيسي
الاسمنت ، من بين أشياء أخرى ، لا يزال يلتف الإشارات في استثناءات. يظهر هذا هنا عند إخراج الشفرة الصفرية باستخدام SIGINT / SIGTERM.
شفرة المصدر class TodoApp(App): def __init__(self, argv=None): super().__init__(argv=argv) self.todoobj = None def load_db(self): self.todoobj = todolib.TodoApp.fromenv() def save(self): self.todoobj.save() class Meta:
إذا كنت تقرأ رئيسي ، يمكنك أن ترى أن تحميل وحفظ todolib.TodoApp يمكن أن يتم أيضا في نهاية __enter __ / __ exit__ ، ولكن تم فصل هذه المراحل في نهاية المطاف إلى طرق منفصلة من أجل إظهار السنانير الأسمنت.
تجريب
للاختبار ، يمكنك استخدام نفس فئة التطبيق:
def test_cement(capsys): with todo_cement.TodoApp(argv=["add", "test"]) as app: app.run() out, _ = capsys.readouterr() assert out == EXPECTED
النتائج
المميزات: مجموعة واجهات برمجة التطبيقات تشبه مجموعة من السكاكين السويسرية ، القابلية للتوسعة من خلال السنانير والإضافات ، واجهة مستقرة وتطوير نشط.
سلبيات: في الأماكن وثائق فارغة. قد تبدو النصوص البرمجية الصغيرة القائمة على الأسمنت معقدة بعض الشيء.
كليو
كليو أبعد
ما يكون عن كونه إطار عمل شائع مثل الآخرين المدرجين هنا (حوالي 400 نجم في جيثب في المجموع) ، ومع ذلك تمكنت من التعرف عليه عندما درست كيف نسق الشعر.
لذلك ، يعد Cleo أحد مشاريع مؤلف الشعر الذي تم ذكره بالفعل ، وهو أداة لإدارة التبعيات والمحاكاة الافتراضية وتصميمات التطبيقات. حول الشعر على habr بالفعل أكثر من مرة كتب ، وحول جزء وحدة التحكم - لا.
محلل
تم بناء Cleo ، مثل Cement ، على مبادئ الكائنات ، أي يتم تعريف الأوامر من خلال فئة الأوامر و docstring الخاصة بها ، يتم الوصول إلى المعلمات من خلال طريقة الخيار () ، وهلم جرا. بالإضافة إلى ذلك ، فإن طريقة السطر () ، والتي تُستخدم لإخراج النص ، تدعم الأنماط (أي الألوان) وتصفية الخرج استنادًا إلى عدد الأعلام المطوِّرة خارج الصندوق. كليو لديها أيضا جدول الإخراج. وكذلك تقدم القضبان. وبعد ... بشكل عام ، انظر:
شفرة المصدر from cleo import Command as BaseCommand
رئيسي
كل ما هو مطلوب هو إنشاء كائن
cleo.Application ثم تمرير الأوامر إلى add_commands إليه. لكي لا تتكرر أثناء الاختبار ، تم نقل كل هذا من الرئيسي إلى المُنشئ:
from cleo import Application as BaseApplication class TodoApp(BaseApplication): def __init__(self): super().__init__(name="ToDo app - cleo version", version=todolib.__version__) self.add_commands(AddCommand(), ShowCommand(), DoneCommand(), RemoveCommand()) def main(args=None): TodoApp().run(args=args)
تجريب
لاختبار الأوامر في Cleo ، يوجد
CommandTester ، مثل جميع
الأعمام البالغين
في الإطار ، يعترض I / O ورمز الخروج:
def test_cleo(): app = todo_cleo.TodoApp() command = app.find("add") tester = cleo.CommandTester(command) tester.execute("test") assert tester.status_code == 0 assert tester.io.fetch_output() == "Task test created with number 0.\n"
يؤدي
المميزات: بنية الكائن مع تلميحات الكتابة ، والتي تعمل على تبسيط عملية التطوير (نظرًا لأن العديد من IDEs والمحررين لديهم دعم جيد لرمز OOP ووحدة الكتابة) ؛ قدر كبير من الوظائف للعمل ليس فقط مع الوسائط ، ولكن أيضًا I / O.
Plus أو ناقص: معلمة الفعل الخاصة به ، والتي تتوافق فقط مع I / O Cleo / CliKit. على الرغم من أنه يمكنك كتابة معالج مخصص لوحدة التسجيل ، إلا أنه قد يكون من الصعب الحفاظ عليها مع تطور cleo.
سلبيات: من الواضح - رأي شخصي - واجهة برمجة تطبيقات مبتدئة: يفتقر الإطار إلى مستخدم "كبير" آخر ، باستثناء الشعر ، ويتطور كليو بالتوازي مع تطوره واحتياجاته ؛ في بعض الأحيان تكون المستندات قديمة (على سبيل المثال ، مستويات التسجيل الآن لا تكمن في وحدة clikit ، ولكن في clikit.api.io.flags) ، وبصفة عامة تكون ضعيفة ولا تعكس واجهة برمجة التطبيقات (API) بأكملها.
يركز Cleo ، مقارنةً بالاسمنت ، على CLI ، وهو الوحيد الذي فكر في التنسيق (إخفاء تتبع المكدس الافتراضي) للاستثناءات في الإخراج الافتراضي. لكنه - مرة أخرى رأي شخصي - يخسر أمام الأسمنت في شبابه واستقرار API.
في الختام
في هذه المرحلة ، يكون لكل شخص رأيه الخاص ، وهو أفضل ، ولكن يجب أن يكون الاستنتاج: أعجبني النقر أكثر ، لأن هناك الكثير من الأشياء فيه ومن السهل جدًا تطوير التطبيقات واختبارها. إذا حاولت كتابة التعليمات البرمجية إلى الحد الأدنى - ابدأ بالنار. يحتاج البرنامج النصي الخاص بك إلى الوصول إلى Memcached ، والتنسيق باستخدام jinja وقابلية التمدد - خذ Cement ولن تندم عليه. لديك مشروع للحيوانات الأليفة أو ترغب في تجربة شيء آخر - انظر إلى cleo.