питон
Скрытые жемчужины Python: малоизвестные советы и хитрости — часть 1
Добро пожаловать в первую статью из этой серии. Это будет сборник статей, посвященных раскрытию аспектов Python, которые вы, возможно, пропустили. Python — это универсальный и мощный язык, предлагающий бесчисленное количество инструментов и методов для решения широкого круга задач программирования. Однако с таким широким набором доступных инструментов легко упустить из виду некоторые практичные фрагменты кода, которые могут сделать ваш опыт кодирования более эффективным и приятным. Поэтому я решил посвятить серию именно этой цели. Давай начнем!
Иначе после цикла for
Знаете ли вы, что вы можете использовать оператор else
после цикла for? Оператор else выполняется, если цикл не прерывается и, следовательно, выполняется до завершения. То есть:
for i in range(10): if i == 5: print("Break loop, no else!") break else: print("This won't be called.") for i in range(10): if i == 10: print("This won't be called") break else: print("The loop did not break!")
Выходы:
Break loop, no else! The loop did not break!
Хорошо, но почему? Идея состоит в том, чтобы упростить следующий тип процедуры:
found = None for v in some_list: if some_search_condition(): found = v break if found is None: found = some_default_value
со следующим:
for v in some_list: if some_search_condition(): found = v break else: found = some_default_value
Возможно, для этого есть лучшее название, чем «другое», но это то, что есть.
| оператор в питоне
|
, часто называемый «конвейером», — это оператор в Python, который может кратко определять различные вычисления. Давайте посмотрим на некоторые из них:
Побитовое или
Классическое использование |
заключается в выполнении побитового или, и это происходит во многих языках, таких как Java, C, C++ и JavaScript, и это лишь некоторые из них. Точно так же он также используется для этой цели в Python при выполнении между числами. Например:
a = 4 # in binary 0100 = 0*2^3 + 1*2^2 + 0*2^1 + 0*2^0 = 4 b = 8 # in binary 1000 = 1*2^3 + 0*2^2 + 0*2^1 + 0*2^0 = 8 # bitwise or: # 0100 # 1000 # => 1100 = 12 print(a | b) # => 12
&
аналогичным образом используется для побитового и.
Объединение словарей
Начиная с Python 3.9 оператор |
можно использовать для объединения словарей:
a = { "a": 5, "b": 6 } b = { "c": 7 } print(a | b) # => {'a': 5, 'b': 6, 'c': 7}
Обратите внимание, что если словари имеют общие ключи, значение в последнем словаре в операции будет сохранено:
a = { "a": 5, "b": 6 } b = { "a": 7 } print(a | b) # => {'a': 7, 'b': 6}
Вы также можете связать операцию для объединения нескольких словарей:
a = { "a": 5, "b": 6 } b = { "a": 7 } c = { "a": 8, "c": 10 } print(a | b | c) # => {'a': 8, 'b': 6, 'c': 10}
Союз наборов
set
в Python — это неупорядоченный набор элементов без дубликатов. Он обеспечивает быструю проверку членства и множество полезных операций, таких как объединение наборов. Объединение множеств — это объединенные элементы всех из них (без дубликатов), и здесь можно использовать оператор |
:
a = set([1, 2, 3]) b = set([2, 3, 4]) # union: print(a | b) # or equivalently print(a.union(b))
Его также можно связать:
a = set([1, 2, 3]) b = set([2, 3, 4]) c = set([3, 4, 5]) print(a | b | c) # => {1, 2, 3, 4, 5}
Если вы хотите узнать больше о наборе в Python, я написал подробное руководство по этому поводу здесь.
Поэлементное логическое значение или в NumPy и Pandas
Как в NumPy, так и в Pandas |
имеет важную цель выполнения поэлементного логического или. Я подчеркиваю здесь важность, потому что это обеспечивает значительное ускорение по сравнению, например, с использованием .apply()
в Pandas. Проиллюстрируем это на примере:
from time import time import numpy as np import pandas as pd a = pd.Series(np.arange(1000000)) # using .apply() start_time = time() b = a.apply(lambda x: x < 300000 or x > 700000) print(time() - start_time) # => 0.1980600357055664 start_time = time() b = (a < 300000) | (a > 700000) print(time() - start_time) # => 0.0020973682403564453
При запуске это поэлементно или было в 94 раза быстрее! Точно так же вы можете использовать &
для выполнения поэлементного и. Когда важна скорость, эти операторы незаменимы.
Союз в подсказках типа
При указании подсказки типа для переменной, которой разрешено иметь несколько типов, можно использовать тип Union
:
from typing import Union def some_method( # a can be of type str or int a: Union[str, int] ): ...
Но вы также можете использовать |
и написать:
def some_method( # a can be of type str or int a: str | int ): ...
Ваши собственные операции
Наконец, вы можете сами определить использование |
для своих классов, выполнив перегрузку оператора. Для этого вам просто нужно определить метод внутри класса с именем __or__
. Давайте создадим забавный пример, где методы объединены в цепочку:
class Callback: def __init__(self, func): self.func = func # implementing this method will enable # the | operator for this class: # a | b # where a will be self, and b will be # the first argument in __or__ def __or__(self, other): # chain the methods def combined_func(input): # self(...) can be used since # __call__ is defined below tmp = self(input) return other(tmp) return Callback(combined_func) def __call__(self, input): return self.func(input)
Перегрузив __or__
-оператор, обратные вызовы теперь могут быть объединены в цепочку для создания нового обратного вызова:
method = ( Callback(lambda x: x + 1) | Callback(lambda x: x * 2) | Callback(lambda x: x ** 2) ) print(method(3)) # ((3+1) * 2) ** 2 = 64 # => 64 print(method(4)) # ((4+1) * 2) ** 2 = 100 # => 100
Многоточие
...
или многоточие — это константа в Python, которая используется в различных ситуациях, часто сигнализируя о том же значении, что и в обычном тексте, т. е. «и так далее» или «что-то должно быть здесь". Обратите внимание, что ввод ...
и Ellipsis
эквивалентен:
assert Ellipsis == ...
Давайте посмотрим на некоторые из его применений.
В NumPy
При работе с несколькими измерениями в NumPy многоточие можно использовать для обозначения «остальных измерений», то есть:
import numpy as np tensor = np.random.uniform(size=(10, 10, 10, 10, 10)) print(tensor[0, ...].shape) # corresponds to tensor[0, :, :, :, :] # => (10, 10, 10, 10) print(tensor[0, 0, ...].shape) # corresponds to tensor[0, 0, :, :, :] # => (10, 10, 10) print(tensor[0, 0, ..., 0].shape) # corresponds to tensor[0, 0, :, :, 0] # => (10, 10) print(tensor[0, 0, ..., 0, 0].shape) # corresponds to tensor[0, 0, :, 0, 0] # => (10,)
Чтобы узнать больше о NumPy-трюках, ознакомьтесь с этой статьей.
В качестве заполнителя
Альтернативой использованию pass
для обозначения того, что функция ничего не делает или что она должна быть завершена позже, является использование Ellipsis
:
def my_method(): ...
Вы также можете использовать его для занятий:
class MyClass: ...
Если вы похожи на меня и пишете «…» для контента, который вы собираетесь заполнить позже при написании текста, этот подход должен быть естественным.
Ввод
Многоточие можно использовать внутри подсказок типа в Python в разных ситуациях.
Кортежи
При добавлении подсказки типа для кортежа вы можете определить его фиксированную или переменную длину. В последнем случае на сцену выходит многоточие. Давайте посмотрим на несколько примеров:
from typing import Tuple def some_method( # ex: (1, "hello", 2) a: Tuple[int, str, int], # ex: () b: Tuple[()], # variable length! # ex: (1, 2, 3, 4) or (1, 2) c: Tuple[int, ...], ): ...
Подсказка типа для переменной c
говорит, что кортеж может содержать любое количество целых чисел. Итак, как бы вы определили кортеж переменной длины любоготипа? Есть два варианта:
from typing import Tuple, Any def some_other_method( # option 1 a: Tuple[Any, ...], # option 2 (equivalent to option 1) b: Tuple ): ...
Доступно
Объект типа Callable
— это то, что можно назвать, например, методом. Прежде чем понять роль многоточия здесь, давайте разберемся, как обычно определяется подсказка типа Callable
:
from typing import Callable Callable[[TypeOfFirstArg, TypeOfSecondArg, and so on...], ReturnType]
Например:
from typing import Callable def some_method( # takes zero arguments, returns nothing/None # ex: a() a: Callable[[], None], # takes two arguments, an int and a string, returns an int # ex: b(1, "hello") b: Callable[[int, str], int], ): a() b(1, "hello")
Но что, если Callable
должен иметь возможность принимать любое количество аргументов любого типа? Вот тут-то и появляется многоточие. Просто определите его следующим образом:
def some_other_method( # takes any number of arguments of any type, returns an int # ex: c(1, 2, 3, 4) or c() c: Callable[..., int], ): c() c(1,2,3)
Также обратите внимание, что простое указание:
d: Callable
подразумевает:
d: Callable[..., Any]
то есть метод, который принимает любое количество аргументов и возвращает что-либо.
Кстати, начиная с Python 3.10, многоточие также имеет собственный тип: types.EllipsisType
.
Ваши собственные функции и классы
Как и None
, Ellipsis
является одноэлементным, что означает, что в программе есть только один экземпляр объекта, и любая ссылка на Ellipsis
будет указывать на один и тот же объект в памяти. Таким образом, всякий раз, когда многоточие имеет смысл семантически и/или вам нужен еще один синглтон, кроме None
, его можно использовать в ваших классах или методах.
Pydantic — популярная библиотека для проверки данных, о которой я недавно написал статью, посвященную ее использованию для обработки переменных среды. Там многоточие можно использовать в качестве первого аргумента так называемой Field
-функции, чтобы указать, что атрибут требуется. Например:
class Model(BaseModel): # a list of strings that is required and needs to have at least 5 items a_required_attr: List[str] = Field(..., min_items=5)
Если вам понравилась эта статья:
- 👏 Хлоп, это поможет мне понять, что нравится моим читателям и чего они хотят больше.
- 🙏 Подписывайтесь или подписывайтесь, если хотите читать мои будущие статьи, новые каждую неделю!
- 📚 Если вы ищете больше контента, посмотрите мои списки для чтения в разделах ИИ, Питон или Наука о данных.
Спасибо за чтение и хорошего дня.