Python Craftsman: советы по написанию условного кода ветвления

машинное обучение Python Язык программирования Тенсент

Приветствую всех вОблако Tencent + сообщество, получить больше крупной технической практики Tencent по галантерее ~

Эта статья написанаГусиная фабрика YouwenОпубликован вКолонка «Облако + сообщество»

Автор: Чжу Лэй | Старший инженер, Tencent IEG

Что такое "Мастер Питона"?

Я всегда чувствовал, что программирование — это в некотором смысле «ремесло», потому что элегантный и эффективный код так же приятен для глаз, как и совершенная ручная работа.

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

Цикл статей "Мастер Python" - это моя маленькая попытка. Он фокусируется на том, чтобы поделиться некоторыми «мелкими» вещами в программировании на Python. Я надеюсь, что это может помочь каждому мастеру на пути программирования.

Серия статей:

Преамбула

Написание кода условного перехода является неотъемлемой частью процесса кодирования.

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

Написание хорошего кода условного ветвления важно, потому что плохая, сложная обработка ветвления может привести к путанице и ухудшить качество кода. Итак, в этой статье речь пойдет о том, на что следует обращать внимание при написании кода ветвления на Python.

Код ветвления в Python

Python поддерживает наиболее распространенные операторы условного ветвления if/else, но в нем отсутствуют операторы switch/case, распространенные в других языках программирования.

Кроме того, Python также предоставляет ветки else для циклов for/while и операторов try/except, которые могут быть очень полезны в некоторых особых сценариях.

Ниже я расскажу о том, как написать хороший условный код ветвления с трех аспектов: лучшие практики, распространенные методы и распространенные ловушки.

Лучшие практики

1. Избегайте многоуровневой вложенности веток

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

Слишком глубокая вложенность веток — одна из самых частых ошибок многих начинающих программистов. Если начинающий программист JavaScript пишет много уровней вложенности ветвей, вы можете увидеть слои фигурных скобок: if { if { if { ... }}}. В просторечии известен как «Ад вложенных операторов If»*.

Но поскольку Python использует отступы вместо {}, глубоко вложенные ветки могут иметь более серьезные последствия, чем в других языках. Например, слишком большой уровень отступа может легко привести к тому, что код превысит допустимый уровень.PEP8Ограничение на количество слов в строке, указанное в . Давайте посмотрим на этот код:

def buy_fruit(nerd, store):
    """去水果店买苹果
    
    - 先得看看店是不是在营业
    - 如果有苹果的话,就买 1 个
    - 如果钱不够,就回家取钱再来
    """
    if store.is_open():
        if store.has_stocks("apple"):
            if nerd.can_afford(store.price("apple", amount=1)):
                nerd.buy(store, "apple", amount=1)
                return
            else:
                nerd.go_home_and_get_money()
                return buy_fruit(nerd, store)
        else:
            raise MadAtNoFruit("no apple in store!")
    else:
        raise MadAtNoFruit("store is closed!")

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

Такой код плохо читается и сопровождается. Но мы можем использовать очень простой трюк:"раннее закрытие"для оптимизации этого кода:

def buy_fruit(nerd, store):
    if not store.is_open():
        raise MadAtNoFruit("store is closed!")

    if not store.has_stocks("apple"):
        raise MadAtNoFruit("no apple in store!")

    if nerd.can_afford(store.price("apple", amount=1)):
        nerd.buy(store, "apple", amount=1)
        return
    else:
        nerd.go_home_and_get_money()
        return buy_fruit(nerd, store)

«Досрочное прекращение» означает:использовать внутри функции return или raise** и другие операторы рано заканчивают функцию в ветке. **Например, в новой функции buy_fruit, когда условие ветвления не выполняется, мы напрямую выбрасываем исключение и завершаем эту ветвь кода. Такой код не имеет вложенных ветвей, более прямолинеен и легче читается.

2. Инкапсулируйте эти чрезмерно сложные логические суждения

Если выражение в условном переходе слишком сложное и слишком много не/и/или, читабельность этого кода будет сильно снижена, например, следующий код:

# 如果活动还在开放,并且活动剩余名额大于 10,为所有性别为女性,或者级别大于 3
# 的活跃用户发放 10000 个金币
if activity.is_active and activity.remaining > 10 and \
        user.is_active and (user.sex == 'female' or user.level > 3):
    user.add_coins(10000)
    return

Для такого кода мы можем рассмотреть возможность инкапсуляции определенной логики ветвления в функции или методы для упрощения кода:

if activity.allow_new_user() and user.match_activity_condition():
    user.add_coins(10000)
    return

На самом деле, после переписывания кода предыдущий текст комментария действительно может быть удален. **Поскольку последний код не требует пояснений. **Что касается конкретногоКакие пользователи соответствуют критериям активности?На такой вопрос должен отвечать специальный метод match_activity_condition().

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

3. Следите за дублированием кода в разных ветках

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

Давайте посмотрим на этот пример:

# 对于新用户,创建新的用户资料,否则更新旧资料
if user.no_profile_exists:
    create_user_profile(
        username=user.username,
        email=user.email,
        age=user.age,
        address=user.address,
        # 对于新建用户,将用户的积分置为 0
        points=0,
        created=now(),
    )
else:
    update_user_profile(
        username=user.username,
        email=user.email,
        age=user.age,
        address=user.address,
        updated=now(),
    )

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

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

if user.no_profile_exists:
    profile_func = create_user_profile
    extra_args = {'points': 0, 'created': now()}
else:
    profile_func = update_user_profile
    extra_args = {'updated': now()}

profile_func(
    username=user.username,
    email=user.email,
    age=user.age,
    address=user.address,
    **extra_args
)

Будьте особенно внимательны при написании кода ответвленияДублирование блоков кода в результате ветвлений, если вы можете просто устранить их, то не сомневайтесь.

4. Используйте тернарные выражения экономно

Тернарные выражения — это синтаксис, поддерживаемый, начиная с Python 2.5. До этого сообщество Python когда-то думало, что троичные выражения не нужны, и нам нужно использовать x и a или b для их моделирования.

Правда в том, что во многих случаях код становится более читабельным, если использовать простые операторы if/else. Слепая погоня за тернарными выражениями может легко подтолкнуть вас к написанию сложного, плохо читаемого кода.

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

language = "python" if you.favor("dynamic") else "golang"

В подавляющем большинстве случаев просто используйте обычные операторы if/else.

Общие навыки

1. Используйте «Закон Де Моргана»

При оценке ветвления мы иногда пишем такой код:

# 如果用户没有登录或者用户没有使用 chrome,拒绝提供服务
if not user.has_logged_in or not user.is_from_chrome:
    return "our service is only available for chrome logged in user"

Когда вы впервые видите код, нужно ли какое-то время, чтобы понять, что он пытается сделать? Это потому, что в приведенном выше логическом выражении есть 2 not и 1 or. И мы, люди, просто плохо справляемся со слишком большим количеством «отрицаний» и «или» логических отношений.

В это время следуетЗакон де Морганапоявившийся. С точки зрения непрофессионала, закон Де Моргана состоит в том, что ни А, ни В эквивалентно не (А и В). С помощью этого преобразования приведенный выше код можно переписать следующим образом:

if not (user.has_logged_in and user.is_from_chrome):
    return "our service is only open for chrome logged in user"

Как, код намного легче читать? Помните закон де Моргана, он очень полезен для упрощения логики кода в условных ветвях.

2. "Булево значение true и false" пользовательских объектов.

Мы часто говорим, что в Python «все является объектом». На самом деле, не только "все является объектом", мы также можем использовать множество магических методов* (называемых в документе:user-defined method)*, чтобы настроить различные варианты поведения объекта. Мы можем влиять на выполнение кода многими волшебными способами, которые невозможны в других языках.

Например, все объекты в Python имеют свои собственные «логические значения true и false»:

  • Логический ложный объект: None, 0, False, [], (), {}, set(), frostset(), ... ...
  • Булевы истинные объекты: ненулевые значения, истина, непустые последовательности, кортежи, простые экземпляры пользовательского класса, ...

С помощью встроенной функции bool() вы можете легко проверить логическую истинность объекта. Это значение также используется Python для оценки условного перехода:

>>> bool(object())
True

Вот в чем дело, хотя логическое значение всех экземпляров класса User истинно. Но Python предоставляет способ изменить это поведение:__bool__ для пользовательских классов магический метод (в версиях Python 2.X, nonzero). Когда класс определенboolПосле метода его возвращаемое значение будет рассматриваться как логическое значение экземпляра класса.

Кроме того,boolНе единственный способ повлиять на экземпляр логического значения true или false. Если класс не определенboolметод, Python также попытается вызватьlenметод * (то есть вызов * для любого объекта последовательностиlen функция), и судите, является ли экземпляр истинным или ложным, по тому, равен ли результат 0.

Так какая польза от этой функции? Взгляните на следующий код:

class UserCollection(object):

    def __init__(self, users):
        self._users = users


users = UserCollection([piglei, raymond])

if len(users._users) > 0:
    print("There's some users in collection!")

В приведенном выше коде длина users._users используется для определения наличия контента в UserCollection. На самом деле, добавив в UserCollectionlenМагический метод, ветку выше можно сделать проще:

class UserCollection:

    def __init__(self, users):
        self._users = users

    def __len__(self):
        return len(self._users)


users = UserCollection([piglei, raymond])

# 定义了 __len__ 方法后,UserCollection 对象本身就可以被用于布尔判断了
if users:
    print("There's some users in collection!")

Определив магические методыlenиbool, мы можем позволить классу самому управлять булевыми значениями true и false, которые он хочет выразить, делая код более питоническим.

3. Используйте all() / any() в условном суждении

Две функции all() и any() очень подходят для использования в условных суждениях. Эти две функции принимают итерируемый объект и возвращают логическое значение, где:

  • all(seq): возвращает True, только если все объекты в seq имеют логическое значение true, в противном случае возвращает False.
  • any(seq): возвращает True, если любой объект в seq является логическим значением true, в противном случае возвращает False.

Предположим, у нас есть следующий код:

def all_numbers_gt_10(numbers):
    """仅当序列中所有数字大于 10 时,返回 True
    """
    if not numbers:
        return False

    for n in numbers:
        if n <= 10:
            return False
    return True

Если использовать встроенную функцию all() и простое генераторное выражение, приведенный выше код можно записать следующим образом:

def all_numbers_gt_10_2(numbers):
    return bool(numbers) and all(n > 10 for n in numbers)

Просто и эффективно, без ущерба для удобства использования.

4. Используйте ветку else в try/while/for

Давайте посмотрим на эту функцию:

def do_stuff():
    first_thing_successed = False
    try:
        do_the_first_thing()
        first_thing_successed = True
    except Exception as e:
        print("Error while calling do_some_thing")
        return

    # 仅当 first_thing 成功完成时,做第二件事
    if first_thing_successed:
        return do_the_second_thing()

В функции do_stuff мы хотим перейти ко второму вызову функции только после успешного вызова do_the_first_thing() (то есть без создания каких-либо исключений)*. Для этого нам нужно определить дополнительную переменную first_thing_successed в качестве маркера.

На самом деле, мы можем добиться того же эффекта более простым способом:

def do_stuff():
    try:
        do_the_first_thing()
    except Exception as e:
        print("Error while calling do_some_thing")
        return
    else:
        return do_the_second_thing()

После добавления ветки else в конец блока try функция do_the_second_thing() под веткой будет выполняться только вВсе операторы ниже try выполняются нормально (то есть без исключений, без возврата, прерывания и т. д.) выполняются после завершения.

Точно так же циклы for/while в Python также поддерживают добавление веток else, что означает, что код в ветке else не будет выполняться до тех пор, пока объекты итерации, используемые циклом, не будут исчерпаны, или переменная условия, используемая циклом while, не станет False. .

Распространенные подводные камни

1. Сравнение со значением None

В Python существует два способа сравнения переменных: == и is, которые имеют принципиально разные значения:

  • ==: указывает, на что указывают дваценностьЯвляется ли это последовательным
  • is: Указывает, указывают ли два на одно и то же содержимое в памяти, то есть равен ли id(x) id(y)

None — это объект с одним регистром в языке Python.Если вы хотите определить, является ли переменная None, не забудьте использовать IS вместо ==, потому что только IS может указать, является ли переменная None.

В противном случае могут возникнуть следующие ситуации:

>>> class Foo(object):
...     def __eq__(self, other):
...         return True
...
>>> foo = Foo()
>>> foo == None
True

В приведенном выше коде класс Foo определяется какeqС магическим методом условие == None легко выполняется.

Итак, если вы хотите проверить, является ли переменная None, используйте is вместо ==.

2. Обратите внимание на приоритет операции и и или

Взгляните на два выражения ниже.Угадайте, что их значения одинаковы?

>>> (True or False) and False
>>> True or False and False

Ответ: нет, их значения False и True, вы угадали?

Суть дела в следующем:and оператор имеет приоритет больше, чем or. Таким образом, второе выражение выше на самом деле является True или (False и False) для Python. Таким образом, результат True вместо False.

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

Эпилог

Это вторая статья из серии "Мастер Python". Не знаю, понравится ли вам содержание статьи.

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

После прочтения статьи у вас есть на что пожаловаться? Пожалуйста, оставьте сообщение, чтобы сказать мне.

вопросы и ответы

Проблемы с кодом Python для начинающих

Связанное Чтение

Python | Четыре классные технологии для запуска других программ

Python | Проверка орфографии в 21 строке

Python | Автоматически генерируйте смайлики, теперь Доуту непобедим!

[Ежедневная рекомендация курса] Машинное обучение в действии! Быстрый старт бизнеса в сфере интернет-рекламы и знание CTR

Эта статья была разрешена автором для публикации в сообществе Tencent Cloud + Для получения дополнительных оригинальных текстов, пожалуйстанажмите

Найдите и подпишитесь на общедоступную учетную запись «Сообщество Yunjia», получите технические галантереи как можно скорее и ответьте на 1024 после подписки, чтобы отправить вам подарочный пакет технических курсов!

Огромный технический практический опыт, все вСообщество Юнцзя!