جانغو تحت المجهر

إذا كان وفقًا لتقرير Artyom Malyshev ( بروفيت 404 ) سيقومون بعمل فيلم ، فسيكون المخرج هو Quentin Tarantino - لقد قام بالفعل بعمل فيلم واحد عن Django ، وسيقوم بتصوير الفيلم الثاني أيضًا. جميع التفاصيل من حياة آليات جانغو الداخلية من البايت الأول من طلب HTTP إلى البايت الأخير من الاستجابة. الروعة من أشكال المحلل اللغوي ، وتجميع معبأة من الإجراءات من SQL ، والمؤثرات الخاصة لتنفيذ محرك القالب ل HTML. من يدير تجمع الاتصال وكيف؟ كل هذا بالترتيب الزمني لمعالجة كائنات WSGI. على جميع شاشات البلاد - فك "Django تحت المجهر".



عن المتحدث: Artyom Malyshev هو مؤسس مشروع Dry Python والمطور الأساسي لإصدار Django Channels 1.0. كان يكتب بيثون منذ 5 سنوات وساعد في تنظيم اجتماعات بيثون رانتس في نيجني نوفغورود. قد تكون Artyom مألوفة لك تحت اللقب PROOFIT404 . يتم تخزين عرض التقرير هنا .


ذات مرة ، أطلقنا الإصدار القديم من Django. ثم بدت مخيفة وحزينة.



لقد رأوا أن self_check مر ، وقمنا بتثبيت كل شيء بشكل صحيح ، كل شيء يعمل ، ويمكنك الآن كتابة التعليمات البرمجية. لتحقيق كل هذا ، كان علينا تشغيل الأمر django-admin runserver .

 $ django-admin runserver Performing system checks… System check identified no issues (0 silenced). You have unapplied migrations; your app may not work properly until they are applied. Run 'python manage.py migrate1 to apply them. August 21, 2018 - 15:50:53 Django version 2.1, using settings 'mysite.settings' Starting development server at http://127.0.0.1:8000/Quit the server with CONTROL-C. 

تبدأ العملية وتعالج طلبات HTTP ويحدث كل السحر في الداخل ويتم تنفيذ كل الشفرة التي نريد أن نظهرها للمستخدمين كموقع.

التثبيت


يظهر django-admin على النظام عندما نقوم بتثبيت Django باستخدام ، على سبيل المثال ، pip ، مدير الحزمة .

 $ pip install Django # setup.py from setuptools import find_packages, setup setup( name='Django', entry_points={ 'console_scripts': [ 'django-admin = django.core.management:execute_from_command_line' ] }, ) 

يظهر entry_points setuptools ، مما يشير إلى وظيفة execute_from_command_line . هذه الوظيفة هي نقطة دخول لأي عملية مع Django ، لأي عملية حالية.

الحذاء


ماذا يحدث داخل الوظيفة؟ التمهيد ، الذي ينقسم إلى تكرارات.

 # django.core.management django.setup(). 

تكوين الإعدادات


الأول هو قراءة التكوينات :

 import django.conf.global_settings import_module(os.environ["DJANGO_SETTINGS_MODULE"]) 

global_settings الإعدادات الافتراضية global_settings ، ثم من متغير البيئة نحاول العثور على الوحدة النمطية مع DJANGO_SETTINGS_MODULE ، والتي كتبها المستخدم. يتم دمج هذه الإعدادات في مساحة اسم واحد.

أي شخص يكتب في جانغو على الأقل "مرحبًا ، عالم" يعرف أن هناك INSTALLED_APPS - حيث نكتب رمز المستخدم.

ملء التطبيقات


في الجزء الثاني ، يتم تكرار كل هذه التطبيقات ، أساسًا الحزم ، واحدة تلو الأخرى. نقوم بإنشاء لكل Config ، نستورد نماذج للعمل مع قاعدة بيانات ونتحقق من النماذج من أجل النزاهة. علاوة على ذلك ، يعمل الإطار على Check ، أي ، يتحقق من أن كل نموذج يحتوي على مفتاح أساسي ، وتشير جميع المفاتيح الخارجية إلى الحقول الموجودة وأن حقل Null غير مكتوب في BooleanField ، ولكن يتم استخدام NullBooleanField.

 for entry in settings.INSTALLED_APPS: cfg = AppConfig.create(entry) cfg.import_models() 

هذا هو الحد الأدنى من التعقل للتحقق من النماذج ، ولوحة المسؤول ، عن أي شيء - دون الاتصال بقاعدة البيانات ، دون أي شيء شديد التعقيد والمحددة. في هذه المرحلة ، لا يعرف Django بعد الأمر الذي طلبت تنفيذه ، أي أنه لا يميز migrate من runserver أو shell .

ثم نجد أنفسنا في وحدة نمطية تحاول تخمين وسيطات سطر الأوامر التي نرغب في تنفيذها وفي أي تطبيق يقع.

قيادة الإدارة


 # django.core.management subcommand = sys.argv[1] app_name = find(pkgutils.iter_modules(settings.INSTALLED_APPS)) module = import_module( '%s.management.commands.%s' % (app_name, subcommand) ) cmd = module.Command() cmd.run_from_argv(self.argv) 

في هذه الحالة ، سيكون لدى الوحدة النمطية django.core.management.commands.runserver وحدة django.core.management.commands.runserver . بعد استيراد الوحدة النمطية ، وفقًا للاتفاقية ، يتم استدعاء فئة Command العالمية داخل الأمر ، ويتم إنشاء مثيل لها ، ونقول: " لقد وجدت لك ، هنا لديك وسيطات سطر الأوامر التي مر بها المستخدم ، افعل شيئًا معهم ".

بعد ذلك ، نذهب إلى وحدة runerver ونرى أن Django مصنوع من "regexp and sticks" ، والتي سأتحدث عنها بالتفصيل اليوم:

 # django.core.management.commands.runserver naiveip_re = re.compile(r"""^(?: (?P<addr> (?P<ipv4>\d{1,3}(?:\.\d{1,3}){3}) | # IPv4 address (?P<ipv6>\[[a-fA-F0-9:]+\]) | # IPv6 address (?P<fqdn>[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*) # FQDN ):)?(?P<port>\d+)$""", re.X) 

الأوامر


مرر لأسفل شاشة واحدة ونصف - أخيرًا نتعرف على تعريف فريقنا الذي يقوم بتشغيل الخادم.

 # django.core.management.commands.runserver class Command(BaseCommand): def handle(self, *args, **options): httpd = WSGIServer(*args, **options) handler = WSGIHandler() httpd.set_app(handler) httpd.serve_forever() 

ينفذ BaseCommand الحد الأدنى من العمليات من أجل أن تؤدي وسيطات سطر الأوامر إلى وسيطات لاستدعاء الدالات *args و **options . نرى أنه يتم إنشاء مثيل خادم WSGI هنا ، يتم تثبيت WSGIHandler العالمية في خادم WSGI هذا - وهو بالضبط God Object Django . يمكننا القول أن هذا هو المثال الوحيد للإطار. يتم تثبيت مثيل على الخادم على مستوى العالم - من خلال set application ويقول: "تدور في حدث حلقة ، تنفيذ الطلبات."

هناك دائما حلقة حدث في مكان ما ومبرمج يعطيه المهام.

خادم WSGI


ما هو WSGIHandler ؟ WSGI هي واجهة تسمح لك بمعالجة طلبات HTTP بأقل مستوى من التجريد ، وتبدو وكأنها شيء في شكل دالة.

WSGI معالج


 # django.core.handlers.wsgi class WSGIHandler: def __call__(self, environ, start_response): signals.request_started.send() request = WSGIRequest(environ) response = self.get_response(request) start_response(response.status, response.headers) return response 

على سبيل المثال ، هنا هو مثيل لفئة بها call محددة. إنه ينتظر إدخال القاموس الخاص به ، حيث سيتم عرض الرؤوس على هيئة بايت ومعامل ملفات. هناك حاجة إلى معالج لقراءة <body> الطلب. يقدم الخادم نفسه أيضًا start_response رد الاتصال حتى نتمكن من إرسال response.headers start_response ، على سبيل المثال ، الحالة ، في حزمة واحدة.

علاوة على ذلك ، يمكننا تمرير نص الاستجابة إلى الخادم من خلال كائن الاستجابة. الاستجابة هي مولد يمكنك تكراره.

جميع الخوادم المكتوبة لـ WSGI - Gunicorn و uWSGI و Waitress تعمل على هذه الواجهة وقابلة للتبادل. نحن الآن نفكر في خادم للتطوير ، ولكن أي خادم يصل إلى النقطة التي في جانغو يقرع من خلال البيئة وإعادة الاتصال.

ما هو داخل كائن الله؟


ماذا يحدث داخل وظيفة الله العالمية هذه داخل جانغو؟

  • طلب.
  • MIDDLEWARES.
  • طلب مسار لعرض.
  • عرض - معالجة رمز المستخدم داخل العرض.
  • نموذج - العمل مع النماذج.
  • ORM.
  • نموذج
  • استجابة.

جميع الآلات التي نريدها من Django تتم داخل وظيفة واحدة ، والتي تنتشر عبر الإطار بأكمله.

طلب


نلف بيئة WSGI ، التي هي قاموس بسيط ، في بعض الأشياء الخاصة ، لراحة العمل مع البيئة. على سبيل المثال ، من الأسهل معرفة طول طلب المستخدم من خلال العمل باستخدام شيء مشابه للقاموس مقارنةً بسلسلة البايت التي يجب تحليلها والبحث عن إدخالات قيمة المفتاح فيها. عند العمل مع ملفات تعريف الارتباط ، لا أريد أيضًا أن أحسب يدويًا ما إذا كانت فترة التخزين قد انتهت أم لا ، وأن أترجمها بطريقة أو بأخرى.

 # django.core.handlers.wsgi class WSGIRequest(HttpRequest): @cached_property def GET(self): return QueryDict(self.environ['QUERY_STRING']) @property def POST(self): self._load_post_and_files() return self._post @cached_property def COOKIES(self): return parse_cookie(self.environ['HTTP_COOKIE']) 

يحتوي الطلب على موزعي ، فضلاً عن مجموعة من المعالجات للتحكم في معالجة نص طلب POST: سواء كان ملفًا في الذاكرة أو مؤقتًا في التخزين على القرص. كل شيء تقرر داخل الطلب. يُعد الطلب في جانغو كائنًا تجميعيًا حيث يمكن لجميع البرامج الوسيطة وضع المعلومات التي نحتاجها حول الجلسة والمصادقة وترخيص المستخدم. يمكننا أن نقول أن هذا هو أيضا كائن الله ، ولكن أصغر.

طلب آخر يحصل على الوسيطة.

Middlewares


الوسيطة عبارة عن غلاف يلتف وظائف أخرى مثل الديكور. قبل التخلي عن التحكم في الوسيطة ، في طريقة الاتصال نعطي استجابة أو ندعو الوسيطة ملفوفة بالفعل.

هذا ما يشبه الوسيطة من وجهة نظر مبرمج.

الإعدادات


 # settings.py MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', ] 

تحديد


 class Middleware: def __init__(self, get_response=None): self.get_response = get_response def __call__(self, request): return self.get_response(request) 

من وجهة نظر جانغو ، تبدو أنواع البرمجيات الوسيطة وكأنها نوع من المكدس:

 # django.core.handlers.base def load_middleware(self): handler = convert_exception_to_response(self._get_response) for middleware_path in reversed(settings.MIDDLEWARE): middleware = import_string(middleware_path) instance = middleware(handler) handler = convert_exception_to_response(instance) self._middleware_chain = handler 

تطبيق


 def get_response(self, request): set_urlconf(settings.ROOT_URLCONF) response = self._middleware_chain(request) return response 

نحن نأخذ وظيفة get_response الأولية ، get_response في معالج ، والتي سوف تترجم ، على سبيل المثال ، permission error ولن not found error في رمز HTTP الصحيح. نلف كل شيء في الوسيطة نفسها من القائمة. مكدس middlewares ينمو ، ولكل التالي يلف السابقة. يشبه هذا إلى حد كبير تطبيق كومة الديكور نفسها على جميع المشاهدات في المشروع ، مركزيًا فقط. لا حاجة للتنقل وترتيب الأغلفة بيديك وفقًا للمشروع ، فكل شيء مناسب ومنطقي.

مررنا بـ 7 دوائر من البرامج الوسيطة ، نجا طلبنا وقرر معالجة الأمر. كذلك نصل إلى وحدة التوجيه.

التوجيه


هذا هو المكان الذي نقرر فيه المعالج الذي يجب الاتصال به لطلب معين. وهذا حل:

  • بناء على عنوان url
  • في مواصفات WSGI ، حيث يتم استدعاء request.path_info.

 # django.core.handlers.base def _get_response(self, request): resolver = get_resolver() view, args, kwargs = resolver.resolve(request.path_info) response = view(request, *args, **kwargs) return response 

عناوين المواقع


نحن نأخذ محلل ، ونغذيها عنوان url للطلب الحالي ونتوقع منه إعادة وظيفة العرض نفسها ، ومن عنوان url نفسه نحصل على الوسيطات التي يمكن من خلالها استدعاء طريقة العرض. ثم عرض get_response المكالمات ، يعالج الاستثناءات ويفعل شيئًا معها.

 # urls.py urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<int:year>/', views.year_archive), path('articles/<int:year>/<int:month>/', views.month_archive) ] 

حل


هذا ما يبدو عليه المحلل:

 # django.urls.resolvers _PATH_RE = re.compile( r'<(?:(?P<converter>[^>:]+):)?(?P<parameter>\w+)>' ) def resolve(self, path): for pattern in self.url_patterns: match = pattern.search(path) if match: return ResolverMatch( self.resolve(match[0]) ) raise Resolver404({'path': path}) 

هذا هو أيضا regexp ، ولكن العودية. يتم عرضه في أجزاء من عنوان url ، ويبحث عن ما يريده المستخدم: مستخدمون آخرون ، أو منشورات ، أو مدونات ، أو هل هو نوع من المحول ، على سبيل المثال ، سنة معينة تحتاج إلى حل ، وطرحها في وسيطات ، أو تحويلها إلى int.

من المميزات أن عمق التكرار لطريقة العزم يساوي دائمًا عدد الوسائط التي تسمى طريقة العرض. إذا حدث خطأ ما ولم نعثر على عنوان url معين ، فسيحدث خطأ غير موجود.

ثم نصل في النهاية إلى النهاية - الرمز الذي كتبه المبرمج.

عرض


في أبسط تمثيل لها ، هي وظيفة تُرجع الطلب من الاستجابة ، لكن بداخلها نقوم بمهام منطقية: "من أجل ، إذا ، في يوم من الأيام" - العديد من المهام المتكررة. يوفر لنا Django عرضًا قائمًا على الفصل حيث يمكنك تحديد تفاصيل محددة ، وسيتم تفسير كل السلوك بالتنسيق الصحيح من خلال الفصل نفسه.

 # django.views.generic.edit class ContactView(FormView): template_name = 'contact.html' form_class = ContactForm success_url = '/thanks/' 

مخطط انسيابي


 self.dispatch() self.post() self.get_form() self.form_valid() self.render_to_response() 

طريقة dispatch الخاصة بهذا المثيل موجودة بالفعل في تعيين عنوان url بدلاً من دالة. يفهم Dispatch استنادًا إلى فعل HTTP الطريقة التي يجب الاتصال بها: POST جاء إلينا وعلى الأرجح نريد إنشاء مثيل لعنصر النموذج ، إذا كان النموذج صالحًا ، فاحفظه في قاعدة البيانات وإظهار القالب. يتم كل ذلك من خلال العدد الكبير من الخلطات التي تشكل هذه الفئة.

نموذج


يجب قراءة النموذج من المقبس قبل أن يدخل إلى عرض Django - من خلال نفس معالج الملفات الموجود في بيئة WSGI. بيانات النموذج هي دفق بايت ، حيث يتم وصف فواصل - يمكننا قراءة هذه الكتل وصنع شيء منها. يمكن أن تكون المراسلات ذات القيمة الأساسية ، إذا كان حقلًا ، جزءًا من ملف ، ثم مرة أخرى بعض الحقول - كل شيء مختلط.

 Content-Type: multipart/form-data;boundary="boundary" --boundary name="field1" value1 --boundary name="field2"; value2 

محلل


يتكون المحلل اللغوي من 3 أجزاء.

يتحول مكرر القطعة الذي ينشئ القراءات المتوقعة من دفق البايت إلى مكرر يمكنه إنتاج boundaries . إنه يضمن أنه إذا عاد شيء ما ، فسيكون الحد. يعد هذا ضروريًا حتى لا يكون من الضروري داخل المحلل تخزين حالة الاتصال أو القراءة من المقبس أو عدم قراءته لتقليل منطق معالجة البيانات.

بعد ذلك ، يلتف المولد في LazyStream ، مما يؤدي إلى إنشاء ملف كائن مرة أخرى ، ولكن مع القراءة المتوقعة. حتى يتمكن المحلل اللغوي من السير بالفعل عبر قطع من البايتات وإنشاء قيمة مفتاح منها.

الحقل والبيانات هنا ستكون دائما سلاسل . إذا تلقينا وقت بيانات بتنسيق ISO ، فسيتلقى نموذج Django (الذي كتبه المبرمج) ، باستخدام حقول معينة ، على سبيل المثال ، الطابع الزمني.

 # django.http.multipartparser self._post = QueryDict(mutable=True) stream = LazyStream(ChunkIter(self._input_data)) for field, data in Parser(stream): self._post.append(field, force_text(data)) 

علاوة على ذلك ، على الأرجح ، يريد النموذج حفظ نفسه في قاعدة بيانات ، وهنا يبدأ Django ORM.

ORM


يتم تنفيذ طلبات DSL تقريبًا من أجل ORM:

 # models.py Entry.objects.exclude( pub_date__gt=date(2005, 1, 3), headline='Hello', ) 

باستخدام المفاتيح ، يمكنك جمع تعبيرات SQL مماثلة:

 SELECT * WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello') 

كيف الحال؟

Queryset


تحتوي طريقة exclude على كائن Query أسفل الغطاء. يتم تمرير الكائن وسيطات إلى الدالة ، ويقوم بإنشاء تسلسل هرمي للكائنات ، يمكن لكل منها تحويل نفسه إلى جزء منفصل من استعلام SQL كسلسلة.

عند اجتياز الشجرة ، يقوم كل قسم باستقصاء العقد التابعة له ، ويتلقى استعلامات SQL متداخلة ، ونتيجة لذلك ، يمكننا إنشاء SQL كسلسلة. على سبيل المثال ، لن تكون قيمة المفتاح عبارة عن حقل SQL منفصل ، ولكن ستتم مقارنتها بقيمة القيمة. يعمل تسلسل ورفض الاستعلامات بالطريقة نفسها مثل اجتياز شجرة متكررة ، لكل عقدة يُطلق عليها cast to SQL.

 # django.db.models.query sql.Query(Entry).where.add( ~Q( Q(F('pub_date') > date(2005, 1, 3)) & Q(headline='Hello') ) ) 

مترجم


 # django.db.models.expressions class Q(tree.Node): AND = 'AND' OR = 'OR' def as_sql(self, compiler, connection): return self.template % self.field.get_lookup('gt') 

الإخراج


 >>> Q(headline='Hello') # headline = 'Hello' >>> F('pub_date') # pub_date >>> F('pub_date') > date(2005, 1, 3) # pub_date > '2005-1-3' >>> Q(...) & Q(...) # ... AND ... >>> ~Q(...) # NOT … 

يتم تمرير مترجم مساعد صغير إلى هذه الطريقة ، والتي يمكن أن تميز لهجة MySQL من PostgreSQL وترتيب السكر النحوي الذي يتم استخدامه في لهجة قاعدة بيانات معينة بشكل صحيح.

توجيه DB


عندما تلقينا استعلام SQL ، يقرع النموذج في توجيه قاعدة البيانات ويسأل عن قاعدة البيانات الموجودة فيه. في 99 ٪ من الحالات ، ستكون قاعدة البيانات الافتراضية ، في 1 ٪ المتبقية - نوعا من خاصة بها.

 # django.db.utils class ConnectionRouter: def db_for_read(self, model, **hints): if model._meta.app_label == 'auth': return 'auth_db' 

يؤدي التفاف برنامج تشغيل قاعدة بيانات من واجهة مكتبة معينة ، مثل Python MySQL أو Psycopg2 ، إلى إنشاء كائن عالمي يمكن لـ Django التعامل معه. هناك غلاف للمؤشرات ، المجمع للمعاملات.

ربط بركة


 # django.db.backends.base.base class BaseDatabaseWrapper: def commit(self): self.validate_thread_sharing() self.validate_no_atomic_block() with self.wrap_database_errors: return self.connection.commit() 

في هذا الصدد بالذات ، نرسل طلبات إلى المقبس يطرق قاعدة البيانات وينتظر التنفيذ. سوف يقرأ المجمع الموجود على المكتبة الاستجابة البشرية من قاعدة البيانات في شكل سجل ، ويقوم Django بجمع مثيل النموذج من هذه البيانات في أنواع Python. هذا ليس تكرارًا معقدًا.

لقد كتبنا شيئًا ما في قاعدة البيانات ، وقمنا بقراءة شيء ، وقررنا إخبار المستخدم به باستخدام صفحة HTML. للقيام بذلك ، يحتوي Django على لغة قالب لم يعجبها المجتمع والتي تبدو وكأنها لغة برمجة ، فقط في ملف HTML.

قالب


 from django.template.loader import render_to_string render_to_string('my_template.html', {'entries': ...}) 

كود


 <ul> {% for entry in entries %} <li>{{ entry.name }}</li> {% endfor %} </ul> 

محلل


 # django.template.base BLOCK_TAG_START = '{%' BLOCK_TAG_END = '%}' VARIABLE_TAG_START = '{{' VARIABLE_TAG_END = '}}' COMMENT_TAG_START = '{#' COMMENT_TAG_END = '#}' tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))) 

مفاجأة - regexp مرة أخرى. فقط في النهاية يجب أن يكون هناك فاصلة ، والقائمة سوف تذهب بعيدا. قد يكون هذا هو أصعب إعادة استرجاع رأيته في هذا المشروع.

ليكسر


معالج القوالب والمترجم بسيط للغاية. هناك lexer يستخدم regexp لترجمة النص إلى قائمة الرموز الصغيرة.

 # django.template.base def tokenize(self): for bit in tag_re.split(template_string): lineno += bit.count('\n') yield bit 

نكررها على قائمة الرموز ، انظر: "من أنت؟ لفّك في عقدة علامة. " على سبيل المثال ، إذا كانت هذه بداية لبعض if أو من for أو من for ، فسوف يأخذ معالج العلامات المعالج المناسب. يخبر المعالج نفسه المحلل: "اقرأ لي قائمة الرموز المميزة وصولاً إلى علامة الإغلاق".

يذهب العملية إلى المحلل اللغوي مرة أخرى.

العقدة والعلامة والمحلل هي أشياء متكررة ، وعادة ما يساوي عمق العودية تداخل القالب نفسه بالعلامات.

محلل


 def parse(): while tokens: token = tokens.pop() if token.startswith(BLOCK_TAG_START): yield TagNode(token) elif token.startswith(VARIABLE_TAG_START): ... 

يعطينا معالج العلامات عقدة محددة ، على سبيل المثال ، مع حلقة ، والتي تظهر طريقة render .

للحلقة


 # django.template.defaulttags @register.tag('for') def do_for(parser, token): args = token.split_contents() body = parser.parse(until=['endfor']) return ForNode(args, body) 

للعقدة


 class ForNode(Node): def render(self, context): with context.push(): for i in self.args: yield self.body.render(context) 

طريقة render هي شجرة تجسيد. يمكن أن تذهب كل عقدة عليا إلى عقدة ابنة ، واطلب منها تقديمها. يتم استخدام المبرمجين لإظهار بعض المتغيرات في هذا القالب. يتم ذلك من خلال context - يتم تقديمه في شكل قاموس منتظم. هذه مجموعة من القواميس لمحاكاة نطاق عندما ندخل علامة. على سبيل المثال ، إذا كان context نفسه يغير بعض العلامات الأخرى داخل حلقة for ، فعندما نخرج من الحلقة ، سيتم إرجاع التغييرات. هذا مناسب لأنه عندما يكون كل شيء عالميًا ، فإنه من الصعب العمل.

الرد


أخيرًا ، توصلنا إلى استجابة HTTP:

مرحبا العالم!

يمكننا إعطاء الخط للمستخدم.

  • إرجاع هذا الرد من العرض.
  • عرض قوائم البرامج الوسيطة.
  • Middlewares هذا الرد تعديل ، تكملة وتحسين.
  • تبدأ الاستجابة في التكرار داخل WSGIHandler ، تتم كتابتها جزئيًا إلى المقبس ، ويتلقى المستعرض استجابة من خادمنا.

بدأت جميع الشركات الناشئة الشهيرة التي كُتبت في جانغو ، مثل Bitbucket أو Instagram ، بدورة صغيرة جداً مرت بها كل مبرمج.

كل هذا ، وعرض تقديمي في Moscow Python Conf ++ ، ضروري لك لفهم أفضل لما بين يديك وكيفية استخدامه. في أي سحر ، هناك جزء كبير من regexp يجب أن تكون قادرًا على الطهي.

سوف يقدم لنا أرتيوم ماليشيف و 23 متحدثًا آخر كبيرًا في 5 أبريل مرةً أخرى الكثير من الطعام للتفكير والمناقشة حول موضوع بيثون في مؤتمر موسكو بيثون كونف ++ . دراسة الجدول الزمني والانضمام إلى تبادل الخبرات في حل مجموعة متنوعة من المشاكل باستخدام بيثون.

Source: https://habr.com/ru/post/ar433482/


All Articles