Рубрики
Без рубрики

Некоторые хитрые фрагменты Python, которые могут вас откусить!

Коллекция тонких и хитрых примеров Python

Автор оригинала: Satwik Kansal.

В течение последних двух недель я работал над этим забавным проектом, ползая по Интернету, собирая некоторые тонкие и хитрые фрагменты Python, которые могут вас откусить! Я подумал о том, чтобы поделиться этим здесь, потому что сообщество Codementor дало мне ценную обратную связь по всем моим предыдущим постам до сих пор.

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

  • Внимательно прочитайте исходный код для настройки примера. Если вы опытный программист на Python, в большинстве случаев вы будете успешно предвидеть, что произойдет дальше.
  • Прочтите выходные фрагменты и
    • Проверьте, совпадают ли выходные данные с вашими ожиданиями.
    • Убедитесь, что вы точно знаете причину того, что результат такой, какой он есть.
      • Если нет, прочитайте объяснение (и если вы все еще не понимаете, кричите! и создать проблему здесь ).
      • Если да, мягко похлопайте себя по спине, и вы можете перейти к следующему примеру.

PS: Вы также можете прочитать эти примеры в командной строке. Сначала установите пакет npm wtf python ,

$ npm install -g wtfpython

Теперь просто запустите с python в командной строке, которая откроет эту коллекцию в выбранном вами $ПЕЙДЖЕРЕ .

Итак, не тратя много времени, давайте рассмотрим примеры…

Пропуск строк?

Выход:

>>> value = 11
>>> valuе = 32
>>> value
11

Вут?

Примечание: Самый простой способ воспроизвести это-просто скопировать операторы из приведенного выше фрагмента кода и вставить их в файл/оболочку.

💡 Объяснение

Некоторые символы Юникода выглядят идентичными символам ASCII, но интерпретатор считает их различными.

>>> value = 42 #ascii e
>>> valuе = 23 #cyrillic e, Python 2.x interpreter would raise a `SyntaxError` here
>>> value
42

Ну, что-то тут не так…

def square(x):
    """
    A simple function to calculate square of a number by addition.
    """
    sum_so_far = 0
    for counter in range(x):
        sum_so_far = sum_so_far + x
  return sum_so_far

Вывод (Python 2.x):

>>> square(10)
10

Разве это не должно быть 100?

Примечание: Если вы не можете воспроизвести это, попробуйте запустить файл mixed_tabs_and_spaces.py через оболочку.

💡 Объяснение

  • Не смешивайте табуляции и пробелы! Символ, непосредственно предшествующий возвращению, является “табуляцией”, и код имеет отступ, кратный “4 пробелам” в другом месте примера.

  • Вот как Python обрабатывает вкладки:

    Во-первых, табуляции заменяются (слева направо) от одного до восьми пробелов таким образом, что общее количество символов до замены и включительно кратно восьми <…>

  • Таким образом, “табуляция” в последней строке функции square заменяется восемью пробелами, и она попадает в цикл.

  • Python 3 достаточно хорош, чтобы автоматически выдавать ошибку для таких случаев.

    Вывод (Python 3.x):

Время для пирожных с гашишем!

some_dict = {}
some_dict[5.5] = "Ruby"
some_dict[5.0] = "JavaScript"
some_dict[5] = "Python"

Выход:

>>> some_dict[5.5]
"Ruby"
>>> some_dict[5.0]
"Python"
>>> some_dict[5]
"Python"

“Python” уничтожил существование “JavaScript”?

💡 Объяснение

  • 5 (тип int ) неявно преобразуется в 5.0 (a float type) перед вычислением хэша в Python.

  • Этот StackOverflow answer прекрасно объясняет стоящее за ним обоснование.

Несоответствие времени оценки

array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]

Выход:

>>> print(list(g))
[8]

💡 Объяснение

  • В выражении generator предложение in вычисляется во время объявления, но условное предложение вычисляется во время выполнения.
  • Таким образом, перед началом выполнения array повторно присваивается списку [2, 8, 22] , и так как из 1 , 8 и 15 , только граф 8 больше, чем 0 , генератор только дает 8 .

Изменение словаря во время итерации по нему

x = {0: None}

for i in x:
    del x[i]
    x[i+1] = None
    print(i)

Выход:

0
1
2
3
4
5
6
7

Да, он работает ровно восемь раз и останавливается.

💡 Объяснение:

  • Итерация по словарю, который вы редактируете одновременно, не поддерживается.
  • Он запускается восемь раз, потому что именно в этот момент словарь изменяет размер, чтобы содержать больше ключей (у нас есть восемь записей удаления, поэтому требуется изменение размера). На самом деле это деталь реализации.
  • Обратитесь к этому StackOverflow thread , объясняющему аналогичный пример.

Удаление элемента списка во время итерации по нему

list_1 = [1, 2, 3, 4]
list_2 = [1, 2, 3, 4]
list_3 = [1, 2, 3, 4]
list_4 = [1, 2, 3, 4]

for idx, item in enumerate(list_1):
    del item

for idx, item in enumerate(list_2):
    list_2.remove(item)

for idx, item in enumerate(list_3[:]):
    list_3.remove(item)

for idx, item in enumerate(list_4):
    list_4.pop(idx)

Выход:

>>> list_1
[1, 2, 3, 4]
>>> list_2
[2, 4]
>>> list_3
[]
>>> list_4
[2, 4]

Можете ли вы догадаться, почему выход [2, 4] ?

💡 Объяснение:

  • Никогда не стоит менять объект, который вы повторяете. Правильный способ сделать это-перебрать копию объекта, и list_3[:] делает именно это.

Разница между дель , удалить , и хлопок :

  • remove удаляет первое совпадающее значение, а не конкретный индекс, вызывает ValueError если значение не найдено.
  • del удаляет определенный индекс (именно поэтому first list_1 не был затронут), вызывает IndexError если указан недопустимый индекс.
  • pop удаляет элемент по определенному индексу и возвращает его, вызывает IndexError если указан недопустимый индекс.

Почему выход есть [2, 4] ?

  • Итерация списка выполняется индекс за индексом, и когда мы удаляем 1 из list_2 или list_4 теперь содержимое списков [2, 3, 4] . Остальные элементы смещены вниз, т. е. 2 находится в индексе 0, а 3 находится в индексе 1. Так как следующая итерация будет смотреть на индекс 1 (который является 3 ), то 2 полностью пропускается. То же самое произойдет с каждым альтернативным элементом в последовательности списков.
  • См. Этот хороший StackOverflow thread для аналогичного примера, связанного со словарями в Python.

Обратная косая черта в конце строки

Выход:

>>> print("\\ some string \\")
>>> print(r"\ some string")
>>> print(r"\ some string \")

    File "", line 1
      print(r"\ some string \")
                             ^
SyntaxError: EOL while scanning string literal

💡 Объяснение

  • В необработанном строковом литерале, как указано префиксом r , обратная косая черта не имеет особого значения.
  • Однако на самом деле интерпретатор просто изменяет поведение обратных косых черт, чтобы они передавали себя и следующий символ. Вот почему обратные косые черты не работают в конце необработанной строки.

Давайте сделаем гигантскую веревку!

Это вовсе не WTF, просто некоторые приятные вещи, о которых нужно знать

def add_string_with_plus(iters):
    s = ""
    for i in range(iters):
        s += "xyz"
    assert len(s) == 3*iters

def add_string_with_format(iters):
    fs = "{}"*iters
    s = fs.format(*(["xyz"]*iters))
    assert len(s) == 3*iters

def add_string_with_join(iters):
    l = []
    for i in range(iters):
        l.append("xyz")
    s = "".join(l)
    assert len(s) == 3*iters

def convert_list_to_string(l, iters):
    s = "".join(l)
    assert len(s) == 3*iters

Выход:

>>> timeit(add_string_with_plus(10000))
100 loops, best of 3: 9.73 ms per loop
>>> timeit(add_string_with_format(10000))
100 loops, best of 3: 5.47 ms per loop
>>> timeit(add_string_with_join(10000))
100 loops, best of 3: 10.1 ms per loop
>>> l = ["xyz"]*10000
>>> timeit(convert_list_to_string(l, 10000))
10000 loops, best of 3: 75.3 µs per loop

💡 Объяснение

  • Вы можете прочитать больше о time it отсюда. Обычно он используется для измерения времени выполнения фрагментов.
  • Не используйте + для генерации длинных строк — В Python str является неизменяемым, поэтому левая и правая строки должны быть скопированы в новую строку для каждой пары конкатенаций. Если вы объедините четыре строки длиной 10, вы будете копировать (10+10) + ((10+10)+10) + символы вместо всего лишь 40 символов. Все становится квадратично хуже по мере увеличения числа и размера строки.
  • Поэтому рекомендуется использовать формат . синтаксис или % (однако они немного медленнее, чем + для коротких строк).
  • Или лучше, если у вас уже есть содержимое, доступное в виде итерируемого объекта, то используйте ".join(iterable_object) , что намного быстрее.

Оптимизация интерпретатора конкатенации строк.

>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Notice that both the ids are same.
140420665652016
# using "+", three strings:
>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.25748300552368164
# using "+=", three strings:
>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.012188911437988281

💡 Объяснение:

  • += быстрее, чем + для объединения более двух строк, поскольку первая строка (например, s1 для s1 + s3 ) не уничтожается при вычислении полной строки.
  • Обе строки ссылаются на один и тот же объект из-за оптимизации CPython, которая в некоторых случаях пытается использовать существующие неизменяемые объекты (специфичные для реализации), а не создавать каждый раз новый объект. Подробнее об этом можно прочитать здесь

Да, он существует!

Предложение else для циклов. Одним из типичных примеров может быть:

  def does_exists_num(l, to_find):
      for num in l:
          if num == to_find:
              print("Exists!")
              break
      else:
          print("Does not exist")

Выход:

>>> some_list = [1, 2, 3, 4, 5]
>>> does_exists_num(some_list, 4)
Exists!
>>> does_exists_num(some_list, -1)
Does not exist

Предложение else в обработке исключений. Пример,

try:
    pass
except:
    print("Exception occurred!!!")
else:
    print("Try block executed successfully...")

Выход:

Try block executed successfully...

💡 Объяснение:

  • Предложение else после цикла выполняется только тогда, когда нет явного break после всех итераций.
  • предложение else после блока try также называется “предложением завершения”, поскольку достижение предложения else в операторе try означает, что блок try фактически успешно завершен.

это не то, что есть!

Ниже приведен очень известный пример, присутствующий во всем Интернете.

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

>>> a = 257; b = 257
>>> a is b
True

💡 Объяснение:

Разница между является и ==

  • оператор is проверяет, ссылаются ли оба операнда на один и тот же объект (т. е. Проверяет, совпадает ли идентичность операндов или нет).
  • оператор == сравнивает значения обоих операндов и проверяет, совпадают ли они.
  • Таким образом, is – это равенство ссылок, а = = – равенство значений. Пример, чтобы прояснить ситуацию,

256 является существующим объектом, но 257 не

Когда вы запускаете python числа из -5 чтобы 256 будет выделено. Эти цифры используются очень часто, поэтому имеет смысл просто иметь их наготове.

Цитирую из https://docs.python.org/3/c-api/long.html

Текущая реализация сохраняет массив целочисленных объектов для всех целых чисел от -5 до 256, и когда вы создаете int в этом диапазоне, вы просто получаете ссылку на существующий объект. Таким образом, должна быть возможность изменить значение 1. Я подозреваю, что поведение Python в данном случае не определено.

>>> id(256)
10922528
>>> a = 256
>>> b = 256
>>> id(a)
10922528
>>> id(b)
10922528
>>> id(257)
140084850247312
>>> x = 257
>>> y = 257
>>> id(x)
140084850247440
>>> id(y)
140084850247344

Здесь интерпретатор недостаточно умен при выполнении y , чтобы распознать, что мы уже создали целое число значения 257 и так он продолжает создавать другой объект в памяти.

Оба a и b обратитесь к одному и тому же объекту, если он инициализирован одним и тем же значением в одной и той же строке.

>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488
  • Когда a и b установлены в 257 в той же строке интерпретатор Python создает новый объект, а затем одновременно ссылается на вторую переменную. Если вы делаете это на отдельных линиях, он не “знает”, что уже есть 257 как объект.
  • Это оптимизация компилятора, и она особенно применима к интерактивной среде. Когда вы вводите две строки в живом интерпретаторе, они компилируются отдельно, поэтому оптимизируются отдельно. Если бы вы попробовали этот пример в файле .py , вы бы не увидели такого же поведения, потому что файл компилируется все сразу.

не является… отличается от is (не …)

>>> 'something' is not None
True
>>> 'something' is (not None)
False

💡 Объяснение

  • is not является единственным двоичным оператором и имеет поведение, отличное от использования is и not separated.
  • is not вычисляется как False , если переменные по обе стороны от оператора указывают на один и тот же объект, и True в противном случае.

Функция внутри цикла прилипает к одному и тому же выходу

funcs = []
results = []
for x in range(7):
    def some_func():
        return x
    funcs.append(some_func)
    results.append(some_func())

funcs_results = [func() for func in funcs]

Выход:

>>> results
[0, 1, 2, 3, 4, 5, 6]
>>> funcs_results
[6, 6, 6, 6, 6, 6, 6]

Даже если значения x были разными на каждой итерации до добавления some_func к func , все функции возвращают 6.

//ИЛИ

>>> powers_of_x = [lambda x: x**i for i in range(10)]
>>> [f(2) for f in powers_of_x]
[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

💡 Объяснение

  • При определении функции внутри цикла, которая использует переменную цикла в своем теле, замыкание функции цикла привязывается к переменной, а не к ее значению. Таким образом, все функции используют для вычисления последнее значение, присвоенное переменной.

  • Чтобы получить желаемое поведение, вы можете передать переменную цикла в качестве именованной переменной функции. Почему это работает? Потому что это снова определит переменную в области действия функции.

    Выход:

Переменные цикла, вытекающие из локальной области видимости!

1.

for x in range(7):
    if x == 6:
        print(x, ': for x inside loop')
print(x, ': x in global')

Выход:

6 : for x inside loop
6 : x in global

Но x никогда не определялся вне области действия цикла for…

2.

# This time let's initialize x first
x = -1
for x in range(7):
    if x == 6:
        print(x, ': for x inside loop')
print(x, ': x in global')

Выход:

6 : for x inside loop
6 : x in global

3.

x = 1
print([x for x in range(5)])
print(x, ': x in global')

Вывод (на Python 2.x):

[0, 1, 2, 3, 4]
(4, ': x in global')

Вывод (на Python 3.x):

[0, 1, 2, 3, 4]
1 : x in global

💡 Объяснение:

  • В Python for-loops используют область, в которой они существуют, и оставляют свою определенную переменную цикла позади. Это также применимо, если мы явно определили переменную for-loop в глобальном пространстве имен ранее. В этом случае он повторно свяжет существующую переменную.

  • Различия в выводе интерпретаторов Python 2.x и Python 3.x для примера понимания списка можно объяснить следующим изменением, задокументированным в What’s New In Python 3.0 documentation:

    “Понимание списка больше не поддерживает синтаксическую форму [... for var in item1, item2, ...] . Вместо этого используйте [... для var in (item1, item2, ...)] . Кроме того, обратите внимание, что понимание списка имеет другую семантику: они ближе к синтаксическому сахару для выражения генератора внутри конструктора list () , и, в частности, управляющие переменные цикла больше не просачиваются в окружающую область.”

Крестики-нолики,где X выигрывает с первой попытки!

# Let's initialize a row
row = [""]*3 #row i['', '', '']
# Let's make a board
board = [row]*3

Выход:

>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

Мы не назначили 3 “X”s или нет?

💡 Объяснение:

Когда мы инициализируем переменную row , эта визуализация объясняет, что происходит в памяти

изображение

И когда board инициализируется умножением row , это то, что происходит внутри памяти (каждый из элементов board[0] , board[1] и board[2] является ссылкой на тот же список, на который ссылается row )

изображение

Остерегайтесь изменяемых аргументов по умолчанию!

def some_func(default_arg=[]):
    default_arg.append("some_string")
    return default_arg

Выход:

>>> some_func()
['some_string']
>>> some_func()
['some_string', 'some_string']
>>> some_func([])
['some_string']
>>> some_func()
['some_string', 'some_string', 'some_string']

💡 Объяснение:

  • Изменяемые по умолчанию аргументы функций в Python на самом деле не инициализируются каждый раз, когда вы вызываете функцию. Вместо этого в качестве значения по умолчанию используется недавно присвоенное им значение. Когда мы явно передали [] в some_fancy в качестве аргумента, значение по умолчанию переменной default_arg не использовалось, поэтому функция возвращалась как ожидалось.

    Выход:

  • Обычная практика, чтобы избежать ошибок из-за изменяемых аргументов, состоит в том, чтобы назначить None в качестве значения по умолчанию, а затем проверить, передается ли какое-либо значение функции, соответствующей этому аргументу. Пример:

Те же операнды, другая история!

1.

a = [1, 2, 3, 4]
b = a
a = a + [5, 6, 7, 8]

Выход:

>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4]

2.

a = [1, 2, 3, 4]
b = a
a += [5, 6, 7, 8]

Выход:

>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4, 5, 6, 7, 8]

💡 Объяснение:

  • a ведет себя не так, как + b

  • Выражение a + [5,6,7,8] генерирует новый объект и устанавливает ссылку a на этот новый объект, оставляя b неизменным.

  • Выражение a + =[5,6,7,8] фактически сопоставляется с функцией “extend”, которая работает с объектом таким образом, что a и b все еще указывают на тот же объект, который был изменен на месте.

Мутировать неизменное!

some_tuple = ("A", "tuple", "with", "values")
another_tuple = ([1, 2], [3, 4], [5, 6])

Выход:

>>> some_tuple[2] = "change this"
TypeError: 'tuple' object does not support item assignment
>>> another_tuple[2].append(1000) #This throws no error
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000])
>>> another_tuple[2] += [99, 999]
TypeError: 'tuple' object does not support item assignment
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000, 99, 999])

Но я думал, что кортежи неизменны…

💡 Объяснение:

  • Цитирую из https://docs.python.org/2/reference/datamodel.html

    Неизменяемые последовательности Объект типа неизменяемой последовательности не может измениться после его создания. (Если объект содержит ссылки на другие объекты, эти другие объекты могут быть изменяемыми и могут быть изменены; однако коллекция объектов, на которые непосредственно ссылается неизменяемый объект, не может измениться.)

  • += оператор изменяет список на месте. Назначение элемента не работает, но когда возникает исключение, элемент уже был изменен на месте.

Использование переменной, не определенной в области видимости

a = 1
def some_func():
    return a

def another_func():
    a += 1
    return a

Выход:

>>> some_func()
1
>>> another_func()
UnboundLocalError: local variable 'a' referenced before assignment

💡 Объяснение:

  • Когда вы делаете присвоение переменной в области, она становится локальной для этой области. Таким образом, a становится локальным для области another_func , но он не был инициализирован ранее в той же области, которая выдает ошибку.

  • Прочтите это короткое, но потрясающее руководство, чтобы узнать больше о том, как пространства имен и разрешение областей работают в Python.

  • Чтобы изменить переменную внешней области видимости a in another_func , используйте ключевое слово global .

    Выход:

Исчезающая переменная из внешней области видимости

e = 7
try:
    raise Exception()
except Exception as e:
    pass

Вывод (Python 2.x):

>>> print(e)
# prints nothing

Вывод (Python 3.x):

>>> print(e)
NameError: name 'e' is not defined

💡 Объяснение:

  • Источник: https://docs.python.org/3/reference/compound_stmts.html#except

    Если исключение было назначено с использованием as target, оно очищается в конце предложения except. Это как если бы

    был переведен на

    Это означает, что исключение должно быть присвоено другому имени, чтобы иметь возможность ссылаться на него после предложения except. Исключения очищаются, потому что с присоединенной к ним обратной трассировкой они образуют ссылочный цикл с кадром стека, сохраняя все локальные объекты в этом кадре живыми до следующей сборки мусора.

  • Эти предложения не ограничены областью действия в Python. Все в этом примере присутствует в одной и той же области видимости, а переменная e была удалена из-за выполнения предложения except . То же самое не относится к функциям, которые имеют свои отдельные внутренние области. Пример ниже иллюстрирует это:

    Выход:

  • В Python 2.x имя переменной e присваивается экземпляру Exception () , поэтому при попытке печати он ничего не печатает.

    Вывод (Python 2.x):

Возвращайся, возвращайся везде!

def some_func():
    try:
        return 'from_try'
    finally:
        return 'from_finally'

Выход:

>>> some_func()
'from_finally'

💡 Объяснение:

  • Когда оператор return , break или continue выполняется в наборе try оператора “try…finally”, предложение finally также выполняется “на выходе”.
  • Возвращаемое значение функции определяется последним выполненным оператором return . Поскольку предложение finally всегда выполняется, оператор return , выполняемый в предложении finally , всегда будет выполняться последним.

Когда Истина на самом деле ложь

True = False
if True == False:
    print("I've lost faith in truth!")

Выход:

I've lost faith in truth!

💡 Объяснение:

  • Изначально Python не имел типа bool (люди использовали 0 для false и ненулевое значение, например 1 для true). Затем они добавили True , False и тип bool , но для обратной совместимости они не могли сделать True и False константами – они просто были встроенными переменными.
  • Python 3 был обратно несовместим, так что теперь, наконец, можно было это исправить, и поэтому этот пример не будет работать с Python 3.x!

Будьте осторожны с цепными операциями

>>> True is False == False
False
>>> False is False is False
True
>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False

💡 Объяснение:

Согласно https://docs.python.org/2/reference/expressions.html#not-in

Формально, если a, b, c, …, y, z-выражения, а op1, op2, …, opN-операторы сравнения, то a op1 b op2 c … y opN z эквивалентно a op1 b и b op2 c и … y opN z, за исключением того, что каждое выражение вычисляется не более одного раза.

Хотя такое поведение может показаться вам глупым в приведенных выше примерах, оно фантастично с такими вещами, как a и 0 .

  • False is False is False эквивалентно (False is False) и (False is False)
  • True - это False эквивалентно True - это False и False , и поскольку первая часть оператора ( True-это False ) вычисляется как False , общее выражение вычисляется как False .
  • 1 > 0 < 1 эквивалентно 1 > 0 и 0 < 1 , что равно Правда .
  • Выражение (1 > 0) < 1 эквивалентно True < 1 и

    Итак, 1 < 1 вычисляется как False

Разрешение имен игнорирование области действия класса

1.

x = 5
class SomeClass:
    x = 17
    y = (x for i in range(10))

Выход:

>>> list(SomeClass.y)[0]
5

2.

x = 5
class SomeClass:
    x = 17
    y = [x for i in range(10)]

Вывод (Python 2.x):

>>> SomeClass.y[0]
17

Вывод (Python 3.x):

>>> SomeClass.y[0]
5

💡 Объяснение

  • Области, вложенные в определение класса, игнорируют имена, связанные на уровне класса.
  • Выражение генератора имеет свою собственную область действия.
  • Начиная с Python 3.X, понимание списков также имеет свою собственную область применения.

От заполненного до Ни одного в одной инструкции…

some_list = [1, 2, 3]
some_dict = {
  "key_1": 1,
  "key_2": 2,
  "key_3": 3
}

some_list = some_list.append(4)
some_dict = some_dict.update({"key_4": 4})

Выход:

>>> print(some_list)
None
>>> print(some_dict)
None

💡 Объяснение

Большинство методов, изменяющих элементы объектов последовательности/сопоставления, таких как list.append , dict.update , list.sort и т. Д., изменяют объекты на месте и возвращают None . Обоснование этого заключается в том, чтобы повысить производительность, избегая создания копии объекта, если операция может быть выполнена на месте (см. )

Явная типизация строк

Это совсем не WTF, но мне потребовалось так много времени, чтобы понять, что такие вещи существуют в Python. Так что делюсь им здесь для начинающих.

a = float('inf')
b = float('nan')
c = float('-iNf')  #These strings are case-insensitive
d = float('nan')

Выход:

>>> a
inf
>>> b
nan
>>> c
-inf
>>> float('some_other_string')
ValueError: could not convert string to float: some_other_string
>>> a == -c #inf==inf
True
>>> None == None # None==None
True
>>> b == d #but nan!=nan
False
>>> 50/a
0.0
>>> a/a
nan
>>> 23 + b
nan

💡 Объяснение:

'inf' и 'nan' -это специальные строки (без учета регистра), которые при явном приведении типа к типу float используются для представления математических “бесконечности” и “не числа” соответственно.

Атрибуты класса и атрибуты экземпляра

1.

class A:
    x = 1

class B(A):
    pass

class C(A):
    pass

Выход:

>>> A.x, B.x, C.x
(1, 1, 1)
>>> B.x = 2
>>> A.x, B.x, C.x
(1, 2, 1)
>>> A.x = 3
>>> A.x, B.x, C.x
(3, 2, 3)
>>> a = A()
>>> a.x, A.x
(3, 3)
>>> a.x += 1
>>> a.x, A.x
(4, 3)

2.

class SomeClass:
    some_var = 15
    some_list = [5]
    another_list = [5]
    def __init__(self, x):
        self.some_var = x + 1
        self.some_list = self.some_list + [x]
        self.another_list += [x]

Выход:

>>> some_obj = SomeClass(420)
>>> some_obj.some_list
[5, 420]
>>> some_obj.another_list
[5, 420]
>>> another_obj = SomeClass(111)
>>> another_obj.some_list
[5, 111]
>>> another_obj.another_list
[5, 420, 111]
>>> another_obj.another_list is SomeClass.another_list
True
>>> another_obj.another_list is some_obj.another_list
True

💡 Объяснение:

  • Переменные класса и переменные в экземплярах класса внутренне обрабатываются как словари объекта класса. Если имя переменной не найдено в словаре текущего класса, то выполняется поиск по нему в родительских классах.
  • Оператор += изменяет изменяемый объект на месте без создания нового объекта. Таким образом, изменение атрибута одного экземпляра влияет на другие экземпляры, а также на атрибут класса.

Ловить исключения!

some_list = [1, 2, 3]
try:
    # This should raise an ``IndexError``
    print(some_list[4])
except IndexError, ValueError:
    print("Caught!")

try:
    # This should raise a ``ValueError``
    some_list.remove(4)
except IndexError, ValueError:
    print("Caught again!")

Вывод (Python 2.x):

Caught!

ValueError: list.remove(x): x not in list

Вывод (Python 3.x):

  File "", line 3
    except IndexError, ValueError:
                     ^
SyntaxError: invalid syntax

💡 Объяснение

  • Чтобы добавить несколько исключений в предложение except, вам нужно передать их в виде заключенного в скобки кортежа в качестве первого аргумента. Второй аргумент – это необязательное имя, которое при указании связывает экземпляр исключения, который был вызван. Пример,

    Вывод (Python 2.x):

    Вывод (Python 3.x):

  • Отделение исключения от переменной запятой устарело и не работает в Python 3; правильный способ-использовать как . Пример,

    Выход:

Полуночного времени не существует?

from datetime import datetime

midnight = datetime(2018, 1, 1, 0, 0)
midnight_time = midnight.time()

noon = datetime(2018, 1, 1, 12, 0)
noon_time = noon.time()

if midnight_time:
    print("Time at midnight is", midnight_time)

if noon_time:
    print("Time at noon is", noon_time)

Выход:

('Time at noon is', datetime.time(12, 0))

Полуночное время не печатается.

💡 Объяснение:

До Python 3.5 логическое значение для объекта datetime.time считалось False , если оно представляло полночь в UTC. Он подвержен ошибкам при использовании синтаксиса if obj: для проверки того, является ли obj нулевым или каким-то эквивалентом “пустого”.”

Подсчет булевых чисел

# A simple example to count the number of boolean and
# integers in an iterable of mixed data types.
mixed_list = [False, 1.0, "some_string", 3, True, [], False]
integers_found_so_far = 0
booleans_found_so_far = 0

for item in mixed_list:
    if isinstance(item, int):
        integers_found_so_far += 1
    elif isinstance(item, bool):
        booleans_found_so_far += 1

Выход:

>>> booleans_found_so_far
0
>>> integers_found_so_far
4

💡 Объяснение:

  • Boolean-это подкласс int

  • См. Этот StackOverflow answer для обоснования этого.

Иголка в стоге сена

Почти каждый программист Python столкнулся бы с такой ситуацией.

t = ('one', 'two')
for i in t:
    print(i)

t = ('one')
for i in t:
    print(i)

t = ()
print(t)

Выход:

one
two
o
n
e
tuple()

💡 Объяснение:

  • Правильным утверждением для ожидаемого поведения является t = ('one',) или t, (пропущенная запятая), в противном случае интерпретатор считает t a str и перебирает его символ за символом.
  • () является специальным маркером и обозначает пустой кортеж .

Второстепенные

  • join() – это строковая операция, а не операция списка. (что-то вроде нелогичного при первом использовании)

    💡 Пояснение: Если join() является методом для строки, то он может работать с любой итерацией (список, кортеж, итераторы). Если бы это был метод в списке, он должен был бы быть реализован отдельно для каждого типа. Кроме того, нет особого смысла помещать специфичный для строки метод в общий список.

    Кроме того, он сильно специфичен, и кажется неправильным помещать специфичный для строки метод в общий список.

  • Несколько странных на вид, но семантически правильных утверждений:

    • [] = () – семантически корректное утверждение (распаковка пустого кортежа в пустой список )
    • -а'[0][0][0][0][0] это также семантически правильное утверждение, поскольку строки итеративны в Python.
    • 3 --0-- 5 и --5 являются семантически правильными утверждениями и оцениваются как Правда .
  • Boolean-это подкласс int

  • Python использует 2 байта для хранения локальных переменных в функциях. Теоретически это означает, что в функции можно определить только 65536 переменных. Однако в python встроено удобное решение, которое можно использовать для хранения более 2^16 имен переменных. Следующий код демонстрирует, что происходит в стеке, когда определено более 65536 локальных переменных (Предупреждение: Этот код печатает около 2^18 строк текста, так что будьте готовы!):

  • Несколько потоков Python не работают одновременно (да, вы все правильно расслышали!). Может показаться интуитивным создать несколько потоков и позволить им выполняться одновременно, но из-за глобальной блокировки интерпретатора в Python все, что вы делаете, – это заставляете ваши потоки выполняться на одном ядре по очереди. Чтобы добиться фактического распараллеливания в Python, вы можете использовать модуль Python multiprocessing .

  • Нарезка списка с выходящими за рамки индексами не вызывает ошибок

Это все, что у меня есть на данный момент. Я буду продолжать искать еще такие примеры, как этот. Если у вас есть какие-то отзывы или вы знаете более интересные примеры, не стесняйтесь комментировать здесь или создавать выпуск .