Комментарии 39
if foo = make_foo():
# do work with foo
На мой взгляд, даже хуже. Хуже для глаз в первую очередь.
Все же уже есть какие то сложившиеся шаблоны в восприятии исходников.
Шаблоны, в которых сознательная часть мозга не используется. А новый синтаксис просто срывает это шаблон.
А в чем преимущество этого варианта написания — лично я не вижу. Экономим перевод строки?
Так и не понял, почему автору "if" не нравится. Вообще… как концепция.
Ну единственное применение для такой конструкции "if", что я вижу — ограничение области видимости переменной (жизни ее на стеке… например).
Но для демонстрации этого, пример автором выбран не удачный с указателем.
Хотя, лично мне, как то не лень пару "{}" поставить. Более общее и наглядное и, главное, совместимое по исходникам решение.
А насчет "for"… До сих пор под некоторое железо (старые модели Verifone PINPAD) приходится пользовать кросс компиляторы где, "i" в выражении "for(int i=..." не является ограниченным в видимости "for".
Увы… все эти новые фичи языка интересны, но приходится писать так, что бы код был переносим всегда.
Понял бы вас, если бы речь шла о паре точечных фокусов, но если это парадигма целого модуля, возмущение бедолаги звучит как «они использовали функциональное программирование, а мне теперь в нём разбираться, вот дураки».
Готов поспорить, этот аргумент слышали разработчики Питона, Джанго, Реакта… список можно продолжать.
if ((Foo* foo = make_foo()))
{
}
if (Foo* foo = make_foo())
и возможность перепутать сравнение с присваиванием в
if (foo = make_foo())
Ничего странного. Первое, вообще‐то, может и умножением быть. Анализатору, конечно, такое очевидно, но он не должен предполагать, что это также очевидно пользователю.
Хотя я бы ещё предположил, что к тому времени, как такое присваивание дойдёт до анализатора, информации о том, что там есть и объявление переменной у него уже не будет.
Скажем, github.com/vlasovskikh/funcparserlib:
def parse(seq):
'Sequence(Token) -> object'
...
n = lambda s: a(Token('Name', s)) >> tokval
def make_array(n):
if n is None:
return []
else:
return [n[0]] + n[1]
...
null = n('null') >> const(None)
true = n('true') >> const(True)
false = n('false') >> const(False)
number = toktype('Number') >> make_number
string = toktype('String') >> make_string
value = forward_decl()
member = string + op_(':') + value >> tuple
object = (
op_('{') +
maybe(member + many(op_(',') + member)) +
op_('}')
>> make_object)
array = (
op_('[') +
maybe(value + many(op_(',') + value)) +
op_(']')
>> make_array)
value.define(
null
| true
| false
| object
| array
| number
| string)
json_text = object | array
json_file = json_text + skip(finished)
return json_file.parse(seq)
Не сказал бы, что полный код примера funcparserlib радикально выигрывает в изящности или "понятности с первого взгляда". И по числу строчек примерно соответствует (144 против 130). Так что не стоит спешить обвинять автора в велосипедостроении. То, что он нам представил, — это в первую очередь упражнение, которое небесполезно было бы повторить самому. Это заслуживает и плюса, и благодарности за проделанную работу.
if (Foo* foo = make_foo())
{
// do something with foo, it is != nullptr
}
// no foo here
2. Рекурсия в parse_comma_separated_values — это провал. «Извините, ваш список слишком длинный, у меня стэк кончился».
3. Ну и сколько гбайт/с пережевывает этот парсер? Ну хотя бы сотен мегабайт/с?
4. Где изящество-то?
2. Да, действительно, на очень больших списках будет так себе. Но можно переписать без рекурсии:
def parse_comma_separated_values(src):
result = []
for value, src in parse_value(src): # достаем первый элемент (mommy says I'm special)
result.append(value)
break
else:
return # ни одного элемента не получается достать, ну упс
while src:
for (_, value), src in sequence(parse_comma, parse_value):
result.append(value)
break
else:
break # все сломалось, возвращаем сколько нашли
yield result, src
А это уже можно завернуть в ещё одну отдельную функцию, чтобы было вообще
parse_list(item=parse_value, delimiter=parse_comma)
. Но в статье я решил сделать упор в сторону лаконичности.3. Воу-воу, если бы мне нужно было пережёвывать гигабайты в секунду, я бы спокойно и академично написал на С с lex-yacc.
4. В заголовке.
Комбинаторные парсеры (пример выше) выглядят гораздо "изящнее", чем for'ы вместо последовательностей термов. (Если эти for'ы убрать, то и получатся комбинаторные парсеры. А так — "код в стиле барокко").
Рекурсивный спуск выглядит проще (но объёмнее).
И ещё есть всякие генераторы парсеров, которые должны работать быстрее.
Статья хорошо показывает, что парсер с нуля написать несложно, но я бы не рекомендовал использовать конкретно этот подход. Он избыточен и неочевиден.
И почему Python 2 ?
И почему на Хабре?
И почему на русском языке?
(Я не знаю ответа на ваш вопрос, но чувствую, что он как-то связан с ответами на приведённые вопросы...)
class Re(object):
def __init__(self):
self.last_match = None
def match(self, pattern, text):
self.last_match = re.match(pattern, text)
return self.last_match
def search(self, pattern, text):
self.last_match = re.search(pattern, text)
return self.last_match
def group(self, index):
return self.last_match.group(index)
Теперь я делаю вот так и забыл о прежнем чувстве дискомфорта:
r = Re()
if r.search(r'[0-9]+', str):
# число
elif r.search(r'[a-z]+', str):
# слово
Понимаю, что это не совсем по теме, поскольку не про парсер. Но просто вспомнилось, когда увидел свои прежние if'ы…
на Python 2
Меня как из ушата окатили. Так гадко стало. И обидно.
А генераторы — это способ по-быстрому оформить ленивость.
Ну и начинать надо было с обёртки над match
def maybe(x):
''' lift optional value (maybe None) to the single-item-list monad '''
return [v for v in [x] if v is not None]
def maybe_match(expr, g1, g2, src):
''' expr - regular expression, g1 - identifier of token group, g2 - identifier of the rest, src - source '''
return [m.group(g1,g2) for m in maybe(re.match(expr, src)]
. . .
for token,rest in maybe_match(r'([+\-]?[0-9]+)(.*)', 1,2, src):
. . .
def tkn(iv):
fv = {'null':None, 'true':True, 'false':False}
s = iv.strip()
if s in fv.keys():
rv = fv[s]
else:
try:
rv = float(s) if ('.' in s) | ('e' in s) | ('E' in s) else int(s)
except ValueError:
rv = s[1:-1] if (s[0] == s[-1]) and s[0] in "\"'" else s
return rv
def arr(iv):
rv = []
tkn = ""
ignore = False
if iv[0] == '[' and iv[-1] == ']':
for c in iv[1:-1]:
if c in "'\"[]{}":
ignore = not ignore
tkn += '' if c == ',' and not ignore else c
if (c == ',') and not ignore:
rv.append(parse(tkn))
tkn = ""
rv.append(parse(tkn))
return rv
def split(iv):
s = iv.strip()
i = s[1:].find(s[0])
j = s[i+1:].find(':')
return [s[1:i+1], s[i+j+2:]] if i > -1 and j > -1 else []
def dict(iv):
rv = {}
tkn = ""
p = []
ignore = False
if iv[0] == '{' and iv[-1] == '}':
for c in iv[1:-1]:
if c in "'\"{}[]":
ignore = not ignore
tkn += '' if c == ',' and not ignore else c
if (c == ',') and not ignore:
p = split(tkn)
if len(p) > 0:
rv[p[0]] = parse(p[1])
tkn = ""
p = split(tkn)
if len(p) > 0:
rv[p[0]] = parse(p[1])
return rv
def parse(iv):
s = iv.strip()
return None if len(s) < 1 else arr(s) if s[0] == '[' else ( dict(s) if s[0] == '{' else tkn(s) )
Пишем изящный парсер на Питоне