Как и во многих языках в python 1 эквивалентно True, а 0 — False, то есть
1 == True.
Казалось бы, и что в этом такого? Однако, это имеет некоторые побочные эффекты, связанные с тем, что одинаковые объекты обязаны иметь одинаковые хеши, соответственно у вас не получится запихать в один словарь ключ 1 и True.
>>> a = {1: "one", 0: "zero", True: "true", False: "false"}
# -> {1: 'true', 0: 'false'}
Так же это разрешает следующие операции:
>>> print(2 * False + True)
# -> 1
В данном примере строки использовались в качестве значений словаря, однако, зачастую хочется их использовать в качестве ключей словаря, меня всегда раздражало, что при создании словаря с помощью фигурных скобок, строки нужно указывать в кавычках, хотелось бы их опустить, это возможно, если создавать словарь через конструктор dict().
>>> {"one": 1, "two": 2, "three": 3} == dict(one=1, two=2, three=3)
# -> True
Кроме того, с помощью фигурных скобок создаются не только словари, но и множества(set).
>>> a = {1, 2, 3}
Для объединения двух множеств мне почему-то хочется воспользоваться оператором +, наверно, из-за способа конкатенации строк. Однако, python не поддерживает данный оператор для множеств. Но разумеется, это не значит, что нам всегда придётся пользоваться функциями, создатели подошли к данному вопросу более системно и добавили в язык поддержку основных операций над множествами (а не только объединения) и «повесили» их на логические операторы.
a = {1, 2, 3}
b = {0, 2, 4}
print(a & b) # -> {2}
print(a | b) # -> {0, 1, 2, 3, 4}
print(a ^ b) # -> {0, 1, 3, 4}
print(a - b) # -> {1, 3}, однако один арифметический
# оператор всё же оставили
Продолжая разговор про словари, начиная с версии 3.7 спецификацией языка гарантируется, что словари сохраняют порядок вставки элементов, OrderedDict больше не нужен.
www.python.org/downloads/release/python-370
mail.python.org/pipermail/python-dev/2017-December/151283.html
d = dict(zero='Cero', one='Uno', two='Dos', three='Tres', four='Cuatro',
five='Cinco', six='Seis', seven='Siete', eight='Ocho', night='Nueve')
for index, (key, value) in enumerate(d.items()):
print(f"{index} is {key} in England and {value} in Spain")
Обратите внимание на строку вывода, она начинается с префикса f — это особый тип строк, введённый в python 3.6.
Всего в языке три вида строк: обычные, обозначаемые кавычками без префиксов, сырые\не обрабатываемые(raw), в которых спец-символы, вроде, \n не обрабатываются и вставляются как текст и собственно f-строки.
Созданы они были для упрощения вывода, python поддерживает огромное количество способов вывода:
print("result" + str(2)) # Простая конкатенация строк, python не осуществляет
# автоматическое приведение всех аргументов к
# строковому типу, это остаётся за программистом
print("result", 2) # print может принимать несколько аргументов через запятую,
# в таком случае они будут выводиться через пробел,
# вам не нужны преобразовывать выводимые объекты в строку,
# в отличие от предыдущего способа
print("result %d" % 2) # %-синтаксис, сделан по аналогии с языком C.
print("result %d %.2f" % (2, 2)) # https://docs.python.org/3.4/library/string.html#formatspec
print("result %(name)s" % {"name": 2}) # также разрешено создавать именованные метки
print("{}".format(2)) # У класса строки есть метод format()
# он позволяет опускать тип выводимой переменной
print("{0} {1} {0}".format(1, 2)) # так же можно указать номер переменной и таким образом
# вывести её два раза
# нумерация начинается с нуля
# если число переданных переменных меньше использованных в выводе, будет сгенерированно исключение
print("{} {}".format(2)) # -> IndexError: tuple index out of range
print("{0} {0}".format(2, 3)) # -> 2 2 Однако если передано слишком много переменных
# код отработает без ошибок
from math import pi # при таком выводе так же поддерживаются строки формата
print("{:.2f}".format(pi)) # -> 3.14
from string import Template # возможен и такой способ вывода
s = Template("result $res") # однако он не получил большого распространения
print(s.substitute(res = [3, 4]))
Теперь добавили ещё и f-строки. В них доступны любые переменные из области видимости, можно вызывать функции, получать элементы по ключу, кроме того, они поддерживают строки формата.
from math import pi
result = 4
name = "user"
print(f"{name:84s} pi= {pi:.2f}, result={result}, {name[2]}")
# -> user pi= 3.14, result=4, e
from datetime import datetime
print(f"{datetime.now():%Y:%m-%d}")
Они быстрее всех остальных способов вывода, так что, если вам доступен python3.6 рекомендуется использовать именно их.
Одна из наикрутейших фишек python — в нём упаковываются и распаковываются не объекты и примитивы, а параметры и коллекции.
def func(*argv, **kwargs)
Однако, есть один архитектурный недостаток в реализации:
- argv — кортеж, его значения нельзя изменять, нельзя добавлять или удалять значения
- kwargs — словарь, изменяемый, поэтому кеширование невозможно
Недостаток, конечно, не большой, но всё же неприятно, что нельзя напрямую передавать kwargs в кеш, основанный на словаре, с другой стороны, если вы добавите в кортеж список, то такой кортеж тоже нельзя будет просто так добавить в словарь.
Множества тоже создаются на основе хеш-таблицы, это значит, что значения должны быть хешируемы, кроме того само множество является изменяемым и не хешируемым типом, есть специальный тип frozenset — не изменяемое множество (не спрашивайте меня, зачем оно нужно).
Обсуждали создание типа frozendict, однако пока его не добавили (хотя как минимум одно применение ему уже есть — в качестве kwargs). За неизменяемый словарь приходится отдуваться namedtuple. А ещё и за записи и простенькие классы.
Кто в студенческие\школьные годы писал циклы для вывода значений массива и бесился из-за запятой в конце, каждый раз решал, забить или переписать, чтобы было красиво, и только на курсе 2-3 узнал о методе join? Или я один такой?
Одна из неприятных особенностей метода join у строк — он работает только с элементами-строками, если в коллекции есть хоть одна нестрока приходится использовать генераторное выражение, что выглядит слишком сложным решением такой простой задачи, однако, есть способ упростить вывод значений списков (без скобок).
a = list(range(5))
print(" ".join(a)) # -> TypeError: sequence item 0: expected str instance, int found
print(" ".join(str(i) for i in a)) # -> 0 1 2 3 4
print(*a) # -> 0 1 2 3 4
Так как строки — тоже коллекции, то их так же можно «джойнить».
print('-'.join("hello")) # -> h-e-l-l-o
Рассмотрим строку из предыдущего примера.
print(" ".join(str(i) for i in a)) # -> 0 1 2 3 4
Генераторное выражение передано в ф-цию join без каких-либо скобок, круглые скобки можно опускать для упрощения чтения кода. Python заботится о выразительности.
print(sum(i**2 for i in range(10))) # -> 285
Кроме того, круглые скобки можно опускать и при создании кортежей:
article = "python", 2018, "LinearLeopard" # объявление кортежа
theme, year, author = "python", 2018, "LinearLeopard"# распаковка кортежа
theme, year, _ = "python", 2018, "LinearLeopard" # слева и справа должно
# находиться одинакове число
# переменных, можно подчеркнуть,
# что вам какая-то не нужно,
# обозначив её через
# подчёркивание
theme, _, _ = "python", 2018, "LinearLeopard" # имена могут повторяться
theme, * author = "python", 2018, "LinearLeopard" # можно объявить жадный
# параметр, который съест
# все неподходящие,
# разумеется, допустим
# только один
# жадный оператор
Звёздочку можно использовать и в объявления функций, таким образом можно создать параметры, которые можно указать только по ключу.
def sortwords(*wordlist, case_sensitive=False):
Можно передавать в ф-цию сколько угодно параметров без боязни, что один из них будет воспринят как значение параметра case_sensitive.
Можно и так.
def func(first, second, *, kwonly):
Внимательнее рассмотрим, чем просто * отличается от *args.
def func(first, second, *, kwonly=True):
print(first, second, kwonly)
def func2(first, second, *args, kwonly=True):
print(first, second, *args, kwonly)
func(1) #-> TypeError: func() missing 1 required positional argument: 'second'
func(1, 2) #-> 1 2 True
func(1, 2, False) #-> TypeError: func() takes 2 positional arguments but 3 were given
# используя * в объявлении вы укажите, что
# ваша функция должна быть вызвана с двумя
# позиционными параметрами
func(1, 2, kwonly=False) #-> 1 2 False
func2(1, 2, False) #-> 1 2 False True
# *args заберёт в себя все позиционные
# параметры, то есть вашу функцию может будет
# вызывать с неограниченным (>2) числом
# параметров
С параметрами по умолчанию связана одна интересная особенность: они вычисляются на этапе компиляции модуля в байт-код, так что лучше не использовать там изменяемые типы. Объявим функцию, которая добавляет элемент в конец списка, если второй аргумент опущен, функция возвращает новый список в котором содержится только этот элемент.
def add_to(elem, collection=[]):
collection.append(elem)
return collection
a = ["a", "c"]
print(add_to("b", a)) # -> ['a', 'c', 'b']
print(add_to("a")) # -> ['a']
print(add_to("b")) # -> ['a', 'b'] Откуда здесь 'a'?
Значения по умолчанию ф-ция хранит в поле __defaults__, можно в любой момент узнать, что там находится.
print(add_to.__defaults__) # -> (['a', 'b'],)
Так как аргумент по умолчанию (пустой список) создался в момент старта программы и не пересоздаётся каждый раз заново, мы получили именно такое поведение.
Исправить подобное поведение можно, если сделать значение по умолчанию неизменяемым типом,
а список создавать в теле функции:
def add_to(elem, collection=None):
collection = collection or []
collection.append(elem)
return collection
Обратите внимание на команду
collection = collection or []
это более короткий (и менее понятный, хотя не для всех) аналог
collection = collection if collection else []
Комментарии (35)
worldmind
01.09.2018 18:19> collection = collection or []
если правильно помню if else вводили потому что такой вариант в некоторых случаях работает не так как ожидается, но деталей не помню, они должны быть тутLinearLeopard Автор
01.09.2018 19:35Согласен, лучше пользоваться длинной формой, я решил указать здесь такую, чтобы показать, что она возможна.
А вот
collection |= [] # TypeError: unsupported operand type(s) for |=: 'NoneType' and 'list'
не работаетkalininmr
02.09.2018 12:34+1и не должно, or и | — разные операторы
LinearLeopard Автор
02.09.2018 13:36Да, я к тому, что в других языках это можно сделать, но в python решили так не делать.
kalininmr
02.09.2018 13:39+2это в каких таких?
логическое или и битовое или — вроде везде разные опрераторы
EgZvor
03.09.2018 15:12Судя по PEP, ввели не вариант, а новый механизм, потому что используя только
or
and
можно было не всё сделать. Кроме того, что этими механизмами можно записывать условия в выражениях, они больше ничем не связаны. Собственно, варианты сor
иand
работают только потому, что эти бинарные операции работают лениво, и возвращают дляor
: первый операнд, если он сводится к True, иначе — второй; дляand
: первый операнд, если он — False, иначе второй.
trapwalker
03.09.2018 19:10Все просто. Если воспользуетесь ленивыми булевыми операциями, вы не сможете отличить пустую строку от пустого списка, нуля или None. В некоторых случаях это вполне допустимо и на мой взгляд читается гораздо проще, чем тернарный оператор ( if else ).
item = ( items and item[0] or can_use_others and other_items and other_items[0] or default )
Представьте себе такое выражение с тернарными операторами.
С другой стороны если развернуть это в блок кода, то будет читабельнее, с третьей стороны в лямбде блок кода не употребишь.
phenik
01.09.2018 18:19Простая проверка на NaN
if x != x:
работает при обработке данных таблиц экселя, в других случаях не проверял.barker
01.09.2018 20:32всё-таки лучше math.isnan, т.к. можно при желании сломать эту проверку для произвольных x
worldmind
03.09.2018 21:22Это очень плохой способ, никогда так не делайте, во всяком случае напрямую, только обернув в метод с нормальным названием
DaneSoul
01.09.2018 21:47Как и во многих языках в python 1 эквивалентно True, а 0 — False, то есть
1 == True.
На самом деле такая формулировка не точна.
Они равны (и поэтому функция хеширования дает одинаковый результат и Ваш пример со словарем корректен), но они не эквивалентны (то есть это разные объекты с разными id).
Проверка на проверка на равенство делается оператором ==
Проверка на эквивалентность делается оператором is
print(1 == True) # True print(1 is True) # False print(id(1)) # 10943008 print(id(True)) # 10262944
LinearLeopard Автор
01.09.2018 22:10Да, извиняюсь, я имел в виду логическое равенство, а не равенство id.
Просто использовать термин «равно», мне показалось тоже не хорошей идеей. Надо было выбрать другой термин.
mayorovp
02.09.2018 21:35+2Это называется «идентичность», а не «эквивалентность». Эквивалентность — это как раз ==.
justhabrauser
01.09.2018 22:02-2«Как и во многих языках в python 1 эквивалентно True»
Ой всё (tm)
Если человек не различает _эквивалентно_ и приведение типов в операции сравнения…
С днем знаний, чоLinearLeopard Автор
02.09.2018 13:54+3Никаких преобразований типов не производится. Класс bool наследуется от класса int
print(int.__subclasses__()) # -> [<class 'bool'>]
id_potassium_chloride
02.09.2018 01:05Значения по умолчанию ф-ция хранит в поле __defaults__, можно в любой момент узнать, что там находится.
Похожий механизм есть и объектов. Сегодня буквально пишу
И с удивлением обнаруживаю, что arr ведёт себя как static поле. От неожиданности в конструкторе прописал self.arr=[].copy. Вроде надёжно, хы :Dclass Example: arr=[] __init__(self,...
Cykooz
02.09.2018 02:59Переменная, которую вы указали на «уровне» класса — это поле класса (класс ведь тоже объект в питоне), а не экземпляра этого класса. Экземпляр класса может иметь свою «версию» этой переменной, которая появится после первого же присвоения (self.arr = []) в любом методе класса (рекомендуется это делать в методе __init__). Если у экземпляра класса нет своей «версии» переменной, то при попытке «прочитать» её, будет использована «версия» из класса.
Поэтому для «полей» класса действует такая же рекомендация, что и для аргументов функций со значением по умолчанию — не надо использовать изменяемые типы.
trapwalker
03.09.2018 14:24self.arr=[].copy
Судя по «трюкам» в этой статье не все поймут вашего сарказма в этом месте.
Поясню. Не стоило в конструкторе делать копирование. Литералы [], {}, () и т.д. идентичны соответствующим выражениям: list(), dict(), tuple().
Тело объявления класса выполняется один раз при первом импорте модуля или при запуске программы, если это __main__. Если вы употребите литерал [] или вызов list() в конструкторе, то список будет инициализироваться при каждом вызове конструктора. Кстати, __init__ — это, строго говоря, не конструктор. В питоне конструктор — это __new__. Это я так… на всякий случай.
Кстати о списках и прочих mutable объектах в классовых переменных. Это бывает полезно, если мы хотим без использования метаклассов реализовать класс, который будет «знать» все свои инстансы. Он просто будет сохранять их в свою коллекцию на классовом уровне.
class MyClass: all_instances = [] def __init__(self): self.all_instances.append(self) instance1 = MyClass() instance2 = MyClass() print(MyClass.all_instances == [instance1, instance2]) # True
mayorovp
03.09.2018 15:14Кстати,
__init__
— это, строго говоря, не конструктор. В питоне конструктор — это__new__
.Неправда ваша. Метод
__init__
полностью аналогичен конструкторам в других популярных языках (кроме Delphi который пошел своим путем).
alexdesyatnik
02.09.2018 13:56Хорошая статья. За что люблю Питон: вроде несложный язык, а полно приятных и не очень мелочей, постоянно узнаешь что-то новое (в данном случае про создание словарей со строковыми ключами через dict). Небольшой вопрос: а почему бы не использовать map в примере про join? И читаемость получше, и печатать меньше. (Хотя с читаемостью это может быть субъективно, я в молодости был укушен Хаскеллем и прочей функциональшиной.)
Mingun
02.09.2018 23:12Справедливости ради, про создание словарей с простыми строковыми ключами через конструктор
dict()
упоминается в официальном учебнике (последний абзац):
Словари
Другим полезным встроенным в Python типом данных является словарь (смотрите раздел Типы сопоставлений — dict). В других языках словари иногда называются «ассоциативной памятью» или «ассоциативными массивами». В отличие от последовательностей, индексируемых диапазоном чисел, словари индексируются ключами, которые могут быть любым неизменяемым типом; строки и числа ключами могут быть всегда. Кортежи могут использоваться в качестве ключей, только если они содержат строки, числа или кортежи; если кортёж прямо или косвенно содержит любой изменяемый объект, он не может использоваться в качестве ключа. Вы не можете использовать в качестве ключей списки, поскольку они могут быть изменены по месту с помощью присваиваний по индексам, срезам или такими методами, как
append()
илиextend()
.
Лучше всего думать о словаре как о наборе пар ключ: значение, с тем требованием, что ключи должны быть уникальными (в пределах одного словаря). Пара фигурных скобок создаёт пустой словарь:
{}
. Размещение в фигурных скобках списка разделённых запятыми пар ключ: значение добавляет начальные пары ключ: значение в словарь; также таким способом словари пишутся при их выводе на печать.
Основными операциями над словарём являются сохранение значения по некоторому ключу и извлечение значения по указанному ключу. Также с помощью инструкции
del
пару ключ: значение можно удалить. Если вы сохраняете значение по ключу, который уже используется, старое связанное с этим ключом значение будет забыто. При попытке извлечения значения по несуществующему ключу возбуждается ошибка.
Выполнение на словаре выражения
list(d)
вернёт список всех используемых в словаре ключей в порядке их вставки (если вы хотите, чтобы он был отсортирован, просто используйте вместо негоsorted(d)
). Чтобы проверить, находится ли в словаре один ключ, используйте ключевое словоin
.
Ниже приведён небольшой пример использования словаря:
>>> tel = {'jack': 4098, 'sape': 4139} >>> tel['guido'] = 4127 >>> tel {'jack': 4098, 'sape': 4139, 'guido': 4127} >>> tel['jack'] 4098 >>> del tel['sape'] >>> tel['irv'] = 4127 >>> tel {'jack': 4098, 'guido': 4127, 'irv': 4127} >>> list(tel) ['jack', 'guido', 'irv'] >>> sorted(tel) ['guido', 'irv', 'jack'] >>> 'guido' in tel True >>> 'jack' not in tel False
Конструктор
dict()
строит словари непосредственно из последовательностей пар ключ-значение:
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)]) {'sape': 4139, 'guido': 4127, 'jack': 4098}
Кроме того, для создания словарей из произвольных выражений для ключей и значений можно использовать словарные включения:
>>> {x: x**2 for x in (2, 4, 6)} {2: 4, 4: 16, 6: 36}
Если ключи являются простыми строками, иногда проще указывать пары с помощью именованных аргументов:
>>> dict(sape=4139, guido=4127, jack=4098) {'sape': 4139, 'guido': 4127, 'jack': 4098}
alexdesyatnik
03.09.2018 08:34В официальном учебнике и про остальные вещи из этой и других подобных статей всё расписано. Но, мне кажется, чтобы прочитать его от корки до корки и запомнить все нюансы, память нужно иметь поистине слоновью. Для менее продвинутых (или, может быть, менее профессиональных) пользователей Питона статьи вида «10 интересных приёмов, про которые вы всегда хотели узнать» весьма полезны для заполнения пробелов или восстановления подзабытого.
Pichenug
02.09.2018 13:56+1Большинство из того, что вы описали есть во многих стартовых учебниках по языку типа учебника Лутца(хотя воды там ой как много).
Это я к тому, что изучая особенности языка только на ходу очень много базовых, но полезных «штук», проходит мимо. Имхо, лучше систематически подходить к такому вопросу и начать с учебника, тогда, например, распаковка кортежей и создание функций с возможностью передачи аргументов только по ключу не будет сюрпризом.LinearLeopard Автор
02.09.2018 14:01+2Я учил python, наверно, языком 8. Просто гуглив синтаксические конструкции, согласен, лучше учить язык по хорошим учебникам или записям лекций, но иногда тебе просто надо сдать курсач через неделю\ты изменил место работы\перекинули на другой проект\решили, написать вспомогательную часть текущего на python.
Собственно статья для тех, у кого нет недели на чтение книги, но есть время на чтение статейки другой.
Но материала ещё много, надеюсь в последующих статьях вы тоже узнаете что-то новое.
unabl4
03.09.2018 01:39Статически вычисляемое дефолтовое значение параметра функции — это как раз то, чем я себе ногу отстрелил.
Я поставил стандартное значение равным пустому массиву, который я в теле функции заполнял значениями и как-то использовал. Причём функция была ещё и в добавок рекурсивная. В общем, это было крайне неприятной находкой.
EgZvor
03.09.2018 16:29С параметрами по умолчанию связана одна интересная особенность: они вычисляются на этапе компиляции модуля в байт-код
Это не так. Определения функций (и классов) "вычисляются" на этапе выполнения. Хотя я не уверен, что имеется в виду под этапом компиляции в байт-код. Насколько я понимаю такого "этапа" нет.
class HelloPrinter(list): def __init__(self): print('Hello') super().__init__() def func(el, collection=HelloPrinter()): collection.append(el) print(collection) print('Plain print') func('a') func('b') def func(el, collection=HelloPrinter()): collection.append(el) print(collection) func('c') func('d')
? ~ python3 test_default_func.py Hello Plain print ['a'] ['a', 'b'] Hello ['c'] ['c', 'd']
vasily-v-ryabov
Хороший обзор, скину коллегам почитать. Из мелочей: вроде frozenset через z, и вот этот код можно было бы детальнее прокомментировать:
def func(first, second, *, kwonly):
LinearLeopard Автор
Спасибо за замечания, поправил текст статьи, надеюсь, сейчас более понятно, для удобства скопирую и сюда.
saatanaperkele
У Дена Бейдера есть книжонка Python Tricks, коллегам лучше сразу ее читать. Она маленькая и в ней как раз собраны всякие такие приколы.
LinearLeopard Автор
Спасибо за наводку, большинство интересных фишек я подсмотрел в выступлениях Raymond Hettinger, курсе python от CSC и официальной документации, разумеется. В принципе там таких приёмов на ещё ни одну статью. Чем и собираюсь заняться в ближайшее время.
vasily-v-ryabov
Уже куплена неделю назад. :) И коллегам нравится. На Хабре как раз месяц назад статья была с отрывком из неё, так и нашёл.