Интересности и полезности python

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

Как и во многих языках в 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 []


Ссылка на следующую часть
Поделиться публикацией

Похожие публикации

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

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

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

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

          +1
          > collection = collection or []

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

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

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

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

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

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

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

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

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

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

                                  0
                                  С этим аккуратней надо быть, потому что даже после удаления всех объектов класса вся коллекция остается в памяти со всеми аттрибутами, созданными для каждого объекта.
                                  Для вашего случая:
                                  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>]
                                  
                                    0
                                    Само собой. Про мусор всегда надо помнить. weakref в помощь.
                                      0
                                      даже после удаления всех объектов класса вся коллекция остается в памяти со всеми аттрибутами, созданными для каждого объекта.

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


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

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

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


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

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

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

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


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

                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                              Самое читаемое