Небольшая статья о том, как можно решить одну и ту же задачу несколькими способами. Предназначена для новичков в 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, чтобы наверняка исключить возможность ошибки.


Ссылки

Поделиться с друзьями
-->

Комментарии (90)


  1. toly
    22.05.2016 00:38
    +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()
    


    1. ZyXI
      22.05.2016 00:47

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


      1. toly
        22.05.2016 01:14

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

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


        1. ZyXI
          22.05.2016 01:27

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

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


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

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


          1. toly
            23.05.2016 01:18
            +2

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

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

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

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

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

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


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

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


            1. ZyXI
              24.05.2016 21:57

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


    1. loz
      22.05.2016 18:20

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


      1. toly
        23.05.2016 01:31

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

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

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

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


        1. loz
          23.05.2016 04:37

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

          Я про то, что идея то нормальная, но я бы сделал ключами дикта сами ответы — Y, Д, N, Н. Вышло бы на пару строчек больше тут, но проще понимать и получать действие в дальнейшем, цикл будет ненужен и тд.


          1. toly
            23.05.2016 07:25

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


            1. loz
              23.05.2016 12:00

              Если будет уж очень много вариантов — я просто сгенерирую этот словарь.


    1. werevolff
      23.05.2016 16:30

      Поддерживаю данный вариант, но не совсем понятно для чего нужен словарь, если идёт перебор значений? Словарь удобен, когда мы обращаемся по 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. MrShoor
        24.05.2016 04:54
        +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()
        

        А в остальном самый лучший вариант.


        1. werevolff
          24.05.2016 06:50

          Если речь идёт о статичной строке, то да: 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"
          


          1. werevolff
            24.05.2016 06:59

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


    1. nickolaym
      25.05.2016 13:04

      Я бы пошёл на один шаг дальше.
      Не итерации по псевдо-таблице, а честную таблицу:

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


      1. saboteur_kiev
        26.05.2016 14:52

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

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

        что не очень показательно, что это выбор ответов, а не пожелание к размышлению


        1. nickolaym
          26.05.2016 15:36

          Ну, это уже на вкус и цвет. Я особенно не заморачивался тем, как красиво отформатировать варианты.
          Так-то можно

          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. saboteur_kiev
            26.05.2016 16:53
            -1

            И в вашем однократном примере, почему то Y заглавное, n маленькое, пустое считается за Y.
            То есть стандартные ошибки программиста, который не может написать fizzbuzz? ;)


            1. nickolaym
              26.05.2016 22:33

              Это было умышленно.
              Буквы «Y» и «н» находятся на одной кнопке, и — да, по умолчанию пора валить!

              А для однократного использования не жалко и вручную всё наколбасить — "[Y/N], [y/n], [Д/Н], [д/н]"…


  1. griganton
    22.05.2016 01:53

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

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


    1. ZyXI
      22.05.2016 02:21
      +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. gorodnev
        22.05.2016 11:47
        +1

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

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

        Ну и про преждевременную оптимизацию, как корень всех бед тоже не стоит забывать.


      1. griganton
        22.05.2016 15:08

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

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


    1. OneManRevolution
      22.05.2016 02:26

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

      В любом случае спасибо за комментарии. Внесу дополнения в статью.


    1. Andrey_Solomatin
      23.05.2016 08:50

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


  1. Tanner
    22.05.2016 06:44
    +4

    Для меня правильный способ должен начинаться с:

    from distutils.util import strtobool
    


    1. conformist
      22.05.2016 07:18

      Стараюсь обходить импорты в таких простых задачах. Тем более, что strtobool всё равно заставит написать if...else...except. На 2 строки больше получится со списком/кортежем/множеством.


    1. Andrey_Solomatin
      26.05.2016 00:17

      С питоном вечно так: пишешь код, а потом узнаешь что это уже есть в стандартной библиотеке :)


  1. renskiy
    22.05.2016 07:26
    +16

    if response in 'yYдД':
        # ...
    elif response in 'nNнН':
        # ...
    


    1. bak
      22.05.2016 09:56
      +4

      Тогда уж 'yYдД'.split(), иначе ответы вроде 'yYд' тоже будут приниматься.


      1. renskiy
        22.05.2016 10:12

        В таком случае лучше len(response) проверить сначала


      1. buriy
        23.05.2016 08:01

        Тогда, если идиоматически писать, можно предложить такой вариант:
        if response[:1] in 'yYдД':
        #…
        elif response[:1] in 'nNнН':
        #…


        1. bak
          23.05.2016 10:37

          Плохой вариант. Ответы 'yYд' по прежнему будут приниматься, а кроме них ещё и 'yn' и 'y_всякая_ерунда'.


      1. nickolaym
        26.05.2016 22:31
        +1

        Плохая идея. В локали UTF-8 и в питоне-2 разбивка строки будет не на буквы, а на байты…


      1. ADR
        28.05.2016 16:21
        +1

        Понимаю, что это опечатка, но всё же:
        >>> list('1234')
        ['1', '2', '3', '4']
        >>> '1234'.split()
        ['1234']


        1. bak
          28.05.2016 18:09
          -1

          Мой пример для python2.


          1. ZyXI
            28.05.2016 19:12
            +1

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


            1. bak
              28.05.2016 19:20

              Гм, и правда. Что-то меня переклинило ( Там в самом деле list.


    1. Balda2000
      22.05.2016 14:56
      +1

      По логике, вторую проверку можно и не делать.


      1. thunderspb
        23.05.2016 13:22

        нужно, там же еще есть incorrect answer


      1. FalconM
        28.05.2016 16:21

        А как же тогда ответить пользователю «моя твоя не понимать»? Некорректный ответ к отмене операции приводить не должен, во всяком случае, не всегда.


  1. veveve
    22.05.2016 08:52
    +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))
    


    1. magic4x
      23.05.2016 01:19

      Ваш вариант принимает ответы '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))])
      


  1. random1st
    22.05.2016 11:10
    +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 можно и не перехватывать, исключение вполне подходящее


    1. ThePowerfulDeeZ
      22.05.2016 11:49

      Самое красивое решение, на мой взгляд


      1. saboteur_kiev
        22.05.2016 14:16
        +5

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


        1. random1st
          22.05.2016 16:49
          +2

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


      1. random1st
        22.05.2016 18:12

        Самое изящное предложил renskiy ИМХО.


        1. saboteur_kiev
          23.05.2016 04:13

          Верно, и обратите внимание — позитивный и негативный ответ — разделены ;)


  1. Meklon
    22.05.2016 11:34

    Вопрос начинающего питониста:
    while True: там зачем? Оно же всегда верно и всегда будет выполняться. По идее можно выбросить и функция все равно будет работать.


    1. Amareis
      22.05.2016 11:45
      +2

      Бесконечный цикл, чтобы функция постоянно запрашивала ввод. В данном случае необходимо для проверки работы всех вариантов (что б не перезапускать программу для ввода другого варианта ответа).


      1. Meklon
        22.05.2016 15:00

        А. Я бы просто бесконечный цикл в def main(): добавил.


    1. random1st
      22.05.2016 16:54

      Это исключительно для отладки. На самом деле все решение можно вообще уложить в одну строку, просто я написал так, как проверял работу. На самом деле достаточно условия «YyДдNnНн».index(response) <=3 — это выражение будет либо True либо False либо выбрасывать ValueError, а остальное так, бутафория


  1. datacompboy
    22.05.2016 14:21
    +3

    А все замечали в таких диалогах, что «Y» и «н» — на одной кнопке, а потому в зависимости от выбранного языка одна и та же кнопка даст да либо нет?


    1. OneManRevolution
      22.05.2016 14:32

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


  1. MamOn
    22.05.2016 14:41
    +1

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


    1. OneManRevolution
      22.05.2016 14:47
      +1

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


    1. MamOn
      22.05.2016 14:49
      +1

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


  1. grossws
    22.05.2016 15:45
    +1

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


    1. Andrey_Solomatin
      23.05.2016 08:36

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


      1. grossws
        23.05.2016 12:34

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


    1. FantomNotaBene
      23.05.2016 12:45

      Тогда, если все что не является положительным ответом, считать отрицательным, это дело можно упростить до такого вида:

      response = input('Вы уверены? [Д/н (Y/n)]: ').lower()
      print(True if response in ['y', 'д', ''] else False)
      


      1. grossws
        23.05.2016 12:55

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


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


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


        1. FantomNotaBene
          23.05.2016 14:24

          Полностью согласен.

          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('Действие отменено')
          


  1. akzhan
    22.05.2016 17:09
    -4

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


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


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


    1. random1st
      22.05.2016 17:18
      +5

      Как не ставь тег «Python», обязательно придет кто-нибудь и бесцеремонно напишет в комментах на Ruby.


      1. akzhan
        22.05.2016 17:23
        -2

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


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


      1. akzhan
        22.05.2016 17:34
        -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" );
        }


      1. akzhan
        22.05.2016 18:02
        +6

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


    1. akzhan
      22.05.2016 17:20
      -2

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


      case input
      when /YД/i
        # Yes
      when /NН/i
        # No
      else
        throw ArgumentError.new "input"
      end


      1. random1st
        22.05.2016 17:28
        +2

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


        1. akzhan
          22.05.2016 17:38

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


          1. random1st
            22.05.2016 17:42
            +2

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


            1. akzhan
              22.05.2016 17:50

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


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


            1. loz
              22.05.2016 18:28
              -4

              Это идиотизм, функция обработчик никак не заменит выражение в switch, если вы не передадите в нее все текущее окружение. А в elif тупо намного больше писать. Добавить switch можно в хороших языках типа лиспа и ребола, тут же речь про питон, в котором программисты без одобрения гвидо ничего сделать с языком не могут.


              1. random1st
                22.05.2016 18:49
                +2

                Без сомнения, наличие switch в языке — это критический показатель качества. Простите, а Вы правда много чего добавили в Lisp или Rebol?


                1. loz
                  22.05.2016 23:01
                  -1

                  Наличие switch в языке — показатель того, что создатели хотябы хотят сделать его удобным для пользователей, а не откровенно посылают их куда подальше в духе жрите что дают (стоит ли вспоминать мои любимые однострочные лямбды в 2016 году?).
                  Я добавлял в лисп и ребол то, что мне было нужно. И важна именно возможность добавления, а не то, что добавлял конкретный программист, ведь судить о языке по опыту одного человека не очень умно, согласитесь?


                  1. bromzh
                    23.05.2016 12:29

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


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


                    1. loz
                      23.05.2016 13:00
                      +3

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

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

                      Ну язык не поощряет длинные анонимные функции

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

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

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

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


      1. werevolff
        23.05.2016 16:36
        -1

        Мыши плакали, кололись, но продолжали грызть кактус


  1. dna
    22.05.2016 20:07
    +6

    Первым на ум приходит вариант №3 и, собственно, им всё и заканчивается. Остальные варианты уже какая-то обфускация.


  1. yorko
    23.05.2016 02:21
    +1

    По мне, вот самый простой, читаемый и «поддерживаемый» вариант.

    def check_answer():
    
        response = input('Вы уверены? [Д/н (Y/n)]: ').lower()
    
        if response in ['y', 'д']:
            return "True"
        elif response in ['n', 'н']:
            return "False"
        else:
            return 'Ругань'
    


    1. lightman
      23.05.2016 08:22

      Список лучше заменить кортежем — он пошустрее. А то и вовсе строкой, так синтаксической ряби меньше.


      1. Amper
        26.05.2016 21:55

        С чего вы взяли, что «он пошустрее»?


  1. Sna1L
    23.05.2016 11:20

    хмм… но в Дзене Питона говорится, что лучше всего иметь один очевидный способ решения задачи…

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


  1. vladbarcelo
    23.05.2016 11:58
    +1

    Три вопроса:

    — почему не используется кортеж вместо списка?
    — почему нельзя скомбинировать .upper с кортежем и тем самым сократить длину вдвое?
    — почему в примере с регекспами не используется re.IGNORECASE?


  1. Gra4_art
    23.05.2016 11:58
    -1

    Пишу MMPI(псих. тест), где для удобства обработки данных должно сохраняться только 'д' или 'н'.
    Использую вариант:
    answer = (((answer[0].lower()).replace('y', 'д')).replace('n', 'н'))


  1. Chaos_Optima
    23.05.2016 18:30

    Статья вдохновила на ненормальный кодинг сделал на С++

    which(response)
    (
    	oneof('Y', 'y', 'Д', 'д'), [&]
    	{
    		cout << "True";
    	},
    	oneof('N', 'n', 'Н', 'н'), [&]
    	{
    		cout << "False";
    	},
    	always<true>(), [&]
    	{
    		cout << "Error";
    	}	
    );