среда, 30 июля 2008 г.

За что я люблю Python, часть 1

Давно хотел рассказать на примерах чем мне нравится язык Python, и наконец созрел на серию публикаций. Итак...

Начнем с объективных вещей:

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

Пример: есть метод get объекта dict, который возвращает значение по ключу если оно есть, если его нет то возвращается значение по умолчанию.
Код на Java

public void V get(K key) {
return this.values.get(key);
}
public void V get(K key, V default) {
return this.values.get(key, default);
}

Код на python

def get(self, key, default=None):
return self.values.get(key, default)

Чем это хорошо? Давайте посмотрим на типичную эволюцию кода. Сначала у вас было все просто и значение по умолчанию возвращать было не нужно, потом в каком-то месте понадобилось вернуть значение по умолчанию. Что вы делаете в случае Java- либо добавляете метод, либо добавляете if-else блок вместо него и после 5-го такого блока таки добавляете метод и затеваете небольшой рефакторинг. В случае с python вы сразу же добавляете параметр со значением по умолчанию не нарушая прошлый код, просто потому что по другому никак, нет здесь перегрузки методов. Это простой пример, на практике встречаются функции с, например, 5 необязательными разнотипными параметрами и в случае Java это сводится к 2 ** 5 = 32 перегруженых функций, в python это все достигается одним методом И никакого рефакторинга.

*args, **kwargs - аргументы метода и именованные аргументы метода
Помимо возможности передать в метод аргументы которые объявлены в сигнатуре явно, в python вы имеете возможность передавать в метод все что захотите и, с другой стороны, доступаться к этим значениям внутри метода. Как это использовать?
Пример: вам нужно сделать метод который будет генерировать HTML тег IMG с набором аттрибутов, код на Java который это делает невозможен без передачи чего-то вроде hash map с набором аттрибутов (поправьте меня если я не прав) в итоге все выглядит так
Код на Java

public static String imageTag(Map attrs) {
StringBuffer res = new StringBuffer();
for (entry in attrs.entries()) {
res.append(String.format("%s='%s' ", entry.key(), entry.value()));
}
return String.format("<img %s/>", res);
}
.... Вызов метода
Map attrs = new HashMap();
attrs.put("src", "foo.gif");
attrs.put("width", "100");
attrs.put("height", "200")
attrs.put("alt", "бар натуральный")
out.write(Tags.imageTag(attrs));

Код на Python

def image_tag(**kwargs):
return "<img %s/>" % " ".join(["%s='%s'" % (k, v) for (k, v) in kwargs.items()])
.... Вызов метода
print image_tag(src="foo.gif", width=100, height=200, alt=u"бар натуральный")

3 строки вместо 13 всего, 1 строка вместо 6-ти для вызова, а аттрибутов у HTML тегов десятки. В байтах тоже метрика схожая.
Впечатляет? А теперь представьте что вы хотите задать размер по умолчанию 100 на 100 если он не задан.
код на Java

public static String imageTag(Map attrs) {
if (!attrs.hasKey("width")) {
attrs.put("width", "100")
}
if (!attrs.hasKey("height")) {
attrs.put("height", "100")
}
StringBuffer res = new StringBuffer();
for (entry in attrs.entries()) {
res.append(String.format("%s='%s' ", entry.key(), entry.value()));
}
return String.format("<img %s/>", res);
}


Код на Python (дважды спасибо Сергею за исправление)

def image_tag(**kwargs):
kwargs.setdefault('width', 100)
kwargs.setdefault('height', 100)
return "<img %s/>" % " ".join(["%s='%s'" % (k, v) for (k, v) in kwargs.items()"])

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

Продолжение следует...

8 комментариев:

Анонимный комментирует...

Хорошее сравнение с Java!

Жду следующих частей.

P.S. Однако, считаю Ruby - лучшим языком программирования ever made.

Анонимный комментирует...

Это жесть:

def image_tag(width=100, height=100, **kwargs):
....kwargs.update(locals())

Корректно так:


def image_tag(**kwargs):
....kwargs.setdefault('width', 100)
....kwargs.setdefault('height', 100)

Sergey Grigoriev комментирует...

А я люблю Питон за то что функция может возвращать несколько значений как tuple

def myFunc():
return (1,2,3)

a,b,c=myFunc()

в результате a=1,b=2,c=3

Mykola Paliyenko комментирует...

Сергей, это работает я потом отфильтровываю kwargs ниже.
>>> image_tag()
"<img width='100' height='100'/>"
>>> image_tag(a=5)
"<img a='5' width='100' height='100'/>"
>>>

Хотя ваш пример логически правильнее в случае если метод нетривиален.

Анонимный комментирует...

Я понимаю что работает, но трогать locals и globals плохой стиль.

Mykola Paliyenko комментирует...

Спасибо, исправил, так действительно правильней с точки зрения стиля.

Анонимный комментирует...

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

А если добавить немого кунг-фу, то вот так:

def image_tag(**kw):
____kw.setdefault('width', 100)
____kw.setdefault('height', 100)
____attr_format = "%s='%s'".__mod__
____return "[img %s/]" % " ".join(map(attr_format, kw.iteritems()))

Mykola Paliyenko комментирует...

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