- PVSM.RU - https://www.pvsm.ru -
Думали ли вы когда-нибудь о том, что однажды слишком быстро втянулись в веб-программирование на PHP? И вот уже прошло много лет, у вас хороший опыт, и вы не думаете ни о каких других способах „делать“ веб, кроме как на PHP. Может быть, у вас возникают сомнения в правильности выбора, однако непонятно, как найти способ быстро его проверить. А хочется примеров, хочется знать, как изменятся конкретные аспекты деятельности.
Сегодня я попробую ответить на вопрос: «А что если вместо PHP писать на Python?».
Сам я долгое время задавался этим вопросом. Я писал на PHP 11 лет и даже являюсь сертифицированным специалистом. Я научился его «готовить» так, чтобы он работал в точности, как мне надо. И когда я в очередной раз читал на Хабре перевод статьи о том, как всё в PHP плохо [1], я просто недоумевал. Однако подвернулся случай пересесть на Ruby, а потом и на Python. На последнем я и остановился, и теперь попробую рассказать вам PHP-шникам, как нам питонистам живётся.
Наилучший способ познавать новый язык — сравнение с регулярно-используемым, если новый язык не принципиально отличается от текущего. Неплохая попытка сделана на сайте Ruby [2], но, к сожалению, там мало примеров.
Также я должен отметить, что я сравню не все аспекты деятельности, а только те, которые будут бросаться в глаза в первые недели работы с новым языком.
Я попытался сделать эту статью интерактивной. Поэтому при прочтении настоятельно рекомендую набирать примеры из неё в консолях. Вам понадобится консоль PHP 5.3+, а лучше сразу psysh [3]:
php -a
И консоль Python 2/3. Лучше поставить более удобные варианты bpython [4] или ipython [5], чем встроенная в язык по умолчанию, так как в них уже есть автодополнение. Но можно и так:
python
import rlcompleter
import readline
readline.parse_and_bind("tab: complete") # Это включит автодополнение
import rlcompleter
import readline
readline.parse_and_bind("tab: complete") # Это включит автодополнение
В ~/.bashrc добавить:
export PYTHONSTARTUP="/home/%username%/.pyrc"
export PYTHONIOENCODING="UTF-8"
И, чтобы применить изменение прямо сейчас без перезапуска консоли, выполнить:
source ~/.pyrc
Ну и самая необычная вещь: вложенность кода определяется не фигурными скобками, а отступами. То есть вместо того, чтобы писать так:
foreach($a as $value) {
$formatted = $value.'%';
echo $formatted;
}
Надо делать так:
for value in a:
formatted = value + '%'
print(formatted)
Стойте, стойте! Не спешите закрывать вкладку. Здесь вы можете сделать ошибку, такую же, как сделал я. Когда-то мысль о том, что вложенность кода определяется отступами, казалась мне полным идиотизмом. Все защитные силы организма протестовали в едином порыве. Ведь несмотря на всякие Style Guide, все пишут по-разному.
Я открою вам страшную тайну. Проблемы отступов не существует. Отступы в 99% процентах случаев автоматически расставляются IDE точно так же, как и в любом другом языке. Вы вообще даже не думаете об этом. За 2 года работы с языком я не припомню ни одного случая, когда кто-нибудь накосячил бы с отступами.
Следующая вещь на которой стоит сфокусировать внимание — строгая типизация. Но для начала немного кода:
print '0.60' * 5;
print '5' == 5;
$a = array('5'=>true);
print $a[5];
$value = 75;
print $value.'%';
$a='0';
if($a) print 'non zero length'; // Самая частая ошибка
Все указанные вещи возможны благодаря нестрогой типизации. Но в питоне это не работает:
>>> print "25" + 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects
Правда, обычно у вас в коде типы перемешиваться не будут и данный эффект не будет мозолить вам глаза. В принципе, когда я писал на PHP, ситуаций когда нестрогая типизация реально помогала, было 1-2 на проект, а так обычно взаимодействовали переменные одного типа.
Философия строгой типизации накладывает свой отпечаток и на обработку ошибок. Например, если функция int должна возвращать целый тип, то она не может вернуть None на строку, из которой нельзя однозначно извлечь этот тип. Поэтому генерируется исключение. Это грозит тем, что всё, что прислал пользователь, надо преобразовывать в нужный тип, иначе вы рано или поздно схватите эксепшн на проде.
try:
custom_price = int(request.GET.get('custom_price', 0))
except ValueError:
custom_price = 0
Это касается не только стандартных функций, но и некоторых методов для списков, строк, части функций во вспомогательных библиотеках. Обычно питон-разработчик примерно на память помнит, где какое исключение может упасть, и учитывает это. Если не помнит, иногда лазит в код библиотеки, чтобы подсмотреть. Но конечно же иногда бывает, что не все варианты учитываются, и порой пользователи ловят эксепшены на проде. Но поскольку это нечастое явление, и обычно веб-фреймворк присылает их админу на почту автоматически, всё довольно быстро чинится.
Чтобы использовать значения разных типов в одном выражении, вам нужно их преобразовать, для этого есть функции: str, int, bool, long. Ну а для форматирования есть более элегантные конструкции.
Было:
$tak = 'так';
echo "Вы сейчас делаете $tak или {$tak}.";
echo "Или ".$tak.".";
echo sprintf("Но бывает и %s или %1$'.9s.", $tak);
Теперь вам нужно просто переучиться:
etot = 'этот'
var = 'вариант'
print('На %s вариант' % etot)
print('Или на %s %s' % (etot, var))
print('Или на %(etot)s %(var)s' % {'etot': etot, 'var': var}) # Очень удобно для локализаторов
print('Или на {} {}'.format(etot, var))
print('Или на {1} {0}'.format(var, etot))
print('Или на {etot} {var}'.format(var=var, etot=etot))
Вариантов вроде больше, а вот подстановки одной-двух переменных через фигурные скобки в двойных кавычках реально не хватает. Зато есть хороший вариант для локализаторов.
Самое главное, что есть в Python и чего не хватает в PHP, — это встроенные методы. Давайте сравним:
strpos($a, 'tr');
trim($a);
vs
a.index('tr')
a.strip()
А как часто вы делали что-то типа такого?
substr($a, strpos($a, 'name: '));
vs
a[a.index('name: '):]
Ну и наконец юникод. В Python 2 все строки по умолчанию не юникод (В Python 3 — по умолчанию юникод). Но стоит вам подставить магическую букву u вначале строки, как она автоматически становится юникодной. И далее все встроенные (и не встроенные) строковые методы Python будут работать хорошо.
>>> len('Привет мир')
19
>>> len(u'Привет мир')
10
В PHP, кстати, вы можете воспользоваться MBString function overloading php.net/manual/en/mbstring.overload.php [11] и получить аналогичный эффект. Правда, вы лишаетесь работы с помощью перегруженных функций с бинарными строками, но вы по прежнему можете работать со строкой как с массивом.
Многие из вас знают чем одиночные кавычки отличаются от двойных:
$a = 'Hello.n';
$a[strlen($a)-1] != "n";
Что-то подобное есть и в Python. Если подставлять перед строкой литерал r, то это поведение почти аналогично одиночным кавычкам в PHP.
a = r'Hello.n'
a[-1] != 'n'
Теперь разберёмся с массивом. В PHP вы могли запихнуть в качестве ключей целые числа или строки:
var_dump([0=>1, 'key'=>'value']);
Несмотря на то что array переводится как массив, в PHP array не обычный массив (то есть список [12]), а ассоциативный (то есть, словарь [13]). Обычный массив в PHP тоже есть, это SPLFixedArray [14]. Он ест меньше памяти, потенциально быстрее, но ввиду сложности объявления и сложности расширения практически не используется.
В Python для массива мы используем 3-4 типа данных:
a = [1, 2, 3] # краткая форма
a[10] = 11 # Нельзя добавлять произвольный индекс
# > IndexError: list assignment index out of range
a.append(11) # но можно добавлять в конец
del a[0] # удалять по ключу
a.remove(11) # удалять по значению
a = {'a': 1, 'b': 2, 'c': 3} # краткая форма
b[10] = 11 # Можно добавлять произвольный индекс
b[True] = False # И использовать для ключей любые неизменяемые типы (число, строка, булевские, кортежи, замороженные множества)
del a[True] # удалять по ключу
a = (True, 'OK', 200, ) # краткая форма
b[0] = False # Нельзя менять элемент
# > TypeError: 'tuple' object does not support item assignment
del a[True] # Нельзя удалять по ключу
# > TypeError: 'tuple' object doesn't support item deletion
a = ([], ) # Но можно менять вложенные изменяемые структуры (списки, словари, множества, массивы байт, объекты)
a[0].append(1)
# > a == ([1], )
s = set([1,3,4])
s[0] = False # Нельзя работать с элементами по индексу
# > TypeError: 'set' object does not support indexing
s.add(5) # Можно добавлять элемент
s.remove(5) # Удалять
# Ну и стандартная математика для множеств
s | s # Объединение
s & s # Пересечение
s - s # Разность
s ^ s # Объединение уникальных
В PHP массив — эдакий швейцарский нож, всё в одном. А Python больше склоняет к тому, что надо использовать более нативные для Computer Science наборы данных, в каждом место свои, более подходящие. «Да ну их, опять задротам неймётся, программирование должно быть простым», — могут воскликнуть некоторые читатели. И будут неправы, так как:
Ещё одна интересная вещь — импорты. Своеобразный альтернативный взгляд на неймспейсы с обязательным использованием.
В PHP ты написал require_once и дальше тебе это доступно, пока жива PHP-шная сессия выполнения. Обычно при использовании CMS люди запихивают всё в классы, а классы располагают в специальных местах, и пишут свою маленькую функцию, которая знает эти места и регистрируют её через spl_autoload_register в самом начале файла.
В питоне каждый файл — своя область видимости. И в нём будет доступно только то, что вы в него подгрузите. По умолчанию доступна только стандартная библиотека питона (около 80 функций). Но давайте лучше на примере:
Допустим, вы сделали файл tools/logic.py
def is_prime(number):
max_number = int(sqrt(number))
for multiplier in range(2, max_number + 1):
if multiplier > max_number:
break
if number % multiplier == 0:
return False
return True
И теперь хотите его использовать в файле main.py. В этой ситуации вам необходимо импортировать или весь файл или нужные вам части в файл, где вы работаете.
from tools.logic import is_prime
print(is_prime(79))
И так абсолютно везде. Почти все файлы на питоне начинаются с импортов в текущий файл вспомогательных питонячих объектов: ваших и встроенных библиотек. Это всё равно как если бы функции в PHP вида mysqli_*, pdo_*, memcached_*, а также весь ваш код находились только в неймспейсах, и вам приходилось бы каждый раз их импортировать в каждом файле. Какие преимущества у такого подхода?
Из минусов можно отметить только появление такого эффекта как циклические импорты [17], но, во-первых, это редкое явление, во-вторых, это штатная ситуация и вполне понятно, как с ней бороться.
Удобно ли каждый раз прописывать импорты? Это зависит от вашего склада ума. Если вам нравится больший контроль над кодом, то вы предпочтёте прописывание импортов, чем их отсутствие. В некоторых командах существует даже своя система правил, описывающих в каком порядке можно подключать внешний код для минимизации циклических импортов до минимума. Если в вашей команде нет таких правил и вы не хотите особо заморачиваться, то можно просто положиться на IDE, которая автоматически проставит импорты для всего, что вы используете. Ну и в довесок: импорты не уникальная особенность питона, в Java и C# тоже есть импорты, вроде никто не жаловался.
Синтаксис с параметрами по умолчанию в целом похож:
function makeyogurt($flavour, $type = "acidophilus")
{
return "Making a bowl of $type $flavour.";
}
vs
def makeyogurt(flavour, ftype="acidophilus"):
return "Making a bowl of %s %s." % (ftype, flavour, )
Но порой вам нужна функция под неизвестное количество аргументов. Это может быть: проксирующая функция, логирующая функция или функция для получения сигналов. В PHP, начиная с 5.6, вам доступен следующий синтаксис:
function sum(...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $acc;
}
echo sum(1, 2, 3, 4);
// Или
echo add(...[1, 2, 3, 4]);
В Python аналогично можно ловить в массив неименнованные и в словарь именованные аргументы:
def acc(*args, **kwargs):
total = 0;
for n in args:
total += n
return total
print(acc(1, 2, 3, 4))
# Или
print(acc(*[1, 2, 3, 4]))
Соответственно *args — list неименованных аргументов, **kwargs — dict именованных аргументов.
Давайте посмотрим на следующий код:
class BaseClass:
def __init__(self):
print("In BaseClass constructor")
class SubClass(BaseClass):
def __init__(self, value):
super(SubClass, self).__init__()
self.value = value
def __getattr__(self, name):
print("Cannot found: %s" % name)
c = SubClass(7)
print(c.value)
Какие основные отличия от PHP мы можем выделить:
О чем еще следует сказать:
Не знаю как у вас, а я писал на несколько долгоиграющих проектов и всегда отмечал, что стиль кодирования у разных членов команды разный. Нередко по коду можно понять, кто это писал. И всегда хотелось, чтобы был какой-то стандарт оформления кода для единообразия. И всегда были большие споры при согласовании этого документа внутри команды. Для питона это тоже справедливо, но в меньшей степени, ибо есть рекомендации от людей, в квалификации которых мало кто может сомневаться и советов от которых на первое время хватит:
Кроме того существует так называемый Дзен Питона [20], согласно одному из правил которого, «должен существовать один — и, желательно, только один — очевидный способ сделать это». То есть, нельзя один и тот же код написать множеством примерно аналогичных способов. Конечно же, это идеализм, однако он приятно проявляется в мелочах:
В PHP новые версии всегда обратно совместимы со старыми. В питоне есть Python 2 и Python 3. Они не совместимы. По этой причине вся «тяжёлая промышленность» до сих пор сидит на Python 2. С Python 3 выпускаются на прод только энтузиасты. Основная причина этого — драйвер MySQL не портирован на Python 3 (а также некоторые другие нужные вещи [22]). Если вы собираетесь что-либо делать, то Python 2.7 вам более чем будет достаточен. Если вы увлечётесь питоном, вы со временем поймёте, что такое Python 3 и когда его можно использовать.
Здесь я просто оставлю набор ключевых слов, по которым будет понятно, как называется альтернатива той технологии, которой вы пользуетесь сейчас.
Если вы разработчик с опытом, то на всё не сильно напрягаясь уйдёт максимум две-три недели.
Удачи!
Автор: gnomeby
Источник [28]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/85560
Ссылки в тексте:
[1] как всё в PHP плохо: http://habrahabr.ru/post/142140/
[2] на сайте Ruby: https://www.ruby-lang.org/ru/documentation/ruby-from-other-languages/
[3] psysh: http://psysh.org/
[4] bpython: http://bpython-interpreter.org/
[5] ipython: http://ipython.org/
[6] Ликбез по типизации: http://habrahabr.ru/post/161205/
[7] PHP создан чтобы умирать: http://habrahabr.ru/post/179399/
[8] два десятка переменных окружения: https://docs.python.org/2/using/cmdline.html#environment-variables
[9] PYTHONDONTWRITEBYTECODE: https://docs.python.org/2/using/cmdline.html#envvar-PYTHONDONTWRITEBYTECODE
[10] так задумано: https://docs.python.org/2/library/stdtypes.html#boolean-values
[11] php.net/manual/en/mbstring.overload.php: http://php.net/manual/en/mbstring.overload.php
[12] список: http://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%28%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29
[13] словарь: http://ru.wikipedia.org/wiki/%D0%90%D1%81%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2
[14] SPLFixedArray: http://www.php.net/manual/en/class.splfixedarray.php
[15] множество: http://ru.wikipedia.org/wiki/%D0%9C%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%BE_%28%D1%82%D0%B8%D0%BF_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%29
[16] OrderedDict: https://docs.python.org/2/library/collections.html#collections.OrderedDict
[17] циклические импорты: http://stackoverflow.com/questions/744373/circular-or-cyclic-imports-in-python
[18] PEP 8: https://www.python.org/dev/peps/pep-0008/
[19] Рекомендации от Google: http://google-styleguide.googlecode.com/svn/trunk/pyguide.html
[20] Дзен Питона: https://www.python.org/dev/peps/pep-0020/
[21] большой строковой библиотеки: http://php.net/manual/en/ref.strings.php
[22] некоторые другие нужные вещи: https://python3wos.appspot.com/
[23] Dive Into Python: http://www.diveintopython.net/
[24] Project Euler: https://projecteuler.net/problems
[25] BusyBox: http://www.busybox.net/downloads/BusyBox.html
[26] своей PHP-утилиты: https://github.com/gnomeby/memcached-itool
[27] хостинге: https://www.reg.ru/?rlink=reflink-717
[28] Источник: http://habrahabr.ru/post/221035/
Нажмите здесь для печати.