Программирование Web-клиента на языке Python. Язык программирования Python: разработка веб-приложений для нового поколения

12.07.2019

Программирование Web-клиента — мощная техника для создания запросов в Web. Web-клиент — это любая программа, извлекающая данные с Web-сервера при помощи протокола передачи гипертекста (Hyper Text Transfer Protocol, http в ваших URL). Web-броузер является клиентом, так же, как и поисковики, то есть программы, автоматически перемещающиеся по Web для сбора информации. Вы можете также применять Web-клиенты для использования возможностей сервисов, предлагаемых другими обитателями Web, и добавления динамических свойств в ваш собственный Web-сайт.

Программирование Web-клиента входит в любой набор инструментов для разработчиков. Приверженцы Perl’а используют его многие годы. В языке Python этот процесс достигает даже более высоких уровней удобства и гибкости. Большинство необходимых вам функций обеспечивается тремя модулями: HTTPLIB, URLLIB и новым дополнением, XMLRPCLIB. В истинно Питоновском стиле каждый модуль надстроен над своим предшественником, обеспечивая таким образом прочную, хорошо спроектированную базу для ваших приложений. В этой статье мы рассмотрим первые два модуля, оставив XMLRPCLIB на потом.

Для наших примеров мы будем использовать Meerkat. Если вы похожи на меня, вы тратите время на отслеживание тенденций и событий в среде создателей открытых программных средств, которые позволят вам получить конкурентные преимущества. Meerkat представляет собой инструмент, значительно упрощающий эту задачу. Это служба открытого доступа (an open wire service), собирающая и упорядочивающая огромные объемы информации по открытым программным средствам. Поскольку его интерфейс для браузера гибок и настраиваем, то, используя программирование web-клиента, мы можем сканировать, извлекать и даже сохранять эту информацию для последующего использования в автономном режиме. Сначала мы обратимся к Meerkat с помощью HTTPLIB в интерактивном режиме, а затем перейдем к работе с Meerkat’s Open API через URLLIB, чтобы создать настраиваемое средство сбора информации.

HTTPLIB

HTTPLIB представляет собой простую обертку вокруг модуля socket. Из трех упомянутых мною библиотек, HTTPLIB обеспечивает наибольший контроль при обращении к web-сайту. Это дается, однако, за счет увеличения объема работы, необходимого для выполнения вашей задачи. Протокол http не имеет текущего состояния ("stateless") и поэтому ничего не помнит о ваших предыдущих запросах. При соединении с Web-сайтом для каждого запроса вы должны построить новый объект HTTPLIB. Эти запросы образуют диалог с Web-сервером, подражая Web-браузеру. Давайте интерактивно подсоединимся к Meerkat с помощью Open API Рейла Дорнфеста (Rael Dornfest) и посмотрим, что получится. Диалог начинается с построения серии предложений, определяющих сначала, какое действие вы хотите предпринять, а затем идентифицирующее вас для Web-сервера:

>>> import httplib >>> host = "www.oreillynet.com" >>> h = httplib.HTTP(host) >>> h.putrequest("GET", "/meerkat/?_fl=minimal") >>> h.putheader("Host", host) >>> h.putheader("User-agent", "python-httplib") >>> h.endheaders() >>>

Запрос GET сообщает серверу, какую страницу вы хотите получить. Заголовок Host сообщает ему имя запрашиваемого вами домена. Современные сервера, использующие HTTP 1.1, могут иметь несколько доменов по одному и тому же адресу. Если вы не говорите им, какой домен вам нужен, в качестве кода возврата вы получите код переадресации ‘302’. Заголовок User-agent сообщает серверу, к какому типу клиента вы относитесь, чтобы знать, что он может вам посылать, а что нет. Это вся информация, необходимая для обработки вашего запроса Web-сервером. Далее вы запрашиваете ответ:

>>> returncode, returnmsg, headers = h.getreply() >>> if returncode == 200: #OK ... f = h.getfile() ... print f.read() ...

В результате этого будет распечатана текущая страница Meerkat в минимальном виде. Заголовок отклика и содержимое возвращаются отдельно друг от друга, что помогает как в определении и устранении проблем, так и в разборе данных. Если вы хотите увидеть заголовки отклика, используйте print headers.

HTTPLIB скрывает механику программирования сокетов, и использование им файлового объекта для буферизации позволяет вам применять привычный подход к манипуляции данными. Тем не менее, лучше он подходит как базовый блок для построения более мощных Web?клиентских приложений или для интерактивного общения с проблемным Web-сайтом. Для использования в обеих этих областях, HTTPLIB оснащен полезной возможностью отладки. Доступ к ней вы получаете, вызывая метод h.set_debuglevel(1) в любой момент после инициализации объекта (строка h = httplib.HTTP(host) в нашем примере). С уровнем отладки 1, модуль будет дублировать на экран запросы и результаты любых обращений к getreply().

Интерактивная природа Python делает процесс анализа Web-сайтов с помощью HTTPLIB развлечением. Привыкните к этому модулю, и у вас будет мощный и гибкий инструмент для диагностики проблем Web-сайтов. Кроме того, потратьте время на то, чтобы просмотреть исходники HTTPLIB. HTTPLIB, содержащий менее 200 строк кода, — быстрое и простое вступление к программированию сокетов с использованием Python.

URLLIB

URLLIB обеспечивает изощренный интерфейс к функциональности HTTPLIB. Лучше всего использовать его непосредственно для получения данных, а не для анализа Web-сайта. Здесь представлено то же взаимодействие, что и выше, но с использованием URLLIB:

>>> import urllib >>> u = urllib.urlopen("http://www.oreillynet.com/meerkat/?_fl=minimal")

Вот и все, что нужно сделать! Одной строкой вы обратились к Meerkat, получили данные и поместили их во временный кэш. Для доступа к заголовку:

>>> print u.headers

И для просмотра всего файла:

>>>print u.read()

Но это еще не все. В дополнение к HTTP, URLLIB может таким же образом обращаться к FTP, Gopher и даже к локальным файлам. Модуль содержит также множество вспомогательных функций, включая те, что используются для разбора url, кодирования строк в url-безопасный формат и обеспечения индикации хода процесса во время пересылки большого объема данных.

Пример использования Meerkat

Представьте, что у вас есть группа клиентов, ожидающих, что их будут информировать по почте о последних событиях, касающихся Linux. Мы можем написать короткий скрипт с использованием URLLIB для получения этой информации из Meerkat, построить список ссылок и сохранить эти ссылки в файл для последующей передачи. Автор Meerkat, Рэйел Дорнфест, уже сделал большую часть работы за нас в Meerkat API. Все, что нам осталось — это сконструировать запрос, разобрать ссылки и сохранить результаты для последующей передачи.

Зачем делать все это вместо того, чтобы просто отдать Meerkat на откуп пользователям? Обеспечение такого "пассивного" обслуживания дает пользователям возможность просматривать информацию на досуге и избирательно сохранять информацию в знакомом формате (например, в формате электронной почты). Получая новости в свой почтовый ящик утром в понедельник, они не пропустят информацию, прошедшую за выходные.

Поскольку минимальный вариант выборки Meerkat ограничен 15 новостями, мы будем запускать скрипт каждый час (например, как задание cron под Unix, или, используя команду AT под NT) для уменьшения вероятности потери данных. Вот url, который мы будем использовать (результаты использования этого URL вы можете посмотреть здесь).

Это объединит все новости о Linux (profile=5) за последний час, представляя данные в минимальном варианте, без описаний, информации о категории, канале и дате. Мы также используем модуль регулярных выражений, чтобы извлечь информацию о ссылках и перенаправить вывод в файловый объект, открытый в режиме добавления.

Вывод

Мы только коснулись поверхности этих модулей, а существуют еще множество других доступных для Python модулей сетевого программирования, которые могут быть использованы для задач, связанных с Web-клиентом. Программирование Web-клиента особенно полезно при обработке больших объемов табличных данных. Используя программирование Web-клиента в последнем проекте Обмена Электронными Данными (Electronic Data Interchange project), мы избежали использования громоздкого пакета патентованных программ. Мы извлекали необходимую нам обновленную информацию о ценах напрямую из Web и помещали ее в нашу базу данных. Это сэкономило нам массу времени и нервов.

Программирование Web-клиента может быть полезно и для тестирования структуры и целостности Web-сайтов. Наиболее распространенная процедура заключается в проверке неработающих ссылок. Стандартный дистрибутив Python включает в себя полный пример такой проверки, основанной на URLLIB. Webchecker вместе с основанным на Tk внешним интерфейсом можно найти в подкаталоге tools в дистрибутиве. Другой инструмент языка Python, Linbot, еще совершеннее. Он обеспечивает все, что вам нужно для решения проблем с Web-сайтом. По мере того, как сайты становятся все сложнее, другие Web-клиентские приложения становятся все необходимее для обеспечения качества вашего Web-сайта.

В программировании Web-клиента есть ловушка. Ваши программы часто чувствительны к небольшим изменениям в форматировании страниц. То, как сайт показывает свои данные сегодня, может отличаться от того, как он будет показывать их завтра. Когда изменяется формат, должны изменяться и ваши программы. Это одна из причин популярности XML: для данных в web, отмеченных тэгами, отражающими их значение, формат менее важен. По мере того, как стандарты XML будут развиваться и становиться общепринятыми, обработка XML-данных будет еще проще и надежнее.

Проще всего создать динамические страницы на Python при помощи CGI-скриптов. CGI-скрипты - это исполняемые файлы, которые выполняются веб-сервером, когда в URL запрашивается соответствующий скрипт.

Сегодня я расскажу про то, как написать Hello world, как CGI-скрипт.

Настройка локального сервера

В Python уже есть встроенный CGI сервер, поэтому его настройка элементарна.

Для запуска из консоли (для любителей linux-систем). Запускать нужно из той папки, где мы хотим работать:

Python3 -m http.server --cgi

Для сидящих на Windows чуть проще будет запуск Python файла (заметьте, что он должен находиться в той же папке, в которой мы планируем работать! ):

from http.server import HTTPServer , CGIHTTPRequestHandler server_address = ("" , 8000 ) httpd = HTTPServer (server_address , CGIHTTPRequestHandler ) httpd . serve_forever ()

Теперь откройте браузер и в адресной строке наберите localhost:8000

Если у вас примерно такая же картина, значит, у вас все заработало!

Hello world

Теперь в той папке, где мы запустили сервер, создаём папку cgi-bin (у меня она уже создана).

В этой папке создаём скрипт hello.py со следующим содержимым:

#!/usr/bin/env python3 print ("Content-type: text/html" ) print () print ("

Hello world!

" )

Первая строка говорит о том, что это Python скрипт (CGI-скрипты можно не только на Python писать).

Вторая строка печатает заголовок. Он обозначает, что это будет html файл (бывает ещё css, javascript, pdf и куча других, и браузер различает их по заголовкам).

Третья строка (просто символ новой строки) отделяет заголовки от тела ответа.

Четвёртая печатает Hello world.

Теперь переходим на localhost:8000/cgi-bin/hello.py

И радуемся!

Если у вас не работает, проверьте, установлены ли права на выполнение.

Также в консоли запущенного сервера появляются сообщения об ошибках. Например, убрал скобочку и обновил страницу:

В следующей части мы рассмотрим обработку данных форм и cookies.

Среды Web-разработки на языке Python, Часть 1

Разработка для Web с помощью Django и Python

Среда Web-разработки Django на языке Python, от установки до законченного Web-сайта

Серия контента:

Проект Django представляет собой настраиваемую пользователем среду разработки. Он берёт начало из Web-сайта одной онлайн-газеты и был выпущен как система с открытым исходным кодом в июле 2005 года. Главные компоненты среды разработки Django следующие:

  • Объектно-реляционное отображение (object-relational mapping) для создания моделей
  • Безупречный интерфейс администратора, специально созданный для конечных пользователей
  • Элегантно спроектированный механизм адресования (URL)
  • Язык шаблонов, для дизайнеров
  • Система кэширования

Эта статья - первая из двух статей о среде Web-разработки на языке Python. Вторая статья представит вам среду разработки TurboGears.

Чтобы использовать и понимать код в данной статье, вам необходимо установить Python и знать, как им пользоваться на начальном уровне. Проверьте, есть ли у вас Python, и какой версии, набрав python -V . Минимальным требованием Django является версия 2.3.5, которая доступна вам на Web-сайте Python (см. ссылку в разделе в конце статьи). Вам также следует хотя бы немного быть знакомым с архитектурой MVC.

Установка Django

В этой статье используется разрабатываемая версия Django для того, чтобы воспользоваться преимуществами последних улучшений, внесённых в среду разработки Django. Я рекомендую вам использовать эту версию до появления версии 0.95. Узнайте о наличии последней версии Django на его Web-сайте (снова см. ссылку в ).

Чтобы скачать и установить Django вам нужно:

Листинг 1. Скачивание и установка Django
~/downloads# svn co http://code.djangoproject.com/svn/django/trunk/ django_src ~/downloads# cd django_src ~/downloads# python setup.py install

Инструмент администрирования Django

После установки Django вам необходим инструмент администрирования Django, а именно django-admin.py, добавленный в переменную окружения path. Листинг 2 показывает некоторые из команд, доступных инструменту администратора:

Листинг 2. Использование инструмента администрирования Django
~/dev$ django-admin.py usage: django-admin.py action actions: adminindex Prints the admin-index template snippet for the given model module name(s). ... snip ... startapp Creates a Django app directory structure for the given app name in the current directory. startproject Creates a Django project directory structure for the given project name in the current directory. validate Validates all installed models. options: -h, --help show this help message and exit --settings=SETTINGS Python path to settings module, e.g. "myproject.settings.main". If this isn"t provided, the DJANGO_SETTINGS_MODULE environment variable will be used. --pythonpath=PYTHONPATH Lets you manually add a directory the Python path, e.g. "/home/djangoprojects/myproject".

Проекты и приложения в Django

Чтобы начать проект Django, используйте команду программы django-admin startproject вот так:

Листинг 3.Создание проекта
~/dev$ django-admin.py startproject djproject

Показанная выше команда создает директорию под названием djproject, которая содержит файлы базовой конфигурации, необходимые для работы проекта Django:

Листинг 4. Содержание директории djproject
__init__.py manage.py settings.py urls.py

В этом проекте вы создадите приложение для отдела занятости, называемое "jobs." Для создания приложения используйте скрипт manage.py , который является специальным для проекта скриптом в django-admin.py, в котором автоматически задаётся файл settings.py:

Листинг 5. Использование manage.py с параметром startapp
~/dev$ cd djproject ~/dev/djproject$ python manage.py startapp jobs

Это создает костяк приложения с одним модулем Python для ваших моделей и другим для ваших видов (view). Директория jobs будет содаржать следующие файлы:

Листинг 6. Содержание директории приложения jobs
__init__.py models.py views.py

Местоположение приложения внутри проекта является чистой условностью, созданной для новых разработчиков в Django, а не требованием. Как только вы начнете перемешивать и сопоставлять приложения в нескольких проектах, вы можете поместить приложения в их собственные пространства имён для модулей и связать их вместе, используя настройки и главные файлы URL. Но пока, следуйте приведенным ниже этапам.

Чтобы Django знал о новом приложении, вам необходимо добавить строку в поле INSTALLED_APPS в файле settings.py. Для этого приложения для отдела занятости нужно добавить строку djproject.jobs:

Листинг 7. Добавление строки к settings.py
INSTALLED_APPS = ("django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.sites", "djproject.jobs",)

Создание модели

У Django есть своя собственная библиотека object-relational mapper (ORM), которая поддерживает динамический доступ к базам данных через объектный интерфейс Python. Интерфейс Python является очень удобным и эффективным, но вы также можете отказаться от него и напрямую использовать SQL при необходимости.

На данный момент ORM предоставляет поддержку для баз данных PostgreSQL, MySQL, SQLite, а также Microsoft® SQL.

Это пример использует SQLite, в качестве интерфейса к базе данных. SQLite является легковесной базой данных, не требующей конфигурации и располагается на диске в виде простого файла. Чтобы использовать SQLite, просто установите библиотеку pysqlite с помощью инструментов установки (setuptools) (см. дополнительную информацию об инструментах установки (setuptools) и, в частности, об инструменте easy_install , который вам необходимо установить отдельно, в разделе ):

easy_install pysqlite

Перед тем, как работать с моделью, сконфигурируйте базу данных в файле настроек. Для SQLite необходимо указывать только движок и имя базы данных.

Листинг 8. Конфигурирование базы данных в settings.py
DATABASE_ENGINE = "sqlite3" DATABASE_NAME = "/path/to/dev/djproject/database.db" DATABASE_USER = "" DATABASE_PASSWORD = "" DATABASE_HOST = "" DATABASE_PORT = ""

Это приложение для отдела занятости будет иметь два типа объектов: Местоположения (Locations) и Работы (Jobs). Местоположение включает в себя поля города, штата (по желанию), а также страны. Работа имеет местоположение, должность, описание и дату публикации.

Листинг 9. Модуль jobs/models.py
from django.db import models class Location(models.Model): city = models.CharField(maxlength=50) state = models.CharField(maxlength=50, null=True, blank=True) country = models.CharField(maxlength=50) def __str__(self): if self.state: return "%s, %s, %s" % (self.city, self.state, self.country) else: return "%s, %s" % (self.city, self.country) class Job(models.Model): pub_date = models.DateField() job_title = models.CharField(maxlength=50) job_description = models.TextField() location = models.ForeignKey(Location) def __str__(self): return "%s (%s)" % (self.job_title, self.location)

Метод __str__ - это специальный метод класса в языке Python, который возвращает строковое представление объекта. Django достаточно широко использует этот метод, когда отображает объекты в инструменте администратора.

Чтобы увидеть схему базы данных для модели, запустите manage.py с командой sql . Схема пока еще не будет задействована.

Листинг 10. Просмотр схемы базы данных с помощью команды manage.py sql
~/dev/djproject$ python manage.py sql jobs BEGIN; CREATE TABLE "jobs_job" ("id" integer NOT NULL PRIMARY KEY, "pub_date" date NOT NULL, "job_title" varchar(50) NOT NULL, "job_description" text NOT NULL, "location_id" integer NOT NULL); CREATE TABLE "jobs_location" ("id" integer NOT NULL PRIMARY KEY, "city" varchar(50) NOT NULL, "state" varchar(50) NULL, "country" varchar(50) NOT NULL); COMMIT;

Для инициализации и установки модели, запустите команду синхронизирования базы данных syncdb:

~/dev/djproject$ python manage.py syncdb

Отметим, что команда syncdb просит вас создать учетную запись суперпользователя. Это происходит потому, что приложение django.contrib.auth, которое предоставляет базовые средства аутентификации пользователя, установлено по умолчанию в ваших настройках INSTALLED_APPS. Имя суперпользователя и пароль будут использоваться для регистрации в инструменте администратора, описанном в следующем разделе. Помните, что это имя и пароль суперпользователя Django, а не системы.

Наборы запросов

Модели Django обращаются к базе данных через изначально установленный Управляющий (Manager) класс, называемый objects . Например, чтобы вывести список всех Работ, вам нужно использовать метод all , принадлежащий менеджеру objects:

Листинг 11. Вывод всех работ (jobs)
>>> from jobs.models import Job >>> for job in Job.objects.all(): ... print job

Управляющий класс также имеет методы фильтрации, называемые filter (фильтрация) и exclude (исключение). Фильтрация получает все объекты, подходящие под условия, тогда как исключение дает все объекты, не подходящие под них. Нижеследующие запросы должны дать такие же результаты ("gte" означает "greater than or equal,"(больше или равно) и "lt" означает "less than")(меньше чем).

Листинг 12. Исключение и фильтрация работ (jobs)
>>> from jobs.models import Job >>> from datetime import datetime >>> q1 = Job.objects.filter(pub_date__gte=datetime(2006, 1, 1)) >>> q2 = Job.objects.exclude(pub_date__lt=datetime(2006, 1, 1))

Методы filter и exclude возвращают объекты QuerySet, которые можно связать в цепочку и которые могут даже представлять соединения. Запрос q4 , приведенный ниже, будет находить работы, начиная с 1-го января 2006 года, в г. Кливленд (Cleveland), штат Огайо:

Листинг 13. Снова исключение и фильтрация работ (jobs)
>>> from jobs.models import Job >>> from datetime import datetime >>> q3 = Job.objects.filter(pub_date__gte=datetime(2006, 1, 1)) >>> q4 = q3.filter(location__city__exact="Cleveland", ... location__state__exact="Ohio")

Это хорошо, что QuerySets ленивы. Это означает, что они не выполняются в базе данных, пока не произошла их оценка, и благодаря чему работают гораздо быстрее, чем мгновенные запросы.

Эта леность является очень практичной в сочетании с рассекающей (slicing) функциональностью языка Python. Вместо того, чтобы запрашивать все записи, а затем отсекать нужные записи, нижеследующий код использует смещение (OFFSET), равное 5, и предел (LIMIT), равный 10, в настоящем запросе SQL, весьма улучшая качество выполнения.

Листинг 14. Python-рассечение
>>> from jobs.models import Job >>> for job in Job.objects.all() ... print job

Примечание: Используйте метод count , чтобы понять, сколько записей находятся в QuerySet. Метод len в Python делает полную оценку, а затем подсчитывает количество рядов, возращенных в виде записей, тогда как метод count делает действительный Подсчет (COUNT) SQL, который гораздо быстрее. И ваш администратор баз данных поблагодарит вас.

Листинг 15. Подсчет записей
>>> from jobs.models import Job >>> print "Count = ", Job.objects.count() # GOOD! >>> print "Count = ", len(Job.objects.all()) # BAD!

Для более подробной информации, см. раздел для ссылки на "Справочник по интерфейсу API базы данных" для Django.

Инструмент администратора

Одно из самых больших достоинств в Django - это отточенный интерфейс администратора. Этот инструмент был создан с учетом конечного пользователя, благодаря чему предоставляет вашим проектам отличное средство для ввода данных.

Инструмент администратора представляет собой приложение, которое идет вместе с Django. Перед тем, как его использовать, его необходимо установить так же, как и приложение jobs . Сперва нужно добавить модуль приложения (django.contrib.admin) в настройки INSTALLED_APPS:

Листинг 16. Изменения в settings.py
INSTALLED_APPS = ("django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.sites", "djproject.jobs", "django.contrib.admin",)

Чтобы сделать инструмент администратора доступным из адреса (URL) /admin, просто раскройте скобки комментария в строке, в предоставленном вам файле urls.py вашего проекта. В следующем разделе будет рассматриваться конфигурация URL более подробно.

Листинг 17. Использование urls.py, чтобы иструмент администратора стал доступным
from django.conf.urls.defaults import * urlpatterns = patterns("", (r"^admin/", include("django.contrib.admin.urls.admin")),)

Пиложение администратора имеет свою модель базы данных, которую необходимо установить. Снова используйте команду syncdb , чтобы завершить следующее:

python manage.py syncdb

Чтобы увидеть инструмент администратора, вы можете использовать тест-сервер, которая прилагается к Django.

Листинг 18. Использование тест-сервера, чтобы увидеть инструмент администратора
~/dev/djproject$ python manage.py runserver Validating models... 0 errors found. Django version 0.95 (post-magic-removal), using settings "djproject.settings" Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C (Unix) or CTRL-BREAK (Windows).

Теперь вы можете переместиться в инструмент администратора по адресу http://localhost:8000/admin и зарегистрироваться, используя данные администратора, созданные вами ранее. Вы заметите, что ни одна из ваших моделей не доступна для пользования.

Чтобы открыть доступ к классу через инструмент администратора, создайте для него подкласс Admin (Администратора). Затем вы можете настраивать то, как их можно администрировать добавлением характеристик классов к данному подклассу. Листинг 19 показывает, как добавлять класс Location (Местонахождения) к интсрументу администратора.

Листинг 19. Добасвление класса Location с помощью инструмента администратора
class Location(meta.Model): ... class Admin: list_display = ("city", "state", "country")

Теперь вы можете создавать, обновлять и удалять записи Местонахождения (Location) через интерфейс администратора.

Рисунок 1. Изменение местонахождений с помощью инструмента администратора

Вы можете выводить и сортировать Записи (Records) по городам, штатам и странам, в зависимости от характеристики класса list_display .

Рисунок 2. Вывод местонахождений с помощью инструмента администратора

Инструмент администратора имеет большое количество опций для управления каждым из классов моделей. Листинг 20 показывает несколько примеров, примененных к классу Job:

Листинг 20. Опции для управления классами моделей
class Job(meta.Model): ... class Admin: list_display = ("job_title", "location", "pub_date") ordering = ["-pub_date"] search_fields = ("job_title", "job_description") list_filter = ("location",)

В соответствие с вышеупомянутыми настройками, при выводе записей работ (job) будут использоваться: заголовок работы (job), местонахождения и дата издания. Работы будут располагаться в порядке даты издания, начиная с самого последнего (знак минус означает - в порядке убывания). Пользователи могут найти работы по заголовку и описанию, а администраторы могут фильтровать записи, исходя из их местонахождения.

Рисунок 3. Вывод работ с помощью инструмента администратора

Проектирование своей схемы URL

Система передачи адресов URL в Django использует модули конфигураций регулярных выражений, которые отображают строковые шаблоны URL в методах языка Python, называемые видами . Эта система позволяет совершенно четко отделять адреса URL от кода, что делает их максимально легкими для управления.

Модуль urls.py создается и определяется в качестве начала отсчета конфигурации URL (с помощью значения ROOT_URLCONF в модуле settings.py). Единственное требование для файла конфигурации URL состоит в том, что он должен содержать объект, определяющий шаблоны, называемые urlpatterns .

Приложение управления работой (job) будет начинаться с index-видом и детальным (detail) видом, которые доступны через настоящие отображения URL:

  • /jobs index-вид: Отображает последние 10 работ
  • /jobs/1 детальный вид: Отображает работы с одним идентификационным номером (ID)

Оба этих вида (index и детальный вид) будут выполнены в модуле, называемом views.py в приложении работ. Выполнение этой конфигурации в файле urls.py проекта будут выглядеть следующим образом:

Листинг 21. Выполнение конфигурации видов в djproject/urls.py
from django.conf.urls.defaults import * urlpatterns = patterns("", (r"^admin/", include("django.contrib.admin.urls.admin")), (r"^jobs/$", "djproject.jobs.views.index"), (r"^jobs/(?P

Отметьте кусочек кода . Это понадобиться позже.

На практике лучше всего - вытаскивать шаблоны URL, специальные для приложений, и помещать их в само приложение. Это отделяет приложение от проекта и позволяет повторно его использовать. Файл конфигурации URL уровня приложений для работ (jobs) будут выглядеть так:

Листинг 22. Файл конфигурации URL уровня приложений, urls.py
from django.conf.urls.defaults import * urlpatterns = patterns("", (r"^$", "djproject.jobs.views.index"), (r"^(?P\d+)/$", "djproject.jobs.views.detail"),)

С тех пор, как методы видов исходят из одного модуля, для настройки djproject.jobs.views в качестве корневого имени модуля можно использовать первый аргумент, и Django использует его для того, чтобы искать методы index и detail:

Листинг 23. jobs/urls.py: поиск index и detail
from django.conf.urls.defaults import * urlpatterns = patterns("djproject.jobs.views", (r"^$", "index"), (r"^(?P\d+)/$", "detail"),)

Чтобы связать в одно целое адреса (URL), перечисленных выше работ (job), назад в проект, необходимо использовать функцию include . Адреса (URL) уровня приложений теперь связаны в разделе /jobs:

Листинг 24. djproject/urls.py: Связывание адресов (URL) обратно в проект
from django.conf.urls.defaults import * urlpatterns = patterns("", (r"^admin/", include("django.contrib.admin.urls.admin")), (r"^jobs/", include("djproject.jobs.urls")),)

Если вы попытаетесь войти на начальную (index) страницу (http://localhost:8000/jobs) на этом этапе, используя тест-сервер, вы получите ошибку, так как вид, который будет называться (djproject.jobs.views.index), еще не существует.

Выполнение видов

Вид представляет собой простой метод языка Python, который принимает объект отклика и отвечает за:

  • Любую бизнес-логику (явно или неявно)
  • Контекстный словарь с данными для шаблона
  • Рендеринг (rendering) шаблона с контекстом
  • Объект отклика, который пропускает отрендеренные (rendered) результаты обратно в среду разработки

В Django вызванный метод языка Python, когда запрашивается URL, называется видом , а страница, закаченная и отрендеренная, видом называется шаблоном . По этой причине, команда разработчиков Django говорила о Django, как о среде разработки с MVT (model-view-template - модель-вид-шаблон). С другой стороны, TurboGears называет свои методы контроллерами , а отрендеренные шаблоны видами , что действительно позволяет использовать аббревиатуру MVC. Разница лишь в использовании слов, но смысл остается тем же.

Самый простейший из возможных видов возвращает объект HttpResponse (http отклик), инициализированный в строковый тип. создайте следующий метод и сделайте из /jobs HTTP-запрос, чтобы проверить, что ваши файлы urls.py и views.py правильно настроены.

Листинг 25. jobs/views.py (v1)
from django.http import HttpResponse def index(request): return HttpResponse("Job Index View")

Следующий код получает последние 10 работ (job), рендерит их через шаблон и возвращает отклик. Это не будет работать без файла шаблона из .

Листинг 26. jobs/views.py (v2)
from django.template import Context, loader from django.http import HttpResponse from jobs.models import Job def index(request): object_list = Job.objects.order_by("-pub_date")[:10] t = loader.get_template("jobs/job_list.html") c = Context({ "object_list": object_list, }) return HttpResponse(t.render(c))

В вышеприведенном коде шаблон назван, как строковый jobs/job_list.html Рендеринг шаблона происходит в контексте списка работ (job), названном object_list . Отрендеренный шаблон строки попадает в конструктор HTTPResponse, который отправляется назад в клиент запроса с помощью среды разработки.

Этапы загрузки шаблона, создающие контекст и возвращающие новый объект отклика, перемещены ниже с помощью удобного метода, называемого render_to_response . Также новым является метод детального (detail) вида, который использует удобный для этого метод get_object_or_404 , чтобы осуществить выборку объекта Job, используя приведенные аргументы. Если объект не найден, то выдается ошибка 404. Эти два метода избавляют программу от большого количества стандартных кодов, используемых в большинстве Web-приложений.

Листинг 27. jobs/views.py (v3)
from django.shortcuts import get_object_or_404, render_to_response from jobs.models import Job def index(request): object_list = Job.objects.order_by("-pub_date")[:10] return render_to_response("jobs/job_list.html", {"object_list": object_list}) def detail(request, object_id): job = get_object_or_404(Job, pk=object_id) return render_to_response("jobs/job_detail.html", {"object": job})

Отметим, что detail берет object_id в качестве аргумента. Этот номер упоминался раньше, после URL-пути /jobs/ к файлу работ (job) urls.py. Он проходит дальше в метод get_object_or_404 в качестве первичного ключа (primary key - pk).

Приведенные выше виды все еще не будут работать, потому что шаблоны, которые они загружают и рендерят (jobs/job_list.html и jobs/job_detail.html) еще не существуют.

Создание шаблонов

Django предоставляет простой язык шаблонов, спроектированный для быстрого рендеринга и простоты использования. Шаблоны Django создаются из простого текста, встроенного в {{ переменные }} и {% тэги %} . Затем происходит замена переменных на значения, которые они несут. Тэги используются в качестве основы логики управления. Шаблоны можно использовать для генерирования любого формата текстов, включая HTML, XML, CSV и простой текст.

Первое, что нужно сделать - это определить местонахождение шаблонов. Для простоты, создайте директорию для шаблонов внутри директории djproject и добавьте его путь в строке TEMPLATE_DIRS файла settings.py:

Листинг 28. Создание директории шаблонов в settings.py
TEMPLATE_DIRS = ("/path/to/devdir/djproject/templates/",)

Шаблоны в Django поддерживают концепцию, называемую наследованием шаблонов , которая позволяет дизайнерам сайтов создавать однородный вид и функциональность без повторения содержания в каждом из шаблонов. Вы можете использовать наследование, определяя документ скелета, или основы, с блочными тэгами. Эти блочные тэги заполняются шаблонами страниц с содержанием. Этот пример показывает скелет кода на HTML с блоками, называемыми title , extrahead и content:

Листинг 29. Документ скелета, templates/base.html
Company Site: {% block title %}Page{% endblock %} {% block extrahead %}{% endblock %} {% block content %}{% endblock %}

Чтобы приложение было отдельно от проекта, используйте вспомагательный файл основы, в качестве основы для всех файлов страниц приложения Job. Для этого примера, поместите CSS приложения в файл основы для упрощения. В реальном приложении, с хорошо сконфигурированным Web-сервером, возьмите данный CSS и поместите его в статичный файл, обслуживаемый Web-сервером.

Листинг 30. Вспомогательный файл основы, templates/jobs/base.html
{% extends "base.html" %} {% block extrahead %} {% endblock %}

Изначально, тест-сервер в Django не обслуживает статичные файлы, потому что эту работу должен выполнять Web-сервер. Если во время разработки вы захотите, чтобы Django работал с картинками, листами стилей и так далее, тогда см. ссылку в о том, как включить эту возможность.

Теперь создайте 2 шаблона страниц для загрузки и рендеринга. Шаблон jobs/job_list.html просто выполняет итерацию через object_list (список объектов), который он получает через контекст, выдаваемый index-видом, а также отображает ссылку на каждую из страниц с подробной информацией о записи.

Листинг 31. Шаблон templates/jobs/job_list.html
{% extends "jobs/base.html" %} {% block title %}Job List{% endblock %} {% block content %}

Job List

    {% for job in object_list %}
  • {{ job.job_title }}
  • {% endfor %}
{% endblock %}

Страница jobs/job_detail.html показывает одну запись, называемую job (работа):

Листинг 32. Страница templates/jobs/job_detail.html
{% extends "jobs/base.html" %} {% block title %}Job Detail{% endblock %} {% block content %}

Job Detail

{{ job.job_title }} - {{ job.location }}
Posted: {{ job.pub_date|date:"d-M-Y" }}
{{ job.job_description }}
{% endblock %}

Язык шаблонов в Django был спроектирован с ограниченными функциональными возможностями. Эта ограниченность позволяет сохранять простоту шаблонов для непрограммистов и ограждает программистов от использования бизнес-логики там, где ее не должно быть, в слое представления. См. ссылку на документацию по языку шаблонов в .

Универсальные виды

Django идет вместе с четырьмя наборами универсальных видов , которые позволяют разработчикам создавать приложения, следующие типичным шаблонам:

  • Страницы со списком/подробностями (как вышеприведенный пример)
  • Дробление записей по дате (полезно для новостных сайтов и блогов)
  • Создание, обновление и удаление - СОУ (Creation, update, and deletion - CRUD) объектов
  • Простой прямой рендеринг шаблонов или простое HTTP-перенаправление

Вместо создания методов стандартных видов, вся бизнес-логика находится в файле urls.py и управляется с помощью методов универсальных видов, поддерживаемых Django.

Листинг 33. Универсальные виды в jobs/urls.py
from django.conf.urls.defaults import * from jobs.models import Job info_dict = { "queryset": Job.objects.all(), } urlpatterns = patterns("django.views.generic.list_detail", (r"^$", "object_list", info_dict), (r"^(?P\d+)/$", "object_detail", info_dict),)

Три важных изменения в данном файле urls.py:

  • Объект отображения info_dict проходит через набор запросов, чтобы был открыт доступ для Jobs.
  • Он использует django.views.generic.list_detail вместо djproject.jobs.views .
  • Действительно вызванные виды - это object_list и object_detail .

Этот проект следует некоторым требованиям, чтобы переход к универсальным видам произошел автоматически:

  • Универсальный детальный (detail) вид ожидает аргумент с именем object_id .
  • Шаблоны следуют наименованию: app_label /model_name _list.html (jobs/job_list.html) app_label /model_name _detail.html (jobs/job_detail.html)
  • Шаблон списка управляет списком с именем object_list .
  • Детальный шаблон управляет объектом с именем object .

Больше опций можно задать через info_dict , включая значение paginate_by , которое определяет число объектов на странице.

Заключение

Следующая статья этого выпуска будет рассматривать TurboGears, другую среду Web-разработки на языке Python, и будет сравнивать его с Django.

  • PHP ,
  • Python
  • Введение

    В статье хотелось бы поднять вопросы отличия использования Python для web-разработки по сравнению с оной на PHP. Надеюсь, статья не приведет к холиварам, так как она вовсе не о том, какой язык лучше или хуже, а исключительно о технических особенностях Python.

    Немного о самих языках

    PHP - веб-ориентированный язык, (в хорошем смысле слова). С низкоуровневой точки зрения приложение на PHP представляет собой скорее набор отдельных скриптов возможно с единой семантической точкой входа.

    Python - универсальный язык программирования, применимый в том числе и в вебе. С технической точки зрения web-приложение на Python - полноценное приложение, загруженное в память, обладающее своим внутренним состоянием, сохраняемым от запроса к запросу.

    Исходя из вышеописанных особенностей вытекают и различия в обработке ошибок в web-приложениях. В PHP существует целый зоопарк типов ошибок (errors, exceptions), далеко не каждую из которых можно перехватить, хотя это (невозможность перехвата) и не имеет большого значения, так как приложение живет ровно столько, сколько обрабатывается один запрос. Неперехваченная ошибка просто приводит к досрочному выходу из обработчика, и удалению приложения из памяти. Новый запрос будет обрабатываться новым «чистым» приложением. В Python же приложение постоянно находится в памяти, обрабатывая множество запросов без «перезагрузки». Таким образом поддерживать правильное предсказуемое состояние приложения крайне важно. Все ошибки используют стандартный механизм исключений и могут быть перехвачены (разве что за исключением SyntaxError). Неперехваченная ошибка приведет к завершению приложения, которое понадобится перезапускать извне.

    Существует множество способов «приготовить» PHP и Python для веба. Далее я остановлюсь на двух наиболее мне знакомых (и кажется наиболее популярных) - PHP + FastCGI (php-fpm) и Python + WSGI (uWSGI). Конечно же, перед обоими этими связками предполагается наличие фронтенд-сервера (например, Nginx).

    Поддержка многопоточности Python

    Запуск сервера приложений (например, uWSGI) приводит к загрузке интерпретатора Python в память, а затем загрузке самого web-приложения. Обычно bootstrap-модуль приложения импортирует необходимые ему модули, производит вызовы инициализации и в итоге экспортирует подготовленный callable объект, соответствующий спецификации WSGI. Как известно, при первом импорте Python-модулей, код внутри них исполняется, в том числе создаются и инициализируются значениями переменные. Между двумя последовательными HTTP-запросами состояние интерпретатора не сбрасывается, следовательно сохраняются значения всех переменных уровня модуля.

    Напишем простейшее WSGI-приложение, которое наглядно продемонстрирует вышеописанное на примере:

    N = 0 def app(env, start_response): global n n += 1 response = "%.6d" % n start_response("200 OK", [("Content-Type", "text/plain")]) return
    Здесь n является переменной модуля и она будет создана со значением 0 при загрузке приложения в память следующей командой:

    Uwsgi --socket 127.0.0.1:8080 --protocol http --single-interpreter --processes 1 -w app:app
    Само приложение просто выводит на страницу значение переменной n . Для заядлых PHP программистов оно выглядит бессмысленным, так как «должно» каждый раз выводить на страницу строку «000001».

    Проведем тест:

    Ab -n 500 -c 50 http://127.0.0.1:8080/ curl http://127.0.0.1:8080
    В результате мы получим строку «000501» , что подтверждает наше утверждение, о том, что приложение находится загруженным в память uwsgi и сохраняет свое состояние между запросами.

    Если запустить uWSGI с параметром --processes 2 и провести тот же тест, то несколько последовательных вызовов curl покажут, что мы имеем уже 2 различные возрастающие последовательности. Так как ab посылает 500 запросов, примерно половина из них приходится на один процесс uWSGI, а остальные - на второй. Ожидаемые значения, возращаемые curl будут примерно «000220» и «000280» . Интерпретатор Python, судя по всему, один на процесс, и мы имеем 2 независимых окружения и реальную параллельную обработку запросов (в случае многоядерного процессора).

    Python поддерживает потоки, как часть языка. Классическая реализация (CPython) использует нативные потоки OS, но есть GIL - в один момент времени выполняется только один поток. При этом все равно возможны проблемы race condition, так как даже n += 1 не является атомарной операцией.

    Дизассемблируем python код

    «Дизассемблируем» наше WSGI-приложение:
    import dis n = 0 def app(env, start_response): global n n += 1 return if "__main__" == __name__: print(dis.dis(app))
    8 0 LOAD_GLOBAL 0 (n) 3 LOAD_CONST 1 (1) 6 INPLACE_ADD 7 STORE_GLOBAL 0 (n) 10 10 LOAD_GLOBAL 1 (bytes) 13 LOAD_GLOBAL 2 (str) 16 LOAD_GLOBAL 0 (n) 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 LOAD_CONST 2 ("utf-8") 25 CALL_FUNCTION 2 (2 positional, 0 keyword pair) 28 BUILD_LIST 1 31 RETURN_VALUE
    Видно, что инкремент в нашей программе занимает 4 операции. Прерывание GIL может наступить на любой из них.


    Увеличение количества потоков при отсуствии ожидания IO в коде обработчиков HTTP-запросов не приводит к ускорению обработки (а скорее даже ее замедляет, так как потоки могут «толкаться», переключая контексты). Реальной параллельности потоки в следствие ограничеия GIL не создают, хоть и являются не green thread"ами, а настоящими потоками OS.

    Проведем еще один тест. Запустим uwsgi с 1 процессом, но 10 потоками-обработчиками в нем:

    Uwsgi --socket 127.0.0.1:8080 --protocol http --single-interpreter --processes 1 --threads 10 -w app:app
    и выполним с помощью ab 5000 запросов к приложению.

    Ab -n 5000 -c 50 http://127.0.0.1:8080/
    Последующие запросы curl 127.0.0.1 :8080 покажут, что мы имеем только одну возрастающую последовательность, значение которой <= 5000 (меньше 5000 оно может быть в случае race condition на инкременте).

    Влияние языка на архитектуру приложения

    Каждый HTTP-запрос обрабатывается в отдельном потоке (справедливо и для процессов, так как процесс имеет минимум 1 поток). При этом каждый поток за время своей жизни (которое в идеальных условиях совпадает со временем жизни всего uwsgi приложения) обрабатывает множество HTTP-запросов, сохраняя свое состояние (т.е. значения переменных уровня модулей) от запроса к запросу. В этом заключается чуть ли не основное отличие от модели обработки HTTP-запросов в PHP, где каждый запрос приходит новое только что проинициализированное окружение и загрузку приложения необходимо выполнять каждый раз заново.

    Типичным подходом в крупных web-приложениях на PHP является использование Dependency Injection Container для управления инициализацией и доступом к уровню сервисов приложения. Наглядным примером является Pimple . На каждый HTTP-запрос первым делом выполняется код инициализации, регистрирующий все доступные сервисы в контейнере. Далее по мере необходимости осуществляется доступ к объектом сервисов (lazy) в контроллерах. Каждый сервис может зависеть от других сервисов, зависимости разрешаются опять же через контейнер в коде инициализации сервиса-агрегата.

    // Определяем сервисы $container["session_storage"] = function ($c) { return new SessionStorage("SESSION_ID"); }; $container["session"] = function ($c) { return new Session($c["session_storage"]); }; // Используем сервисы class MyController { public function __construct() { // get the session object $this->session = $container["session"]; // "тестриуемость" страдает, но не суть } }
    Благодаря контейнеру, можно обеспечить единовременное создание объектов и возвращение уже готовых объектов на каждое последующее обращение к сервису (если необходимо). Но эта магия работает только в рамках одного HTTP-запроса, поэтому сервисы можно без проблем инициализировать специфичными для запроса значениями. Такие значения зачастую - это текущий авторизованный пользователь, сессия текущего пользователя, собственно HTTP-запрос и пр. В конце запроса сервисы все равно будут разрушены, а в начале обработки следующего запроса - созданы и проинициализированы новые. К тому же можно практически не беспокоиться об утечках памяти, если обработка одного HTTP-запроса умещается в отведенные для скрипта лимиты, так как создание сервисов происходит по требованию (lazy) и на один запрос каждый необходимый сервис скорее всего будет создан только в единственном экземпляре.

    Теперь, принимая во внимание вышеописанную поточную модель Python, можно заметить, что использование аналогичного подхода в Python web-приложении не возможно без дополнительных усилий. Если контейнер будет являться переменной уровня модуля (что выглядит вполне логично), все сервисы, которые он содержит, не могут быть проинициализированы специфичными для текущего запроса значениями, так как сервис будет являться разделяемым ресурсом между несколькими потоками, выполяющими обработку нескольких HTTP-запросов псевдо-параллельно. На первый взгляд существует два способа справиться с этой проблемой - сделать объекты сервисов независимыми от текущего HTTP-запроса (зависимыми остануться вызовы методов сервисов, а стековые переменные, используемые в методах, не являются разделяемыми ресурсами) или же сделать контейнер - ресурсом потока, а не процесса (тогда каждый поток будет общаться только со своим независимым набором сервисов, а в один момент времени один поток может обрабатывать только один HTTP-запрос).

    Кажущийся плюс первого подхода - сервисы инициализируются только один раз за все время жизни uwsgi процесса. Возможна также экономия памяти (так как имеем только один набор сервисов на все потоки). С другой стороны обработка конкретного HTTP-запроса требует лишь какого-то (скорее всего небольшого) поднможества всех доступных сервисов. Если же приложение достаточно большое и обладает внушительным количеством сервисов, то после обработки определенного числа HTTP-запросов, подавляющее большинство сервисов окажется проинициализировнными и находящимися в памяти процесса. Выглядит так, что это может оказаться серьезной проблемой.

    Второй подход можно реализовать с использованием

    Здравствуйте, хабровчане!

    Сегодня мы тут припозднились, но все-таки продолжаем развивать тему языка Python, по которому у нас готовится новая книга, и допечатывается . На сей раз мы хотели затронуть интересную тему перехода с PHP на Python, попутно рассмотрев некоторые фреймворки Python, поговорить о веб-разработке на этих языках и, конечно же, дать вам пищу для размышлений.

    Приятного чтения и хороших выходных!

    Что «говорят люди»

    Программисты, имеющие мнение о том, как обстоят дела в Интернете, охотно высказываются и по поводу PHP. Время от времени кто-нибудь напишет такую филиппику , в которой расскажет, чем плох PHP , и почему все должны избегать этого языка. Вам посоветуют перейти на Java, Ruby, Python – на что угодно, только бы слезть с PHP. Хотя я не могу полностью осудить PHP, притом, что некоторое время назад по пояс увязал в импровизированной базе кода на этом языке, слова «красивый», «выразительный» и «поистине объектно-ориентированный» пробуждают во мне некие чувства, которые PHP решительно не удовлетворяет, при всех своих неловких и противоречивых попытках «исправиться».

    Но когда я, наконец, перемахнул через забор и отправился туда, где трава зеленее, я ожидаемо обнаружил, что и на райском лугу кое-где попадаются проплешины. Более того, я обнаружил, что переход на другие языки означает для PHP-программиста серьезный сдвиг парадигмы. Поскольку я не видел, чтобы кто-нибудь уже об этом рассказывал, решил сам написать для вас эту путеводную статью.

    Что же такое PHP?

    Если вы начали карьеру веб-разработчика с языка PHP, как и я, то, вероятно, разделяете мои предубеждения о том, что представляет собой веб-разработка. Но давайте для начала выясним, что такое язык PHP.

    Система шаблонов

    В основе своей PHP – это система шаблонов. Вероятно, когда-то ее укусил радиоактивный паук, и она мутировала в массивный объектно-ориентированный язык – но, в сущности, так и осталась системой шаблонов. Смысл этого утверждения лучше всего прояснить на следующем примере: что происходит при обращении к PHP-документу в браузере?

    • Веб-сервер (например, Apache) получает запрос на PHP-документ
    • Веб-сервер запускает PHP, передавая запрошенный файл и некоторые детали о запросе
    • PHP обрабатывает запрошенный файл, дампит все «обязательные» или «включенные» файлы, а также интерпретирует разряды “”, когда они встречаются.
    • PHP возвращает интерпретированный документ (теперь уже на чистом HTML/XML/CSS т.д.) на веб-сервер, который отправляет его в браузер.

    Вот почему можно записывать HTML прямо в PHP-файл (вне тегов), и он будет работать. В принципе, PHP – это HTML-файл (даже если в нем нет «сырого» HTML), доказательство тому – предварительная обработка документа перед выдачей.

    Фреймворк

    Кроме того, PHP – своего рода минимальный фреймворк. Он оказывает вам кое-какие любезности (например, разбирает HTTP-запросы, преобразуя их в аккуратные массивы вроде $_GET и $_POST) и предоставляет гигантскую библиотеку дополнительных функций — например, для соединения с базой данных и обработки изображений.

    Важно осознавать это и понимать, что PHP как таковой (без учета каких-либо сторонних аддонов или уровней абстрагирования) уже в готовом виде решал за веб-разработчика массу проблем. Эта поддержка временами была хороша, временами – не очень; но, в любом случае, проблемы редко решаются единственным способом. Легко принять за истину, что PHP решает задачи единственно верным, каноническим образом. Привыкните к тому, что это не так.

    Плох, но не так уж плох.

    Как я уже говорил, люди всегда будут клясть PHP — можете даже не сомневаться. Если вы в состоянии прочесть статью «PHP: фрактал некачественного проектирования », а затем с чистой совестью программировать на нем дальше – что ж, вы проявили твердость. У PHP действительно есть недостатки, от мелких прыщиков до серьезных структурных проблем.

    Но, в конечном итоге, дело за вами, вашим софтом и пользователями вашего софта. Потенциальные недостатки PHP — пустая академическая мишура, пока не отражаются на этих трех китах; предположу, что в большинстве случаев действительно не отражаются. Не каждый пытается написать новое браузерное супер-пупер приложение, которое откроет путь к Web 3.0. Некоторые люди просто хотят автоматизировать простые операции у себя на персональном сайте, либо построить простую CRUD-систему для данных, которыми пользуются на работе. Не ведитесь на ненависть к PHP и не отказывайтесь от нормального инструмента, который вполне неплохо решал ваши проблемы, не требуя дополнительной настройки.

    Что такое Python, и как написать на нем веб-приложение?

    Python – универсальный язык программирования. В нем просто куча библиотек, и на этом языке можно решать самые разные задачи, но, в принципе, этот язык не затачивался под какие-либо конкретные цели.

    Кроме того, работать с ним – одно удовольствие.

    Однако он не является ни веб-фреймворком (даже минимальным), ни системой шаблонов. Если вы планируете применять Python для веб-разработки примерно столь же активно, как и PHP, то Python потребуется дополнить этими вещами.

    Я упоминаю об этом, поскольку в мире PHP также есть свои фреймворки и системы шаблонов – например, Zend Framework или CakePHP. Лично я использовал CodeIgniter на двух-трех сравнительно крупных проектах, и нашел его очень полезным… поначалу. Поддерживая код (и совершенствуя непрерывную интеграцию) пару лет подряд, я разочаровался в этом фреймворке. Обнаружил, что все, что мне требовалось от фреймворка, на деле достигается при помощи пары сотен строк вспомогательных функций при условии аккуратного планирования структуры классов. Итак, мне «фреймворк» поначалу показался огромной пушкой для стрельбы по воробьям.

    Если вы ощущали нечто подобное, то поймете, что в Python потребуется использовать фреймворк – хотя бы самый скромный – так как (напоминаю) вы уже привыкли к работе с минимальным фреймворком под названием PHP.

    То же касается системы шаблонов. Сначала я думал, «люди, чего вы возитесь с этой системой шаблонов», но, в самом деле, если вы не фанат больших кусков HTML с «несинтаксической» подсветкой в своем логическом коде (наверняка не фанат!), то придется работать с системой шаблонов.

    Итак, усвоив это, давайте рассмотрим некоторые фреймворки и системы шаблонов.

    Веб-фреймворки Python

    Едва подавшись в мир фреймворков Python вы немедленно окажетесь перед выбором, напоминающим обоюдоострый клинок. Фреймворков здесь – десятки , от минимальных и низкоуровневых до выраженно абстрагированных программных пакетов, прибамбасов в которых больше, чем на поясе у Бэтмена. Я работал всего с несколькими из них, поэтому остановлюсь на тех, которые знаю — примерно в том порядке, в котором я их изучал.

    mod_python и серверные страницы python

    Прежде, чем я «освоил» веб-разработку на Python, я, разумеется, рассматривал ее с точки зрения PHP-разработчика. Поэтому сразу проникся mod_python и серверными страницами Python (PSP). Такая конструкция позволяет работать с Python примерно так, как вы привыкли программировать на PHP: пишете сценарий, и Apache выполняет его в нужном месте, как только получит запрос GET или POST на этот сценарий. Данный сценарий может представлять собой как чистый Python, так и html-файл со встроенными PSP (в принципе, PSP – это система шаблонов, для работы с ней просто вставляете теги (с разделителем <% %>), как привыкли делать в PHP).

    На первый взгляд все кажется замечательным, но со временем вы осознаете, что практически не улучшили структуру и безопасность вашего приложения по сравнению с PHP, зато променяли все веб-ориентированные вкусности PHP на язык с чуть более (ладно, гораздо более) качественным синтаксисом. В общем, из пустого в порожнее, поэтому я никогда и не видел большой пользы в переходе на mod_python.
    По-видимому, сообщества Python и Apache со мной согласны, так как в 2010 году проект mod_python был закрыт. Невелика потеря, скажу я вам.

    Если произнести в переполненной комнате «Веб-фреймворк Python?» то, вполне возможно, они просто недоуменно на вас посмотрят, но если кто-то и ответит, то, вероятно, скажет: «Django». Некоторые считают Django единственным веб-фреймворком Python, достойным обсуждения.
    Django – это полноценный фреймворк; в нем встроено практически все необходимое, и со всем этим сразу можно работать. Там есть и шаблоны, и абстракция базы данных/объектно-реляционное отображение, сеансы и безопасность и даже собственный веб-сервер.

    Несмотря на многообразные возможности и популярность, Django мне не приглянулся по нескольким причинам:

    Во-первых, мне в самом деле не импонирует работа в стиле «все, что вам когда-либо может понадобиться, у нас уже есть». Я предпочитаю более мелкие, модульные инструменты, при помощи которых могу легко выстраивать код.

    Во-вторых, поскольку это один из тех фреймворков, где проект запускается при помощи специальной команды «init», которая выдает вам целое дерево сгенерированного кода, посреди которого вы пишете свое приложение. Возможно, я ошибаюсь, но по опыту такая ситуация чревата неудобными обновлениями, которые приходится точечно инъецировать в код приложения.

    Наконец (и здесь я тоже могу ошибаться) мне показалось, что Django задумывался для управления структурой и кодом базы данных, поэтому не очень хорошо будет работать с имеющейся базой данных. Поскольку передо мной очень часто ставились задачи написать альтернативные клиентские интерфейсы или инструменты отчетности для готовых баз данных (зачастую доступных только для чтения), мне он не подходит.
    Повозившись с Django несколько дней, я все бросил и вернулся к PHP.

    CherryPy

    После Django меня по нескольким причинам заинтересовал CherryPy. Во-первых, он не генерирует такого большого дерева трафаретного кода, а представляет собой просто библиотеку, которую нужно импортировать. Гораздо важнее, что он не привязан к конкретной системе шаблонов или уровню базы данных, поэтому вы можете использовать любые их варианты (или не использовать вообще). Знакомясь с ним, я все еще считал, что «не нужна мне никакая система шаблонов», а проект, за который я брался, вообще не имел отношения к базе данных. Поэтому такой минимальный фреймворк идеально мне подошел.

    Мне удалось написать приятную утилиту, которой я до сих пор пользуюсь при работе, но ощущения остались противоречивые. Спишите это на мою неопытность в веб-разработке на Python, но, в целом, у меня возникли две проблемы с проектом на CherryPy:

    1. Существует как минимум два или три способа делать определенные вещи — например, настраивать конфигурационные опции, и документация свободно комбинирует эти способы. Один и тот же метод мог срабатывать, но не всегда; поэтому создавалось впечатление, что код мой противоречив.
    2. Многие вещи в программе требуют некой автомагической комбинации имен функций и переменных; такова, например, сеансовая аутентификация. Мне было очень сложно выполнить эту работу правильно, а отладочные сообщения не помогали.

    Может быть, со временем и опытом, подробнее изучив документацию CherryPy, я бы его освоил, но в данном случае меня ждала срочная работа, которую пришлось делать на PHP.

    Наконец, я открыл Flask. Подобно CherryPy, это простая библиотека, которую всего лишь нужно импортировать — и можно пользоваться любой системой шаблонов или уровнем базы данных, что вам приглянутся (хотя, складывается впечатление, что Flask хорошо интегрирован с jinja2). Пока Flask меня вполне устраивает, он более прямолинеен, чем CherryPy, без всей этой автомагии, связанной с именами функций и переменных. Мне пришлось попотеть над тем, как он анализирует данные из сложных форм (по-видимому, не любит вложенные массивы). Кроме того, он помог мне осознать собственную потребность в языке шаблонов, а также узнать практически все необходимое о Jinja2 буквально за десять минут.

    Системы шаблонов Python

    Не буду распространяться здесь о системах шаблонов, так как до сих пор мне доводилось использовать лишь Jinja2; но здесь хотелось бы дать совет таким же PHP-разработчикам, как и я, которые еще полагают, что не нуждаются в таких системах. Ребята, окажите себе услугу и начните работать с системой шаблонов. Помните, что PHP — в сущности, одна из них, поэтому не думайте, что раньше с такими системами не работали (работали, но просто не осознавали этого). Не поступайте так, как я в моем проекте на CherryPy, просто то есть, не записывайте ваш HTML прямо в строки и не используйте подстановку в стиле printf(). Да, это сработает, но будет сделано топорно, вы не получите правильной подсветки синтаксиса и смешаете представление с логикой.

    Однако не пытайтесь выбрать какую-то определенную систему шаблонов. Все они почти одинаковы, только синтаксис отличается. Я выбрал Jinja2, так как она хорошо интегрируется с Flask и, по-видимому, довольно быстра. Но на самом деле с новой системой шаблонов можно разобраться за какие-нибудь полчаса.

    Что мне особенно понравилось в системе Jinja2 (и у других систем шаблонов, вероятно, также есть такое свойство) — так это возможность наследовать от других шаблонов. Так, я могу создать базовый шаблон для всех страниц с определенными именованными блоками, затем сделать так, чтобы другие шаблоны наследовали от этого и могли добавлять конкретные блоки, которые им, возможно, потребуется изменить. В PHP все было сложнее: там приходилось создавать различные элементы страницы (заголовок, область навигации, контент), прибегая к функциям или методам класса.

    Некоторые общие соображения, которые следует усвоить

    Итак, вы подобрали для себя фреймворк и систему шаблонов, но остались определенные концепции, с которыми вы привыкли работать, а также вредные привычки, от которых вы хотите избавиться, чтобы писать веб-приложения на Python.

    Веб-страница — не STDOUT

    Если в PHP вы хотели что-нибудь вставить на веб-страницу, то просто делали эхо-запрос. Поскольку мы просто делаем препроцессинг документа на месте, все, изрыгнутое PHP, окажется на веб-странице. Это оптимальный стиль программирования на PHP, именно так там решаются проблемы с кодом. Признаться, многие PHP-разработчики и отладку выполняют именно так (применяя echo или var_dump() в произвольной точке кода, чтобы посмотреть, что там происходит).

    Фреймворки Python, например, Flask или CherryPy, работают иначе. В них вы пишете функции, возвращаете фрагмент HTML, который фреймворк уже упаковывает в HTTP-отклик. Итак, в отличие от PHP, если вы «print» что-нибудь, то оно не окажется на итоговой веб-страницы, а отправится туда, куда уходит весь стандартный вывод.

    Мне было несложно к этому адаптироваться, поскольку примерно так я и писал приложения на PHP: они у меня строились как наборы функций, возвращающих фрагменты HTML. Однако для некоторых PHP-программистов, привыкших запрашивать данные при помощи echo, такой подход покажется очень неестественным.

    Маршрутизация

    Выше я описывал, как сервер обрабатывает запрос к PHP-файлу. В принципе, если я перейду по ссылке http://example.com/lib/docs/help , веб-сервер попробует найти /lib/docs/help/index.(запрос). Если окажется, что “запрос” означает “php”6, то PHP получит файл, сделает свое дело, и все будут довольны. Если подходящего индексного файла не найдется, то вы получите ошибку 404, а может и 403. Короче говоря: если вы хотите обеспечить доступ к URL при помощи PHP (если не колдовать с настройками конфигурации веб-сервера, например, не писать mod_rewrite), то должны расположить PHP-файл по этому URL.

    В современных фреймворках Python это делается иначе. Сделать конкретный URL, получив с сервера HTML-страницу в качестве отклика, можно и в том случае, когда фактически нужные файлы и не расположены по этому пути. Для таких случаев во фреймворке имеется система для отображения URL на функции или классы вашей программы. Обычно этот процесс называется «маршрутизацией».

    Итак, чтобы в вышеприведенном примере сделать рабочий URL на сайте, написанном с применением фреймворка Python, можно не класть никаких файлов в /lib/docs/help, да и вообще не создавать эти директории под webroot. Просто организуйте, чтобы ваш код отображал путь ‘/lib/docs/help’ на функцию или класс, которые бы возвращали конкретный отклик (фрагмент HTML или, скажем, HTTP-объект).
    Некоторые фреймворки PHP или приложения обеспечивают нечто похожее на такую маршрутизацию (вроде “pretty/simplified/clean URLS”), при этом обычно используется mod_rewrite, скрывающий запрос к конкретному php-файлу.

    Прежде, чем я взялся за программирование веб-приложений на Python, я не так хорошо ориентировался в HTTP. Если вы просто работаете с PHP, то, вероятно, знаете, что информация может отправляться на сервер в виде запросов “GET” или “POST” и знаете, в чем разница между такими запросами, записанными в адресной строке. Оказывается, HTTP далеко не ограничивается GET и POST, и если вы работаете с Python, то весьма целесообразно понимать этот протокол целиком.

    Дело не в том, что фреймворки Python не абстрагируют HTTP; просто они его абстрагируют иначе, нежели PHP – и, честно говоря, абстрагируют чище и естественнее. Например, тогда как PHP предоставляет содержимое HTTP-запроса вашему сценарию при помощи серии глобальных массивов (напр. $_GET, $_SERVER, т.д.) или вызовов функций (напр. getallheaders()), фреймворки Python обычно предоставляют вам тот или иной объект запроса, инкапсулирующий весь HTTP-запрос.

    Если учесть, что фреймворки Python и приложение на нем обрабатывают многие вещи, которые PHP просто делегирует серверу, то вполне логично, что вам придется несколько плотнее познакомиться с HTTP; не пугайтесь. Чем больше я учусь, тем больше склоняюсь к мысли, что HTTP в самом деле проще той мешанины, в которую его абстрагирует PHP.

    Если вы работали с одним или несколькими известными PHP-фреймворками (напр., с CodeIgniter), то, вероятно, встречали термин «MVC». MVC – это стратегия проектирования ПО, при которой ваше приложение четко подразделчется на три части:

    Модель – это ваша модель данных, обрабатывающая все операции считывания/записи данных, связанные с базой данных или хранилищем данных.

    Представление – это интерфейс, который видит конечный пользователь и с которым он, собственно, работает.

    Контроллер, обеспечивающий взаимодействия между представлением и моделью.

    Если вы не работали с фреймворком или уже освоили паттерн MVC, то вас ждет шок. У меня сложилось впечатление, что фреймворки Python практически навязывают идею MVC на том или ином уровне. В конечном итоге, это положительно скажется на вашем коде, а без использования таких паттернов становится сложно масштабировать реальное приложение. Однако здесь придется немало изучить, если вы привыкли просто запихивать запросы к базе данных и бизнес-логику между большими кусками HTML по мере необходимости.

    Планирование и проектирование

    Да, «реальные программеры» здесь могут немного поморщиться, но если вы собираетесь расстаться с PHP, то должны привыкнуть, что теперь придется уделять больше внимания планированию веб-приложения. Если PHP в чем-нибудь и прекрасен, то именно в том, что позволяет сесть в кресло и написать что-то масштабное без особой предварительной подготовки. Просто вставляете код там, где он должен прийтись кстати.
    Фреймворки Python, с которыми мне приходилось иметь дело, подходят к этому вопросу строже. Это не PHP, где вы просто ломитесь через сценарий сверху вниз; поэтому здесь вы не можете просто произвольно определять что заблагорассудится и рассчитывать, что эти вещи окажутся в области видимости, как только они вам потребуются. Аналогично, язык шаблонов – это просто язык шаблонов, а не полноценный язык программирования. Нельзя просто так поместить в шаблоне кучу вызовов функций и операций присваивания переменных лишь потому, что вам так нравится.

    Кроме того, в таких фреймворках как Flask используются многие паттерны проектирования и абстрактные конструкции, назначение которых отнюдь не очевидно для людей, не изучавших всерьез теорию программирования. Чтобы правильно задействовать эти конструкции, требуется на более абстрактном уровне представить себе, что именно делает ваше приложение.

    В конечном итоге, это пойдет вам на пользу, поскольку придется проектировать более качественные и аккуратные приложения, чем, возможно, получились бы у вас на другом языке. Но это предполагает абсолютно новый образ мышления и подход к работе.

    Недостатки Python

    Кроме необходимости переключиться на новую парадигму, которую я описал выше, Python имеет и некоторые характерные недостатки, проявляющиеся при веб-разработке. Думаю, было бы честно упомянуть здесь о них.

    Развертывание

    Чтобы развернуть приложение на PHP, достаточно лишь сбросить файлы.php в каталог под webroot. Развернуть приложение на Python несколько сложнее, это можно сделать несколькими способами, и у каждого из них есть свои достоинства и недостатки, которые необходимо взвесить.
    Разумеется, ваш веб-хост может не поддерживать Python, Flask или любые другие библиотеки Python, требуемые для работы вашего приложения.

    Доступ к базе данных

    Если вы хотите углубиться в высокоуровневые абстракции, извлекающие из вашего кода весь противный SQL и заменяющие его объектами и вызовами методов, то Python покорит вас своими слоями ORM, например, SQLAlchemy. Если мы с вами единомышленники, и вы предпочитаете писать собственный SQL, то ситуация немного меняется.

    В PHP есть приятный уровень абстрагирования базы данных, так называемый PDO, и если вы его еще не используете – прекратите набивать “mysql_” и используйте. PDO – это единая библиотека с согласованными классами, методами и свойствами, не зависящая от конкретного драйвера базы данных, который вы используете.

    В свою очередь, Python обладает DB-API — это спецификация, описывающая создание библиотек для работы с базами данных в Python. Вместо библиотеки с драйверами для различных баз данных Python предоставляет библиотеки, специфичные для каждого продукта, которые (в основном) соответствуют DB-API. Теоретически все должно идти столь же гладко, как и с PDO, но на практике ситуация более разношерстная. Некоторые библиотеки расширяют DB-API дополнительными функциями, а сама спецификация гибко описывает, как должны реализовываться те или иные вещи (например, стиль параметров для параметризованных запросов). Мне это показалось неудобным, когда (например) я портировал Flask-приложение, написанное для psycopg2 (популярная библиотека PostgreSQL) на cx_Oracle (единственная библиотека Oracle, соответствующая DB-API) и бы вынужден корректировать кучу кода из-за различий в интерпретации спецификации.
    Разумеется, можно было бы использовать ORM…

    Акцент на программировании приложений

    По-видимому, такие фреймворки как Flask или CherryPy предполагают, что вы пишете именно приложение, а не случайный набор произвольного веб-контента. Это отлично, если вы действительно пишете приложение, но ведь так бывает не всегда. Иногда просто пишется страница, перед отображением которой требуется выполнить какие-либо вычисления, либо форму, перед отправкой которой нужна какая-то простая обработка.
    Если, допустим, вы ведете сайт с блогом, содержащим несколько разделов с контентом, почти не связанных друг с другом, причем большая часть контента унаследована и трогать ее не требуется, то PHP как раз вам подойдет. Если вы собираетесь добавить новую страницу, никак не связанную с остальным сайтом, но требующую предварительной обработки (например, просмотреть каталог MP3-файлов и отобразить информацию ID3 information), то достаточно написать новый.php-файл и положить его в каталог.

    Если бы мой сайт управлялся приложением на Python, все было бы сложнее. Мене пришлось бы либо написать и развернуть новое приложение WSGI (слишком много работы ради одной страницы), либо добавить в имеющееся приложение новую функцию (усложнив, а, возможно, и дестабилизировав весь сайт).

    Нужно ли переходить?

    Я не из тех блоггеров, которые стали бы вас убеждать перейти с PHP на Python или на какой-нибудь другой язык. PHP в обозримом будущем никуда не денется, для многих приложений он совершенно адекватен. Но я скажу, что время, потраченное на изучение языка вроде Python, а также на освоение его популярных фреймворков, пойдет на пользу любому веб-программисту.

    На самом деле, я дозрел до перехода на Python, когда стал все активнее прибегать к AJAX и обнаружил, что мои проекты превращаются из наборов динамических страниц в приложения на JavaScript, которые время от времени связываются с сервером для считывания и записи данных. Иными словами, когда мой серверный код чаще стал передавать вызовам AJAX операции чтения и записи в базу данных, нежели генерировать целые страницы отформатированного HTML, мне стало удобнее работать с фреймворками Python.

    Ваши страницы состоят в основном из HTML и лишь кое-где содержат немного динамического контента.
    Ваша работа будет развертываться на дешевом разделяемом веб-хостинге
    Вы считаете, что пишете не «приложение», а просто «веб-страницы»

    Если вы всерьез занимаетесь веб-разработкой и сами контролируете свой сервер, то следующий большой проект стоит написать на Python. Очень вероятно, что многие задачи по унаследованным проектам вы так и будете решать на PHP; но работу с Python стоит постоянно наращивать.

    Похожие статьи