В сети много подобных статей, но я просто подумал, что могу добавить свое видение темы и сделать список «хитростей» более полным. Используемые здесь фрагменты кода каким-то образом имеют решающее значение для моего рабочего процесса, и я использую их снова и снова.
Наборы
Часто разработчики забывают, что в Python установлен тип данных, и пытаются использовать списки для всего. Что установлено? Ну короче:
Набор - это неупорядоченная коллекция без повторяющихся элементов.
Если вы познакомитесь с наборами и их логикой, это может решить за вас множество проблем. Например - как получить все уникальные буквы, используемые в слове?
myword = "NanananaBatman" set(myword) {'N', 'm', 'n', 'B', 'a', 't'}
Бум. Проблема решена, но, честно говоря, это из официальной документации Python, так что ничего удивительного.
Как насчет этого? Получить список элементов без повторения элементов?
# first you can easily change set to list and other way around mylist = ["a", "b", "c", "c"] # let's make a set out of it myset = set(mylist) # myset will be: {'a', 'b', 'c'} # and, it's already iterable so you can do: for element in myset: print(element) # but you can also convert it to list again: mynewlist = list(myset) # and mynewlist will be: ['a', 'b', 'c']
Как видите, повторяющаяся буква «c» больше не актуальна. Вы должны знать только одно: порядок элементов между mylist и mynewlist может быть разным:
mylist = ["c", "c", "a", "b"] mynewlist = list(set(mylist)) # mynewlist is: ['a', 'b', 'c'] # as you can see it's different order;
Но! Мы можем пойти глубже.
Представьте себе случай, когда у вас есть отношение «один ко многим» между некоторыми объектами, чтобы сделать его более конкретным - пользователь и разрешения; обычно бывает так, что один пользователь может иметь несколько разрешений. Теперь представьте, что кто-то хочет изменить несколько разрешений - добавить некоторые и удалить некоторые одновременно, как решить эту проблему?
# this is the set of permissions before change; original_permission_set = {"is_admin", "can_post_entry", "can_edit_entry", "can_view_settings"} # this is new set of permissions; new_permission_set = {"can_edit_settings", "is_member", "can_view_entry", "can_edit_entry"} # now permissions to add will be: new_permission_set.difference(original_permission_set) # which will result: {'can_edit_settings', 'can_view_entry', 'is_member'} # As you can see can_edit_entry is in both sets; so we do not need # to worry about handling it # now permissions to remove will be: original_permission_set.difference(new_permission_set) # which will result: {'is_admin', 'can_view_settings', 'can_post_entry'} # and basically it's also true; we switched admin to member, and add # more permission on settings; and removed the post_entry permission
Короче говоря - не бойтесь наборов, поскольку они могут решить вам много проблем, подробнее о наборах вы можете найти в официальных документах Python.
Календарь веселья
Очень часто, когда вы разрабатываете что-то, что сильно зависит от дат и периодов времени, таких как месяцы или недели, вас интересует некоторая информация, например, какой последний день месяца в данном году. Это кажется простым, но поверьте мне, что правильная обработка даты и времени - чрезвычайно сложная тема, и я хотел бы сказать, что реализация календаря имеет очень плохую подготовку, и это кошмар, являющийся причиной множества крайних случаев.
Итак - как найти последний день месяца?
import calendar calendar.monthrange(2020, 12) # will result: (1, 31) # BUT! you need to be careful here, why? Let's read the documentation: help(calendar.monthrange) # Help on function monthrange in module calendar: # monthrange(year, month) # Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for # year, month. # As you can see the first value returned in tuple is a weekday, # not the number of the first day for a given month; let's try # to get the same for 2021 calendar.monthrange(2021, 12) (2, 31) # So this basically means that the first day of December 2021 is Wed # and the last day of December 2021 is 31 (which is obvious, cause # December always has 31 days) # let's play with February calendar.monthrange(2021, 2) (0, 28) calendar.monthrange(2022, 2) (1, 28) calendar.monthrange(2023, 2) (2, 28) calendar.monthrange(2024, 2) (3, 29) calendar.monthrange(2025, 2) (5, 28) # as you can see it handled nicely the leap year;
Что касается начала месяца, это действительно просто - он всегда начинается с 1-го :)
Как вы можете использовать информацию о том, что месяц начинается в определенный день недели? Вы легко можете определить день недели для любого дня:
calendar.monthrange(2024, 2) (3, 29) # means that February 2024 starts on Thursday # let's define simple helper: weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] # now we can do something like: weekdays[3] # will result in: 'Thursday' # now simple math to tell what day is 15th of February 2020: offset = 3 # it's the first value from monthrange for day in range(1, 29): print(day, weekdays[(day + offset - 1) % 7]) 1 Thursday 2 Friday 3 Saturday 4 Sunday ... 18 Sunday 19 Monday 20 Tuesday 21 Wednesday 22 Thursday 23 Friday 24 Saturday ... 28 Wednesday 29 Thursday # which basically makes sense;
Возможно, это не супер-готовый пример, поскольку будний день можно легко найти с помощью модуля datetime:
from datetime import datetime mydate = datetime(2024, 2, 15) datetime.weekday(mydate) # will result: 3 # or: datetime.strftime(mydate, "%A") 'Thursday'
В любом случае, в модуле календаря определенно есть развлечения, о чем полезно знать:
# checking if year is leap: calendar.isleap(2021) # False calendar.isleap(2024) # True # or checking how many days will be leap days for given year span: calendar.leapdays(2021, 2026) # 1 calendar.leapdays(2020, 2026) # 2 # read the help here, as range is: [y1, y2), meaning that second # year is not included; calendar.leapdays(2020, 2024) # 1
Enumerate имеет второй аргумент
Да, есть второй аргумент. Забавно, но некоторые действительно опытные разработчики не знают; Вы должны проверить примеры:
mylist = ['a', 'b', 'd', 'c', 'g', 'e'] for i, item in enumerate(mylist): print(i, item) # Will give: 0 a 1 b 2 d 3 c 4 g 5 e # but, you can add a start for enumeration: for i, item in enumerate(mylist, 16): print(i, item) # and now you will get: 16 a 17 b 18 d 19 c 20 g 21 e
Это простая начальная точка, с которой должно начинаться перечисление. Это может помочь вам в ситуациях, когда вы работаете над какой-то логикой смещений.
Обработка логики if-else
Часто бывает, что вам нужно обрабатывать много разной логики в зависимости от условия, неопытный разработчик кончает примерно так:
OPEN = 1 IN_PROGRESS = 2 CLOSED = 3 def handle_open_status(): print('Handling open status') def handle_in_progress_status(): print('Handling in progress status') def handle_closed_status(): print('Handling closed status') def handle_status_change(status): if status == OPEN: handle_open_status() elif status == IN_PROGRESS: handle_in_progress_status() elif status == CLOSED: handle_closed_status() handle_status_change(1) # Handling open status handle_status_change(2) # Handling in progress status handle_status_change(3) # Handling closed status
Выглядит неплохо, но я видел кодовые базы с 20 и более условиями, подобными этому.
Как тогда поступать?
from enum import IntEnum class StatusE(IntEnum): OPEN = 1 IN_PROGRESS = 2 CLOSED = 3 def handle_open_status(): print('Handling open status') def handle_in_progress_status(): print('Handling in progress status') def handle_closed_status(): print('Handling closed status') handlers = { StatusE.OPEN.value: handle_open_status, StatusE.IN_PROGRESS.value: handle_in_progress_status, StatusE.CLOSED.value: handle_closed_status } def handle_status_change(status): if status not in handlers: raise Exception(f'No handler found for status: {status}') handler = handlers[status] handler() handle_status_change(StatusE.OPEN.value) # Handling open status handle_status_change(StatusE.IN_PROGRESS.value) # Handling in progress status handle_status_change(StatusE.CLOSED.value) # Handling closed status handle_status_change(4) # Will raise the exception
Это общий шаблон, который можно использовать в python, как правило, он делает код немного чище - особенно там, где ваш основной метод обработки огромен и требует обработки множества условий.
Модуль Enum
Я немного поцарапал тему в предыдущем абзаце, давайте копнем глубже.
Модуль enum
предоставляет утилиты для обработки перечислений, самые интересные из них: Enum, IntEnum
- давайте немного покопаемся:
from enum import Enum, IntEnum, Flag, IntFlag class MyEnum(Enum): FIRST = "first" SECOND = "second" THIRD = "third" class MyIntEnum(IntEnum): ONE = 1 TWO = 2 THREE = 3 # Now we can do things like: MyEnum.FIRST # <MyEnum.FIRST: 'first'> # it has value and name attributes, which are handy: MyEnum.FIRST.value # 'first' MyEnum.FIRST.name # 'FIRST' # additionally we can do things like: MyEnum('first') # <MyEnum.FIRST: 'first'>, get enum by value MyEnum['FIRST'] # <MyEnum.FIRST: 'first'>, get enum by name
С IntEnum
он похож, но есть некоторые отличия:
MyEnum.FIRST == "first" # False # but MyIntEnum.ONE == 1 # True # to make first example to work: MyEnum.FIRST.value == "first" # True
В кодовых базах среднего размера enum
модуль чрезвычайно полезен для управления вашими константами в проекте.
Локализация enum может быть немного сложной, но это выполнимо, позвольте мне быстро показать вам, как я справляюсь с этим в django
:
from enum import Enum from django.utils.translation import gettext_lazy as _ class MyEnum(Enum): FIRST = "first" SECOND = "second" THIRD = "third" @classmethod def choices(cls): return [ (cls.FIRST.value, _('first')), (cls.SECOND.value, _('second')), (cls.THIRD.value, _('third')) ] # And later in eg. model definiton: some_field = models.CharField(max_length=10, choices=MyEnum.choices())
IPython FTW
ipython
означает интерактивный питон, и это командная оболочка для интерактивных вычислений, это как интерпретатор питона, но с включенными батареями или по-другому: на стероидах.
Для использования ipython
вам необходимо установить его:
pip install ipython
А позже вместо обычного использования команды ofpython
для входа в интерпретатор используйте вместо этого ipython
# you should see something like this after you start: Python 3.8.5 (default, Jul 28 2020, 12:59:40) Type 'copyright', 'credits' or 'license' for more information IPython 7.18.1 -- An enhanced Interactive Python. Type '?' for help. In [1]:
Он поддерживает системные команды, например: ls
или cat
клавиша tab
будет показывать подсказки, что делает сеанс интерактивного программирования намного более приятным. Вы также можете использовать стрелки вверх / вниз для поиска последних команд, как правило, вы можете делать много чего - обратитесь к официальной документации ipython. Особенно рекомендую ознакомиться с волшебными функциями, с их помощью можно сохранять, делиться кодом, который вы использовали, измерять время выполнения и многое другое.
На данный момент все. Надеюсь, вы найдете здесь что-нибудь интересное.
Уровень кодирования
Спасибо, что стали частью нашего сообщества! Подпишитесь на наш канал YouTube или присоединитесь к Интервью по программированию Skilled.dev.