Pull to refresh

Comments 44

Хороший обзор, скину коллегам почитать. Из мелочей: вроде frozenset через z, и вот этот код можно было бы детальнее прокомментировать:
def func(first, second, *, kwonly):
Спасибо за замечания, поправил текст статьи, надеюсь, сейчас более понятно, для удобства скопирую и сюда.

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) числом
                   # параметров

У Дена Бейдера есть книжонка Python Tricks, коллегам лучше сразу ее читать. Она маленькая и в ней как раз собраны всякие такие приколы.
Спасибо за наводку, большинство интересных фишек я подсмотрел в выступлениях Raymond Hettinger, курсе python от CSC и официальной документации, разумеется. В принципе там таких приёмов на ещё ни одну статью. Чем и собираюсь заняться в ближайшее время.
Очень советую почитать Python. К вершинам мастерства оно же Fluent Python от Лучано Рамальо. В книге хорошо расписываются подробности «кишок» питона)

Уже куплена неделю назад. :) И коллегам нравится. На Хабре как раз месяц назад статья была с отрывком из неё, так и нашёл.

> collection = collection or []

если правильно помню if else вводили потому что такой вариант в некоторых случаях работает не так как ожидается, но деталей не помню, они должны быть тут
Согласен, лучше пользоваться длинной формой, я решил указать здесь такую, чтобы показать, что она возможна.
А вот
collection |= []
# TypeError: unsupported operand type(s) for |=: 'NoneType' and 'list' 

не работает
и не должно, or и | — разные операторы
Да, я к тому, что в других языках это можно сделать, но в python решили так не делать.
это в каких таких?
логическое или и битовое или — вроде везде разные опрераторы

Судя по PEP, ввели не вариант, а новый механизм, потому что используя только or and можно было не всё сделать. Кроме того, что этими механизмами можно записывать условия в выражениях, они больше ничем не связаны. Собственно, варианты с or и and работают только потому, что эти бинарные операции работают лениво, и возвращают для or: первый операнд, если он сводится к True, иначе — второй; для and: первый операнд, если он — False, иначе второй.

Все просто. Если воспользуетесь ленивыми булевыми операциями, вы не сможете отличить пустую строку от пустого списка, нуля или None. В некоторых случаях это вполне допустимо и на мой взгляд читается гораздо проще, чем тернарный оператор ( if else ).
item = (
    items and item[0]
    or can_use_others and other_items and other_items[0]
    or default
)

Представьте себе такое выражение с тернарными операторами.
С другой стороны если развернуть это в блок кода, то будет читабельнее, с третьей стороны в лямбде блок кода не употребишь.
Простая проверка на NaN
if x != x:
работает при обработке данных таблиц экселя, в других случаях не проверял.
всё-таки лучше math.isnan, т.к. можно при желании сломать эту проверку для произвольных x
Это очень плохой способ, никогда так не делайте, во всяком случае напрямую, только обернув в метод с нормальным названием
Как и во многих языках в 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
Да, извиняюсь, я имел в виду логическое равенство, а не равенство id.
Просто использовать термин «равно», мне показалось тоже не хорошей идеей. Надо было выбрать другой термин.
Это называется «идентичность», а не «эквивалентность». Эквивалентность — это как раз ==.
«Как и во многих языках в python 1 эквивалентно True»
Ой всё (tm)

Если человек не различает _эквивалентно_ и приведение типов в операции сравнения…

С днем знаний, чо
Никаких преобразований типов не производится. Класс bool наследуется от класса int
print(int.__subclasses__()) # -> [<class 'bool'>]

Значения по умолчанию ф-ция хранит в поле __defaults__, можно в любой момент узнать, что там находится.
Похожий механизм есть и объектов. Сегодня буквально пишу
class Example:
    arr=[]
    __init__(self,...
И с удивлением обнаруживаю, что arr ведёт себя как static поле. От неожиданности в конструкторе прописал self.arr=[].copy. Вроде надёжно, хы :D
Переменная, которую вы указали на «уровне» класса — это поле класса (класс ведь тоже объект в питоне), а не экземпляра этого класса. Экземпляр класса может иметь свою «версию» этой переменной, которая появится после первого же присвоения (self.arr = []) в любом методе класса (рекомендуется это делать в методе __init__). Если у экземпляра класса нет своей «версии» переменной, то при попытке «прочитать» её, будет использована «версия» из класса.
Поэтому для «полей» класса действует такая же рекомендация, что и для аргументов функций со значением по умолчанию — не надо использовать изменяемые типы.
self.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

Кстати, __init__ — это, строго говоря, не конструктор. В питоне конструктор — это __new__.

Неправда ваша. Метод __init__ полностью аналогичен конструкторам в других популярных языках (кроме Delphi который пошел своим путем).

С этим аккуратней надо быть, потому что даже после удаления всех объектов класса вся коллекция остается в памяти со всеми аттрибутами, созданными для каждого объекта.
Для вашего случая:
del instance1
del instance2
instance3 = MyClass()
print(instance3.all_instances) # [<__main__.MyClass object at 0xXXXXXXX1>, <__main__.MyClass object at 0xXXXXXXX2>, <__main__.MyClass object at 0xXXXXXXX3>]
Само собой. Про мусор всегда надо помнить. weakref в помощь.
даже после удаления всех объектов класса вся коллекция остается в памяти со всеми аттрибутами, созданными для каждого объекта.

Не удаляйте объекты. :)
А если серьёзно, то и так сложно придумать пример, когда такое может быть полезно (вот для форм или эмуляции физических систем), вряд ли мы захотим удалять такие объекты, а если и захотим, то удалять скорее всего будем с помощью метода класса.
Я бы вообще переменные отдельных экземпляров не хранил в таких случаях:
def init_system():
    Class(1)
    Class(2)
    Class(3)


Но метод потенциально опасный, нужно весьма аккуратно подходить к подобным реализациям.
SEX = 'Female', 'Male'
sex = SEX[True] # Male
sex = SEX[False] # Female
Хорошая статья. За что люблю Питон: вроде несложный язык, а полно приятных и не очень мелочей, постоянно узнаешь что-то новое (в данном случае про создание словарей со строковыми ключами через dict). Небольшой вопрос: а почему бы не использовать map в примере про join? И читаемость получше, и печатать меньше. (Хотя с читаемостью это может быть субъективно, я в молодости был укушен Хаскеллем и прочей функциональшиной.)

Справедливости ради, про создание словарей с простыми строковыми ключами через конструктор dict() упоминается в официальном учебнике (последний абзац):


Словари

5.5 Словари


Другим полезным встроенным в 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}
В официальном учебнике и про остальные вещи из этой и других подобных статей всё расписано. Но, мне кажется, чтобы прочитать его от корки до корки и запомнить все нюансы, память нужно иметь поистине слоновью. Для менее продвинутых (или, может быть, менее профессиональных) пользователей Питона статьи вида «10 интересных приёмов, про которые вы всегда хотели узнать» весьма полезны для заполнения пробелов или восстановления подзабытого.
Большинство из того, что вы описали есть во многих стартовых учебниках по языку типа учебника Лутца(хотя воды там ой как много).
Это я к тому, что изучая особенности языка только на ходу очень много базовых, но полезных «штук», проходит мимо. Имхо, лучше систематически подходить к такому вопросу и начать с учебника, тогда, например, распаковка кортежей и создание функций с возможностью передачи аргументов только по ключу не будет сюрпризом.
Я учил python, наверно, языком 8. Просто гуглив синтаксические конструкции, согласен, лучше учить язык по хорошим учебникам или записям лекций, но иногда тебе просто надо сдать курсач через неделю\ты изменил место работы\перекинули на другой проект\решили, написать вспомогательную часть текущего на python.
Собственно статья для тех, у кого нет недели на чтение книги, но есть время на чтение статейки другой.
Но материала ещё много, надеюсь в последующих статьях вы тоже узнаете что-то новое.
Статически вычисляемое дефолтовое значение параметра функции — это как раз то, чем я себе ногу отстрелил.
Я поставил стандартное значение равным пустому массиву, который я в теле функции заполнял значениями и как-то использовал. Причём функция была ещё и в добавок рекурсивная. В общем, это было крайне неприятной находкой.
С параметрами по умолчанию связана одна интересная особенность: они вычисляются на этапе компиляции модуля в байт-код

Это не так. Определения функций (и классов) "вычисляются" на этапе выполнения. Хотя я не уверен, что имеется в виду под этапом компиляции в байт-код. Насколько я понимаю такого "этапа" нет.


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']
Значения по умолчанию вычисляются один раз при определении функции.
Да. Есть специальный оператор `def` для определения функций. Когда он выполняется, тогда и вычисляются выражения в описании аргументов. То же самое с классами.
Спасибо за статью!
Побежал, поставил 3.7 только по причине f-строк, просто кайф!

Вот этот момент:
# используя * в объявлении вы укажите, что
# ваша функция должна быть вызвана с двумя
# позиционными параметрами

Не совсем clear.
То есть ИМХО: все что до * будут позиционными а все что после * будут ключевыми

def a(foo, *, bar):
   print(f"foo:{foo}, bar:{bar}")
a(100, bar=200)


Основная суть такая: если после звёздочки идёт имя — то все неименованные позиционные аргументы будут упакованы в кортеж с таким именем, если вы ничего дополнительно не указали, то кортеж будет пустой, а просто звёздочка запрещает передачу дополнительные параметров.
В вашем примере вы должны будете вызывать ф-цию именно так, вы её и вызвали, то есть передав первый аргумент по ключу или без него и второй только без ключа, если передать только один параметр или передать три, то программа упадёт.
print(" ".join(str(i) for i in a))

Можно же использовать map
''.join(map(str, [1, 2, 3, 4, 5]))
'12345'

А если надо, то можно использовать лямбда функцию
' '.join(map(lambda x: str(x**2), [1, 2, 3, 4, 5]))
'1 4 9 16 25'
Sign up to leave a comment.

Articles