Pull to refresh

Comments 97

Если бы было больше ветвлений и/или в блоках условий было бы больше чем одна строка, я бы сделал так:

# coding: utf-8

def positive_answer(response):
    print("Положительный ответ: {}".format(response))

def negative_answer(response):
    print("Отрицательный ответ: {}".format(response))

def incorrect_answer(response):
    print("Введено некорректное значение: {}".format(response))

actions_map = {
    ("y", "Y", "д", "Д"): positive_answer,
    ("n", "N", "н", "Н"): negative_answer,
}


def are_you_sure4():
    while True:
        print("Вы уверены? [Д/н (Y/n)]: ")
        response = raw_input()

        for options, action in actions_map.iteritems():
            if response in options:
                action(response)
                break
        else:
            incorrect_answer(response)

are_you_sure4()

Если словарь всегда используется как список пар, то нафига словарь, а не список или кортеж? И у вас будет NameError (raw_input) (и AttributeError (iteritems) если вы исправите): приведённый код запускается на Python-3*: иначе ни приведённые регулярки, ни .upper() не работали бы с русским текстом.

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

P.S. Код работает для python 2.7
Если нужно итерировать парами значений, то я стараюсь на всякий случай использовать словарь, потому что так проще красивее получить доступ к списку только первых или к списку только вторых значений.

Ага. А читающий ваш код будет думать, что вы откуда‐то будете брать ("y", "Y", "д", "Д"), а не то, что вам нужен список пар. Словарь предполагает, что основной или один из основных методов доступа — __getitem__, а вы его не собираетесь использовать.


P.S. Код работает для python 2.7

А приведённый в статье там не работает.

А читающий ваш код будет думать, что вы откуда‐то будете брать («y», «Y», «д», «Д»), а не то, что вам нужен список пар.

Сомневаюсь что читающий код начнет пристально вглядываться в actions_map вне контекста его использования.

Словарь предполагает, что основной или один из основных методов доступа — __getitem__, а вы его не
собираетесь использовать.

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

Вообще, свое решение привел для того что бы:

  • показать пример легко расширяемого кода — легко добавлять новые варианты и увеличивать размер кода для условий
  • структурированный код (перекликается с расширяемостью) — легко можно найти какие варианты есть вообще и куда вносить изменения
  • использование функций как объектов
  • как бонус — отсутсвие повторяющихся if-ов


А приведённый в статье там не работает.

Я не ставил целью кого-либо запутать. Я работаю с 2.7.
Я не ставил целью кого-либо запутать. Я работаю с 2.7.

Ещё одна причина не использовать ваш вариант со словарём: в случае со списком пар вы будете использовать итератор что в Python-3*, что в 2.6+. А в случае со словарём вам нужен либо dict.items(), который в 2* генерирует список, а не итератор, либо 2to3 (что не удобно при разработке и вообще, по слухам там много ошибок; если нужно писать для обоих Python, лучше писать сразу на совместимом «диалекте» Python-23), либо какой‐то wrapper (класс‐наследник от dict, у которого items == iteritems; внешняя функция вместо метода; …).


Вообще, свое решение привел для того что бы:

Ничего из списка не является основанием для предпочтения словаря перед списком пар.


структурированный код (перекликается с расширяемостью) — легко можно найти какие варианты есть вообще и куда вносить изменения

Более структурированный вариант был бы


RETRY = ()

class Questioner:
    def __init__(self, question, answers, default_action=None):
        self.prompt = question
        self.answers = answers
        self.default_action = default

    def ask(self, *args, **kwargs):
        while True:
            response = input(self.prompt)
            action = self.answers.get(response.lower(), self.default_action)
            if action is None:
                raise ValueError('Invalid response')
            if action(response, *args, **kwargs) is not RETRY:
                break

def positive_answer(response):
    print("Положительный ответ: {}".format(response))

def negative_answer(response):
    print("Отрицательный ответ: {}".format(response))

def incorrect_answer(response):
    print("Введено некорректное значение: {}".format(response))
    return RETRY

qner = Questioner('Вы уверены? [Д/н (Y/n)]: ', {
    'y': positive_answer,
    'д': positive_answer,

    'n': negative_answer,
    'н': negative_answer,
}, default_action=incorrect_answer)

are_you_sure5 = qner.ask

are_you_sure5()

Основное отличие — не введение класса, а то, что incorrect_answer отделён от основных ответов, но определяется рядом, а не где‐то далеко в функции. Это и без класса можно было сделать, просто я обычно предпочитаю так.




Кстати, а чего это habrahabr превращает двойные новые строки в одинарные? По PEP8 в ряде случаев предполагаются двойные.

Что? Зачем делать ключами actions_map кортежи и потом еще и итерироваться по ним, когда уже есть мап? Чтобы сэкономить пару строчек когда?
Зачем делать ключами actions_map кортежи и потом еще и итерироваться по ним, когда уже есть мап?

Не понимаю о чем речь.

Чтобы сэкономить пару строчек когда?

Если получается без особых усилий сэкономить хотя бы одну строчку (без ущерба читаемости конечно), то я обычно экономлю.
Не понимаю о чем речь.

Я про то, что идея то нормальная, но я бы сделал ключами дикта сами ответы — Y, Д, N, Н. Вышло бы на пару строчек больше тут, но проще понимать и получать действие в дальнейшем, цикл будет ненужен и тд.
А если понадобится поддержка китайского, будете для каждого варианта добавлять по строчке? А если вариантов 10? А я как раз упомянул, что такой код я написал бы в случае большого количества вариантов.
Если будет уж очень много вариантов — я просто сгенерирую этот словарь.
Поддерживаю данный вариант, но не совсем понятно для чего нужен словарь, если идёт перебор значений? Словарь удобен, когда мы обращаемся по key к элементу. Получается, что мы имеем N функций, отвечающих за реакцию кода на ответ пользователя. Каждая функция имеет X имён. Как присвоить X имён одной функции? Напрашивается стандартный, простой ответ: никак. Можно, конечно, изобрести способ о двух колесах и с педальками…

Второй момент: слишком много функций, которые выводят одинаковый шаблон. Предлагаю сделать вот такой простенький вариант:

def are_you_sure():
    print("Вы уверены? [Д/н (Y/n)]: ")
    response = input()
    answer_templates = {
        True: lambda r: "Положительный ответ: {}".format(r),
        False: lambda r: "Отрицательный ответ: {}".format(r),
        None: lambda r: "Введено некорректное значение: {}".format(r)
    }
    answer_types = {"Д": True, "Y": True, "Н": False, "N": False}
    answer_key = answer_types.get(response.upper(), None)
    print(answer_templates[answer_key](response))


are_you_sure()
Я бы еще лямбду убрал:
def are_you_sure():
    print("Вы уверены? [Д/н (Y/n)]: ")
    response = input()

    out_result = {True: "Положительный ответ", False: "Отрицательный ответ", None: "Введено некорректное значение"};	
    answer_types = {"Д": True, "Y": True, "Н": False, "N": False}

    answer_key = answer_types.get(response.upper(), None)
    print(out_result[answer_key]+": {}".format(answer_key))

are_you_sure()

А в остальном самый лучший вариант.
Если речь идёт о статичной строке, то да: lambda тут лишняя. Если будет использоваться format, то можно подумать. Например:

>>> import datetime
>>> get_message = lambda answer_type, dtm, answer: '{0}: Your answer is {1}. You said "{2}"'.format(dtm, answer_type, answer)
>>> answers = {True: 'positive', False: 'negative', None: 'bullshit'}
>>> print(get_message(answers[None], datetime.datetime.now(), 'Use While loop or regexp in this task'))
2016-05-24 03:49:20.928235: Your answer is bullshit. You said "Use While loop or regexp in this task"
Но опять же, lambda здесь пригодится только для повторного использования а в первом случае — если шаблоны будут отличаться по позиции строки для формата.
Я бы пошёл на один шаг дальше.
Не итерации по псевдо-таблице, а честную таблицу:

def positive() :
  print "принято!"
  return True
def negative() :
  print "отказано."
  return False
def mistake() :
  print "ошибка, попробуйте ещё"

actions_map_source = {
  ( "y","Y","д","Д" ) : positive,
  ( "n", "N", "н", "Н" ) : negative,
}
# вот этот шаг
action_map = { k:v for (ks,v) in actions_map_source.iteritems() for k in ks }

def are_you_sure(what):
  answers = '/'.join(sorted(k for k in action_map.keys()))
  while True:
    response = raw_input("вы уверены, что %s? [%s] " % (what, answers))
    action = action_map.get(response, None)
    if action:
      return action()
    else:
      mistake()

print are_you_sure("надо думать")

как выше заметили — некоторые любят писать фреймворк для простейших задач.
А еще у вас при запуске будет

«надо думать YNynДдНн»

что не очень показательно, что это выбор ответов, а не пожелание к размышлению
Ну, это уже на вкус и цвет. Я особенно не заморачивался тем, как красиво отформатировать варианты.
Так-то можно
answers = '/'.join( ''.join(ks) for ks in actions_map_source.keys() ) # "yYдД/nNнН"


А что до фреймворка, то всё зависит от количества и способа повторений в коде.
Для однократного использования можно и самый тупой вариант наколбасить.
while True:
  response = raw_input("вы уверены, что пора валить? [Y/н]")
  if action == 'Y' or action == '':
    print "Time to go!"
    exit()
  elif action == 'н':
    print "Было бы предложено."
    break
  else:
    print "Определитесь, товарищ!"
    continue
И в вашем однократном примере, почему то Y заглавное, n маленькое, пустое считается за Y.
То есть стандартные ошибки программиста, который не может написать fizzbuzz? ;)
Это было умышленно.
Буквы «Y» и «н» находятся на одной кнопке, и — да, по умолчанию пора валить!

А для однократного использования не жалко и вручную всё наколбасить — "[Y/N], [y/n], [Д/Н], [д/н]"…
про Способ №3 — если в задаче нужно искать, является ли элемент частью какого-то множества, лучше ислопьзовать set (выглядит как словарь только из ключей: {"Y","y","Д","д"}), а не list. Всё дело в том, что проверка на вхождение у set имеет сложность О(1), а у list — O(N) (линейно растёт с увеличением количества элементов). На четырёх элементах конечно не заметно, но чем больше элементов — тем больше будет отрыв.

Способ №4, кстати, работает некорректно, так как ответ «деревня», например, воспримется как положительный ответ, хотя должен быть некорректный. Всё дело в том, что re.match пытается сматчить начало строки, и не обязательно, если вся остальная часть строки под регулярку не подходит, re.match вернёт объект матча, который приводится к True

На четырёх элементах можно увидеть и обратное: проверка наличия в списке будет быстрее, чем проверка наличия в множестве. В pypy-2.6.0-r1, например, timeit.timeit('"д" in {"Y","y","Д","д"}') в 4—5 раз медленнее timeit.timeit('"д" in ("Y","y","Д","д")') и timeit.timeit('"д" in ["Y","y","Д","д"]'), в jython-2.7.0 — в 1,5 (список)—2,5 (кортеж) медленнее, в python-2.7.10-r1 — в 2 раза медленнее. В остальных случаях (python-3.4.3-r1, pypy3-2.4.0) медленнее (…)/[…] (где‐то в два раза). Короче, не стоит заниматься фигнёй на пустом месте, сама проверка на наличие может и медленнее, но этот список/множество создаётся при каждом вызове функции, а создание множества затратнее создания списка. Ну и коэффициент при единице у множества и при N у списка совершенно разный.


Спекуляции вроде «у этого O(N), у того O(1)» без собственно тестов (причём, желательно, на реальном коде, а не как я проверял выше) легко могут привести к деоптимизации. Слишком много факторов: может твой список процессор нормально пихает в кэш, а множество нет. Может при 1 у множества огромный коэффициент. Может PyPy оптимизирует x in […] в несколько десятков тактов, а x in {…} (из‐за того, что так реже пишут) — в несколько сотен. И т.д.


Пока что по моим тестам получается, что лучше не заморачиваться и писать кортеж.

>> Спекуляции вроде «у этого O(N), у того O(1)»

Дело в том, что при указании сложности мы забываем про коэффициенты и «незначительные» члены, а на практике получается, что сортировка вставками работает быстрее быстрой сортировки на малых наборах данных.

Ну и про преждевременную оптимизацию, как корень всех бед тоже не стоит забывать.
Перед комментарием гонял тесты на 2.7.11 |Anaconda 2.5.0 (64-bit) — там способ через set и list оказались одинаковы по скорости на четырёх элементах.

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

В любом случае спасибо за комментарии. Внесу дополнения в статью.
Сет стоит использовать потому, что это коллекция которая логически лучше подходит под использование (x in set).
Для меня правильный способ должен начинаться с:

from distutils.util import strtobool

Стараюсь обходить импорты в таких простых задачах. Тем более, что strtobool всё равно заставит написать if...else...except. На 2 строки больше получится со списком/кортежем/множеством.
С питоном вечно так: пишешь код, а потом узнаешь что это уже есть в стандартной библиотеке :)
if response in 'yYдД':
    # ...
elif response in 'nNнН':
    # ...
Тогда уж 'yYдД'.split(), иначе ответы вроде 'yYд' тоже будут приниматься.
В таком случае лучше len(response) проверить сначала
Тогда, если идиоматически писать, можно предложить такой вариант:
if response[:1] in 'yYдД':
#…
elif response[:1] in 'nNнН':
#…
Плохой вариант. Ответы 'yYд' по прежнему будут приниматься, а кроме них ещё и 'yn' и 'y_всякая_ерунда'.
Плохая идея. В локали UTF-8 и в питоне-2 разбивка строки будет не на буквы, а на байты…
Понимаю, что это опечатка, но всё же:
>>> list('1234')
['1', '2', '3', '4']
>>> '1234'.split()
['1234']
Мой пример для python2.

Там будет то же самое в случае со split(). А в случае с list() разбивка будет на байты (если не указано что‐то вроде from __future__ import unicode_literals).

По логике, вторую проверку можно и не делать.
нужно, там же еще есть incorrect answer
А как же тогда ответить пользователю «моя твоя не понимать»? Некорректный ответ к отмене операции приводить не должен, во всяком случае, не всегда.
Если вариантов ответов очень много, можно сэкономить символов на print'ах:

while True:
    response = input('Вы уверены? [Д/н (Y/n)]: ')

    msgs = {
        'yYдД': 'Положительный ответ',
        'nNнН': 'Отрицательный ответ',
    }
    defalut = 'Введено некорректное значение'

    msg = next((v for k, v in msgs.items() if response in k), defalut)

    print('{}: {}'.format(msg, response))

Ваш вариант принимает ответы 'Yд', 'Nн'и т.д. К тому же, несмотря на кажущуюся изящность предложенного решения, идет разбор словаря (окей, это py3, список ключей достается бесплатно), а сам поиск происходит в строках 'yYдД', т.е. вроде как используется самый быстрый инструмент, но по факту ради красоты.


Чтобы два раза не вставать:


В этом способе тоже можно использовать принцип отсечения лишнего и сократить регулярные выражения до вида [YД] и [NН] с помощью метода upper().

Достаточно добавить флаг игнорирования регистра:


import re

positive = ('y', 'yes', 'д', 'да', 'si', 'ja')
positive_check = re.compile(r'^({})$'.format('|'.join(positive)), re.I)  # вот это re.I
messages = {
    0: 'Отрицательный ответ',
    1: 'Положительный ответ',
}

while True:
    response = input('Вы уверены? [Д/н (Y/n)]: ')
    choice = response or ''  # Оно может быть None?
    match = positive_check.match(choice)
    msg = messages[bool(match)]
    print(msg)

    # Для любителей экономия строк и дебаггеров-экстрасенсов
    response = input('Вы уверены? [Д/н (Y/n)]: ')
    print(messages[bool(positive_check.match(response))])
UFO just landed and posted this here
Плюсую за изяшное решение, но оно не самое красивое с точки зрения поддержки кода.
Если нужно добавлять новые значения будет другой разработчик — оно читается и разбирается хуже, чем решение из предыдущего комментария, где позитивный и негативный ответ явно разделены.
UFO just landed and posted this here
UFO just landed and posted this here
Верно, и обратите внимание — позитивный и негативный ответ — разделены ;)
Вопрос начинающего питониста:
while True: там зачем? Оно же всегда верно и всегда будет выполняться. По идее можно выбросить и функция все равно будет работать.
Бесконечный цикл, чтобы функция постоянно запрашивала ввод. В данном случае необходимо для проверки работы всех вариантов (что б не перезапускать программу для ввода другого варианта ответа).
UFO just landed and posted this here
А все замечали в таких диалогах, что «Y» и «н» — на одной кнопке, а потому в зависимости от выбранного языка одна и та же кнопка даст да либо нет?

Да. Например, в Ubuntu apt аналогичным образом запрашивает подтверждение. Если слишком критично, то лучше просить ввести полностью Yes или No, как это реализовано в некоторых консольных программах.

Для новичков наверное ещё стоит сделать небольшую ремарочку о том, что ожидать от пользователя ввода [YyNnДдНн] это плохая идея, потому как в русской раскладке «Н» находится на одной и той же кнопке с «Y».
Ой, наверное не стоит заваривать чай между обновлением комментариев и нажатием кнопки «Написать».

Также стоит отметить, что обычно написание одного из вариантов с заглавной буквы (Yes/Да в данном случае) обычно означает, что это вариант по умолчанию и может быть выбран вводом пустой строки. Примеры в статье это не обрабатывают в текущий момент.

Так бы и написал начинающий программист :)

Так бы написали и опытный. Ибо негоже нарушать принцип наименьшего удивления.

Тогда, если все что не является положительным ответом, считать отрицательным, это дело можно упростить до такого вида:
response = input('Вы уверены? [Д/н (Y/n)]: ').lower()
print(True if response in ['y', 'д', ''] else False)

Вопрос не в упрощении кода (проверка response == '' слегка усложнит его), а в уменьшении ментальной нагрузки на пользователя программы.


Т. е. если я вижу Upgrade packages? [y/N], то нажав просто энтер ожидаю, что установка будет отменена; набрав t (промазав по y) ожидаю, что меня спросят повторно.


Если это не так (нестандартное поведение), то мне придётся специально помнить о том, что эта программа ведёт себя не по-человечески и надо вводить y и только y. Примерно, как если бы принималась только заглавная n и только прописная y.

Полностью согласен.
def check_answer():
	response = input('Вы уверены? [Д/н (Y/n)]: ').lower()
	if response in {'y', 'д', ''}:
		return True
	elif response in {'n', 'н'}:
		return False
	else: 
		print('Ответом должен быть один из символов из набора [Д, д, Y, y, Н, н, N, n]')
		return check_answer()
		
if check_answer():
	print('Действие подтверждено')
else:
	print('Действие отменено')

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


то есть по-хорошему это нечто вроде


if ( input =~ /^[YДNН]$/i ) {
  if ( input =~ /YД/i ) {
    # yes
  }
  else {
    # no
  }
}
else {
  raise ArgumentError.new( "input" );
}
UFO just landed and posted this here

Искренне недоумеваю, а какая разница? Ловите на Perl:


if ( $input =~ m/^[YДNН]$/i ) {
  if ( $input =~ m/YД/i ) {
    # yes
  }
  else {
    # no
  }
}
else {
  die "input";
}

Вот на C++, навскидку (могут быть мелкие ошибки, не писал лет двадцать):


#include <boost/regex.hpp> 

using namespace boost;

smatch m;

if ( regex_match( input, m, "^[YyДдNnНн]$" ) {
  if ( regex_match( input, m, "[YyДд]" ) {
    // yes
  }
  else {
   // no
  }
}
else {
  throw new ArgumentError( "input" );
}

Странные фанатики языка мне минус поставили. Знал бы Python, написал бы на нем. Но какая разница, если речь о концепциях, как обрабатывать входные данные. Язык вторичен в данном случае.

Ну и, конечно, наверняка в Python есть аналог вот этому:


case input
when /YД/i
  # Yes
when /NН/i
  # No
else
  throw ArgumentError.new "input"
end
UFO just landed and posted this here

и вправду, ребячество. я уже посмотрел, встроенного switch в python нет, но очень легко реализуется.

UFO just landed and posted this here

Ничуть не спорю. В том же Perl использование given/Switch сейчас не рекомендовано (experimental smartmatch feature).


Я хотел в первую очередь сместить акцент на то, чтобы разнести проверку на корректность значения и само значение. А оператор выбора — это по настроению.

Это идиотизм, функция обработчик никак не заменит выражение в switch, если вы не передадите в нее все текущее окружение. А в elif тупо намного больше писать. Добавить switch можно в хороших языках типа лиспа и ребола, тут же речь про питон, в котором программисты без одобрения гвидо ничего сделать с языком не могут.
UFO just landed and posted this here
Наличие switch в языке — показатель того, что создатели хотябы хотят сделать его удобным для пользователей, а не откровенно посылают их куда подальше в духе жрите что дают (стоит ли вспоминать мои любимые однострочные лямбды в 2016 году?).
Я добавлял в лисп и ребол то, что мне было нужно. И важна именно возможность добавления, а не то, что добавлял конкретный программист, ведь судить о языке по опыту одного человека не очень умно, согласитесь?

Switch в языке высокого уровня, где всё есть объекты — излишек. Нужен либо паттерн-матчинг, как в скале, например, либо ничего.


Всё потому что switch хорошо работает только с примитивами. Свич в подавляющем количестве языков сравнивает через ==. Для чисел и строк это нормально. Для объектов он бесполезен:


class Foo:
    def __init__(self, bar, baz):
        self.bar = bar
        self.baz = baz

a = Foo(1, 2)
b = Foo(1, 2)

a == b  # => False

Сейчас я в основном пишу на java и js, тут есть свич. Но в коде его надобность практически отсутствует.
Лямбды не дают спать? Ну язык не поощряет длинные анонимные функции.


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

Паттрен-матчинг это следующий уровень switch, но к сожалению додуматься до него смогли лишь в двух с половиной языках, и питон не один из них.
Switch на объектах все еще полезен, ведь есть возможность перегружать __eq__ (или как оно там правильно).

То, что в твоем коде не используется switch — не показатель, в примере в этом посте он, например. может быть использован.
Ну язык не поощряет длинные анонимные функции

Это откровенная неправда, Гвидо был не прочь сделать нормальные лямбды, да вот только череда провальных решений в дизайне языка не позволила сделать ничего лучше, чем то что мы получили в итоге. Как минимум многострочные лямбды не парсятся из-за того, что отступы являются частью семантики. Но теперь принято называть откровенно плохой дизайн «не поощрением».

В отличие от тех же лиспов, где ты хз, функция ли это, или же макрос, который изменяет синтаксис.

Аргумент уровня — в динамическом языке ты хз, X — это число, объект, функция или строка.
Возможностей в лиспах много, но почему-то популярными они не становятся.

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

Простите за некропостинг, но все же.


В Ruby оператор switch (который case) работает по-особенному.
На объектах есть перегружаемый оператор ===, который в книге Мацумото называется case-equality operator, созданный специально для него.


Собственно:


case(object)
when cond1 then 1
when cond2 then 2
end

эквивалентно:


if cond1 === object
  1
elsif cond2 === object
  2
end

Эта часть языка мне не нравится, поскольку может ввести в заблуждение (и вводит!).
Поэтому лично я предпочитаю использовать его форму без параметра (где не используется ===) ради простых веток:


case
when then something1
when cond2 then something2
when cond3 then something3
end

vs


if cond1
  something1
elsif cond2
  something2
elsif cond3
  something3
end
Мыши плакали, кололись, но продолжали грызть кактус
Первым на ум приходит вариант №3 и, собственно, им всё и заканчивается. Остальные варианты уже какая-то обфускация.
По мне, вот самый простой, читаемый и «поддерживаемый» вариант.
def check_answer():

    response = input('Вы уверены? [Д/н (Y/n)]: ').lower()

    if response in ['y', 'д']:
        return "True"
    elif response in ['n', 'н']:
        return "False"
    else:
        return 'Ругань'
Список лучше заменить кортежем — он пошустрее. А то и вовсе строкой, так синтаксической ряби меньше.
С чего вы взяли, что «он пошустрее»?
списки в Python гетерогенны, т.е. принимают любые типы.
кортежи неизменяемы. Строка тоже кортеж, но при этом еще и типизированный.
1) Не понимаю, как ваше первое предложение соотносится со вторым? И что, что списки гетерогенны? А кортежи не гетерогенны? Как это вообще связано с тем, что я сказал?
2) Как связано то, что кортежи неизменяемы с тем, что итерация или проверка на вхождение по ним якобы шустрее аналогичной для списка?
3) Нет, строка в Python совсем не тоже самое, что кортеж.
4) А теперь самое главное. Возьмём кусок вышеприведенного кода с заменой списка на кортеж и без замены, вот так:

>>> test1 = lambda response: response in ('y', 'д') 
>>> test2 = lambda response: response in ['y', 'д']
>>>

затем дизасемблируем обе функции, сначала ту, что с кортежем:

>>> from dis import dis
>>> dis(test1)
  1           0 LOAD_FAST                0 (response)
              3 LOAD_CONST               3 (('y', 'д'))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
>>> 

всё логично и предсказуемо, а теперь ту, что со списком:

>>> dis(test2)
  1           0 LOAD_FAST                0 (response)
              3 LOAD_CONST               3 (('y', 'д'))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
>>> 

неожиданно? как видите, листинг вообще ничем не отличается.
Знакомьтесь, Peephole Optimizer, встроенный в CPython.
Действительно, ошибался. Разницы нет. Что и подтверждают тесты
Но на самом деле последний вариант предпочтительнее

В последнем варианте надо писать len("5") == 1 and "5" in "123456789", а не то, что вы написали: первый и второй не эквивалентны третьему. И на это обращали внимание здесь уже много раз. И ещё везде нужно написать переменную, а не "5": загрузка значения переменной занимает большее время, чем загрузка константы, а в последнем варианте это делается два раза. Может оптимизатор поможет, может нет, но в любом случае реальный код работает с переменной.

Кто ж спорит, меня возмутила исключительно фраза «Список лучше заменить кортежем — он пошустрее. ».
хмм… но в Дзене Питона говорится, что лучше всего иметь один очевидный способ решения задачи…

На самом деле, не увидел в статье разного мышления. По-моему, это одно и то же решение, которое улучшается со временем. Единственное, что выделяется — решение с помощью регулярок и то… В общем, имхо способы одинаковые, никакого разного мышления здесь нет, просто лаконичность меняется.
Три вопроса:

— почему не используется кортеж вместо списка?
— почему нельзя скомбинировать .upper с кортежем и тем самым сократить длину вдвое?
— почему в примере с регекспами не используется re.IGNORECASE?
Пишу MMPI(псих. тест), где для удобства обработки данных должно сохраняться только 'д' или 'н'.
Использую вариант:
answer = (((answer[0].lower()).replace('y', 'д')).replace('n', 'н'))
Статья вдохновила на ненормальный кодинг сделал на С++
which(response)
(
	oneof('Y', 'y', 'Д', 'д'), [&]
	{
		cout << "True";
	},
	oneof('N', 'n', 'Н', 'н'), [&]
	{
		cout << "False";
	},
	always<true>(), [&]
	{
		cout << "Error";
	}	
);
Sign up to leave a comment.

Articles