Python: Мышление программиста

  • Tutorial

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


В качестве примера взят простой случай — реализация диалога подтверждения какой-либо операции. Программа задает пользователю вопрос Вы уверены? [Д/н (Y/n)]:, на который требуется ответить, введя одно из восьми допустимых значений (Д, д, Н, н, Y, y, N, n).


Способ №1

Первое, что приходит на ум, это реализовать проверку совпадения каждого из условий следующим образом:


    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

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

            if response == "Д" or response == "д":
                print("Положительный ответ: {}".format(response))
            elif response == "Y" or response == "y":
                print("Положительный ответ: {}".format(response))
            elif response == "Н" or response == "н":
                print("Отрицательный ответ: {}".format(response))
            elif response == "N" or response == "n":
                print("Отрицательный ответ: {}".format(response))
            else:
                print("Введено некорректное значение: {}".format(response)

    are_you_sure1()  

Можно было расписать все 8 блоков if/elif, но для краткости используется логический оператор or (ИЛИ) для каждой из пар возможных значений.


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


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


Способ №2

Второй способ заключается в отсечении лишних сущностей (Бритва Оккама). Не имеет значения в каком именно регистре будет введен символ в ответ на вопрос программы. Поэтому воспользуемся методом строки upper() или lower() для приведения символов к верхнему или нижнему регистру:


    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    def are_you_sure2():
        while True:
            print("Вы уверены? [Д/н (Y/n)]: ")
            # Принимает значение, введенное пользователем и
            # переводит его в верхний регистр
            response = input().upper()  

            if response == "Д" or response == "Y":
                print("Положительный ответ: {}".format(response))
            elif response == "Н" or response == "N":
                print("Отрицательный ответ: {}".format(response))
            else:
                print("Введено некорректное значение: {}".format(response))

    are_you_sure2()

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


Способ №3

Еще один способ — проверить входит ли введенное значение в список допустимых.


    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

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

            if response in ["Д", "д", "Y", "y"]:
                print("Положительный ответ: {}".format(response))
            elif response in ["Н", "н", "N", "n"]:
                print("Отрицательный ответ: {}".format(response))
            else:
                print("Введено некорректное значение: {}".format(response))

    are_you_sure3()

Проверка осуществляется с помощью оператора вхождения in. Пример альтернативного мышления.


Способ №4

Еще один пример альтернативного мышления, заключающийся в использовании регулярных выражений. Для этого воспользуемся стандартным модулем для работы с регулярными выражениями re и методом re.match().


    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    import re    # Импорт модуля для работы с регулярными выражениями

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

            if re.match("[yYдД]", response):
                print("Положительный ответ: {}".format(response))
            elif re.match("[nNнН]", response):
                print("Отрицательный ответ: {}".format(response))
            else:
                print("Введено некорректное значение: {}".format(response))

    are_you_sure4()

Метод re.match(шаблон, строка) ищет по заданному шаблону в начале строки. В качестве шаблона используется регулярное выражение [yYдД] и [nNнН] (квадратные скобки группируют символы). Более подробно тема регулярных выражений раскрывается в статьях, ссылки на которые приведены в конце. Также рекомендую книгу «Освой самостоятельно регулярные выражения. 10 минут на урок» Бена Форты.


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


Также стоит отметить, что в данном случае корректными будут считаться любые значения, начинающиеся с разрешенных символов, например, да, Yum и т.д., так как re.match() ищет совпадения только с начала строки. В отличии от других способов, где должно быть точное соответствие и любые лишние символы вызовут сообщение о некорректности. Можно считать это преимуществом, но ничего не мешает исправить такое поведение.


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


Способ №5

В комментариях random1st привел еще один изящный способ:


    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    def check_answer():
        while True:
            response = input('Вы уверены? [Д/н (Y/n)]: ')
            try:
                print(  0 <= "YyДдNnНн".index(response) <=3 and "True" or "False")
            except ValueError:
                print ("Incorrect")

    check_answer()

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


Ремарка

@datacompboy и MamOn указали на еще один интересный момент. Символ Y и Н на русскоязычной клавиатуре находятся на одной кнопке, что при невнимательности со стороны пользователя может привести к совершенно противоположным результатам. Подобные моменты тоже нужно учитывать при разработке. Поэтому в приложениях, где ошибка выбора может привести к необратимым результатам, лучше затребовать подтверждение Yes/ No, чтобы наверняка исключить возможность ошибки.


Ссылки

Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 97
  • +1
    Если бы было больше ветвлений и/или в блоках условий было бы больше чем одна строка, я бы сделал так:

    # 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()
    
    • 0

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

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

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

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


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

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

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

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

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

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

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

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


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

            Я не ставил целью кого-либо запутать. Я работаю с 2.7.
            • 0
              Я не ставил целью кого-либо запутать. Я работаю с 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 в ряде случаев предполагаются двойные.

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

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

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

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

            Я про то, что идея то нормальная, но я бы сделал ключами дикта сами ответы — Y, Д, N, Н. Вышло бы на пару строчек больше тут, но проще понимать и получать действие в дальнейшем, цикл будет ненужен и тд.
            • 0
              А если понадобится поддержка китайского, будете для каждого варианта добавлять по строчке? А если вариантов 10? А я как раз упомянул, что такой код я написал бы в случае большого количества вариантов.
              • 0
                Если будет уж очень много вариантов — я просто сгенерирую этот словарь.
        • 0
          Поддерживаю данный вариант, но не совсем понятно для чего нужен словарь, если идёт перебор значений? Словарь удобен, когда мы обращаемся по 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()
          
          • +1
            Я бы еще лямбду убрал:
            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()
            

            А в остальном самый лучший вариант.
            • 0
              Если речь идёт о статичной строке, то да: 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"
              
              • 0
                Но опять же, lambda здесь пригодится только для повторного использования а в первом случае — если шаблоны будут отличаться по позиции строки для формата.
          • 0
            Я бы пошёл на один шаг дальше.
            Не итерации по псевдо-таблице, а честную таблицу:

            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("надо думать")
            

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

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

              что не очень показательно, что это выбор ответов, а не пожелание к размышлению
              • 0
                Ну, это уже на вкус и цвет. Я особенно не заморачивался тем, как красиво отформатировать варианты.
                Так-то можно
                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
                
                • –1
                  И в вашем однократном примере, почему то Y заглавное, n маленькое, пустое считается за Y.
                  То есть стандартные ошибки программиста, который не может написать fizzbuzz? ;)
                  • 0
                    Это было умышленно.
                    Буквы «Y» и «н» находятся на одной кнопке, и — да, по умолчанию пора валить!

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

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

              На четырёх элементах можно увидеть и обратное: проверка наличия в списке будет быстрее, чем проверка наличия в множестве. В 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 {…} (из‐за того, что так реже пишут) — в несколько сотен. И т.д.


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

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

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

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

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

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

                    from distutils.util import strtobool
                    

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

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

                                      • 0
                                        Гм, и правда. Что-то меня переклинило ( Там в самом деле list.
                                • +1
                                  По логике, вторую проверку можно и не делать.
                                  • 0
                                    нужно, там же еще есть incorrect answer
                                    • 0
                                      А как же тогда ответить пользователю «моя твоя не понимать»? Некорректный ответ к отмене операции приводить не должен, во всяком случае, не всегда.
                                  • +2
                                    Если вариантов ответов очень много, можно сэкономить символов на 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))
                                    
                                    • 0

                                      Ваш вариант принимает ответы '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))])
                                      
                                    • +4
                                      def check_answer():
                                          while True:
                                              response = input('Вы уверены? [Д/н (Y/n)]: ')
                                              try:
                                                  print(  0 <= "YyДдNnНн".index(response) <=3 and "True" or "False")
                                              except ValueError:
                                                  print ("Incorrect")
                                      

                                      ValueError можно и не перехватывать, исключение вполне подходящее
                                      • 0
                                        Самое красивое решение, на мой взгляд
                                        • +5
                                          Плюсую за изяшное решение, но оно не самое красивое с точки зрения поддержки кода.
                                          Если нужно добавлять новые значения будет другой разработчик — оно читается и разбирается хуже, чем решение из предыдущего комментария, где позитивный и негативный ответ явно разделены.
                                          • +2
                                            Ох уж мне эти «писатели фреймворков для проверки ввода данных пользователем из двух значений»
                                          • 0
                                            Самое изящное предложил renskiy ИМХО.
                                            • 0
                                              Верно, и обратите внимание — позитивный и негативный ответ — разделены ;)
                                        • 0
                                          Вопрос начинающего питониста:
                                          while True: там зачем? Оно же всегда верно и всегда будет выполняться. По идее можно выбросить и функция все равно будет работать.
                                          • +2
                                            Бесконечный цикл, чтобы функция постоянно запрашивала ввод. В данном случае необходимо для проверки работы всех вариантов (что б не перезапускать программу для ввода другого варианта ответа).
                                            • 0
                                              А. Я бы просто бесконечный цикл в def main(): добавил.
                                            • 0
                                              Это исключительно для отладки. На самом деле все решение можно вообще уложить в одну строку, просто я написал так, как проверял работу. На самом деле достаточно условия «YyДдNnНн».index(response) <=3 — это выражение будет либо True либо False либо выбрасывать ValueError, а остальное так, бутафория
                                            • +3
                                              А все замечали в таких диалогах, что «Y» и «н» — на одной кнопке, а потому в зависимости от выбранного языка одна и та же кнопка даст да либо нет?
                                              • 0

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

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

                                                  Спасибо, внес дополнение в статью.

                                                  • +1
                                                    Ой, наверное не стоит заваривать чай между обновлением комментариев и нажатием кнопки «Написать».
                                                  • +1

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

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

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

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

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


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


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

                                                          • 0
                                                            Полностью согласен.
                                                            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('Действие отменено')
                                                            
                                                      • –4

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


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


                                                        if ( input =~ /^[YДNН]$/i ) {
                                                          if ( input =~ /YД/i ) {
                                                            # yes
                                                          }
                                                          else {
                                                            # no
                                                          }
                                                        }
                                                        else {
                                                          raise ArgumentError.new( "input" );
                                                        }
                                                        • +5
                                                          Как не ставь тег «Python», обязательно придет кто-нибудь и бесцеремонно напишет в комментах на Ruby.
                                                          • –2

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


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

                                                              Вот на 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" );
                                                              }
                                                              • +6

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

                                                              • –2

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


                                                                case input
                                                                when /YД/i
                                                                  # Yes
                                                                when /NН/i
                                                                  # No
                                                                else
                                                                  throw ArgumentError.new "input"
                                                                end
                                                                • +2
                                                                  Простите, я бы предложил Вам линейку в качестве метода окончательного выяснения отношений, но, пожалуй, это может быть расценено как ребячество. Воздержусь.
                                                                  • 0

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

                                                                    • +2
                                                                      Вместо switch можно использовать dict, где key — это вариант выбора, а value — функция-обработчик. Или использовать цепочку if | elif | else.   Уж поверьте, в  python  switch просто не нужен.
                                                                      • 0

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


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

                                                                        • –4
                                                                          Это идиотизм, функция обработчик никак не заменит выражение в switch, если вы не передадите в нее все текущее окружение. А в elif тупо намного больше писать. Добавить switch можно в хороших языках типа лиспа и ребола, тут же речь про питон, в котором программисты без одобрения гвидо ничего сделать с языком не могут.
                                                                          • +2
                                                                            Без сомнения, наличие switch в языке — это критический показатель качества. Простите, а Вы правда много чего добавили в Lisp или Rebol?
                                                                            • –1
                                                                              Наличие switch в языке — показатель того, что создатели хотябы хотят сделать его удобным для пользователей, а не откровенно посылают их куда подальше в духе жрите что дают (стоит ли вспоминать мои любимые однострочные лямбды в 2016 году?).
                                                                              Я добавлял в лисп и ребол то, что мне было нужно. И важна именно возможность добавления, а не то, что добавлял конкретный программист, ведь судить о языке по опыту одного человека не очень умно, согласитесь?
                                                                              • 0

                                                                                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, тут есть свич. Но в коде его надобность практически отсутствует.
                                                                                Лямбды не дают спать? Ну язык не поощряет длинные анонимные функции.


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

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

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

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

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

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

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

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


                                                                                    В 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
                                                                      • –1
                                                                        Мыши плакали, кололись, но продолжали грызть кактус
                                                                    • +6
                                                                      Первым на ум приходит вариант №3 и, собственно, им всё и заканчивается. Остальные варианты уже какая-то обфускация.
                                                                      • +1
                                                                        По мне, вот самый простой, читаемый и «поддерживаемый» вариант.
                                                                        def check_answer():
                                                                        
                                                                            response = input('Вы уверены? [Д/н (Y/n)]: ').lower()
                                                                        
                                                                            if response in ['y', 'д']:
                                                                                return "True"
                                                                            elif response in ['n', 'н']:
                                                                                return "False"
                                                                            else:
                                                                                return 'Ругань'
                                                                        
                                                                        • 0
                                                                          Список лучше заменить кортежем — он пошустрее. А то и вовсе строкой, так синтаксической ряби меньше.
                                                                          • 0
                                                                            С чего вы взяли, что «он пошустрее»?
                                                                            • 0
                                                                              списки в Python гетерогенны, т.е. принимают любые типы.
                                                                              кортежи неизменяемы. Строка тоже кортеж, но при этом еще и типизированный.
                                                                              • +1
                                                                                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.
                                                                                • +1
                                                                                  Действительно, ошибался. Разницы нет. Что и подтверждают тесты
                                                                                  • 0
                                                                                    Но на самом деле последний вариант предпочтительнее
                                                                                    • +2

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

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

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

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

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