Автор оригинала: 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
(afloat
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
удаляет определенный индекс (именно поэтому firstlist_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 отсюда. Обычно он используется для измерения времени выполнения фрагментов.
- Не используйте
+
для генерации длинных строк — В Pythonstr
является неизменяемым, поэтому левая и правая строки должны быть скопированы в новую строку для каждой пары конкатенаций. Если вы объедините четыре строки длиной 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
inanother_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
astr
и перебирает его символ за символом. ()
является специальным маркером и обозначает пустойкортеж
.
Второстепенные
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 .
Нарезка списка с выходящими за рамки индексами не вызывает ошибок
Это все, что у меня есть на данный момент. Я буду продолжать искать еще такие примеры, как этот. Если у вас есть какие-то отзывы или вы знаете более интересные примеры, не стесняйтесь комментировать здесь или создавать выпуск .