Изучение и понимание Python с помощью удивительных фрагментов.
Переводы: Китайский 中文 | Вьетнамский Tiếng Việt | Испанский Испанский | Корейский 한국어 | Русский Русский | немецкий немецкий | Добавить перевод
Другие режимы: Интерактивный веб-сайт | Интерактивный блокнот
Python, будучи прекрасно спроектированным языком программирования высокого уровня, основанным на интерпретаторе, предоставляет нам множество функций для удобства программиста. Но иногда результаты фрагмента Python могут показаться неочевидными на первый взгляд.
Вот забавный проект, пытающийся объяснить, что именно происходит под капотом некоторых нелогичных фрагментов и менее известных функций Python.
Хотя некоторые примеры, которые вы видите ниже, возможно, не являются WTF в прямом смысле этого слова, но они откроют некоторые интересные части Python, о которых вы, возможно, не подозревали. Я считаю, что это хороший способ изучить внутреннюю структуру языка программирования, и я верю, что вам это тоже будет интересно!
Если вы опытный программист на Python, вы можете принять задачу сделать большинство из них правильно с первой попытки. Возможно, некоторые из них вы уже испытывали раньше, и, возможно, я смогу оживить ваши старые приятные воспоминания! ?
PS: Если вы постоянный читатель, вы можете узнать о новых модификациях здесь (примеры, отмеченные звездочкой, — это те, которые были добавлены в последней основной версии).
Итак, поехали...
is
is not ...
нет is (not ...)
del
работаgoto
, но почему?+=
быстрееdict
*dict
s *Все примеры структурированы следующим образом:
▶ Какой-нибудь причудливый заголовок
# Set up the code. # Preparation for the magic...Вывод (версии Python):
> >> triggering_statement Some unexpected output(Необязательно): одна строка, описывающая неожиданный результат.
Объяснение:
- Краткое объяснение того, что происходит и почему это происходит.
# Set up code # More examples for further clarification (if necessary)Вывод (версии Python):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
Примечание. Все примеры протестированы на интерактивном интерпретаторе Python 3.5.2 и должны работать для всех версий Python, если это явно не указано перед выводом.
На мой взгляд, хороший способ получить максимальную пользу от этих примеров — прочитать их в последовательном порядке и для каждого примера:
По какой-то причине оператор «Морж» в Python 3.8 ( :=
) стал довольно популярным. Давайте проверим это,
1.
# Python version 3.8+
> >> a = "wtf_walrus"
> >> a
'wtf_walrus'
> >> a := "wtf_walrus"
File "<stdin>" , line 1
a := "wtf_walrus"
^
SyntaxError : invalid syntax
>> > ( a := "wtf_walrus" ) # This works though
'wtf_walrus'
> >> a
'wtf_walrus'
2 .
# Python version 3.8+
> >> a = 6 , 9
> >> a
( 6 , 9 )
> >> ( a := 6 , 9 )
( 6 , 9 )
> >> a
6
> >> a , b = 6 , 9 # Typical unpacking
> >> a , b
( 6 , 9 )
>> > ( a , b = 16 , 19 ) # Oops
File "<stdin>" , line 1
( a , b = 16 , 19 )
^
SyntaxError : invalid syntax
>> > ( a , b := 16 , 19 ) # This prints out a weird 3-tuple
( 6 , 16 , 19 )
>> > a # a is still unchanged?
6
>> > b
16
Быстрая переподготовка операторов-моржей
Оператор Моржа ( :=
) был представлен в Python 3.8. Он может быть полезен в ситуациях, когда вы хотите присвоить значения переменным внутри выражения.
def some_func ():
# Assume some expensive computation here
# time.sleep(1000)
return 5
# So instead of,
if some_func ():
print ( some_func ()) # Which is bad practice since computation is happening twice
# or
a = some_func ()
if a :
print ( a )
# Now you can concisely write
if a := some_func ():
print ( a )
Выход (> 3.8):
5
5
5
Это сэкономило одну строку кода и неявно предотвратило двойной вызов some_func
.
«Выражение присваивания» без скобок (использование оператора моржа) ограничено на верхнем уровне, отсюда и SyntaxError
в операторе a := "wtf_walrus"
первого фрагмента. Заключение в скобки сработало, как и ожидалось, и присвоило a
Как обычно, заключение выражения, содержащего оператор =
, в круглые скобки не допускается. Отсюда и синтаксическая ошибка в (a, b = 6, 9)
.
Синтаксис оператора Walrus имеет форму NAME:= expr
, где NAME
— допустимый идентификатор, а expr
— допустимое выражение. Следовательно, итеративная упаковка и распаковка не поддерживаются, что означает:
(a := 6, 9)
эквивалентно ((a := 6), 9)
и, в конечном итоге (a, 9)
(где значение a
равно 6')
> >> ( a := 6 , 9 ) == (( a := 6 ), 9 )
True
> >> x = ( a := 696 , 9 )
> >> x
( 696 , 9 )
> >> x [ 0 ] is a # Both reference same memory location
True
Аналогично, (a, b := 16, 19)
эквивалентно (a, (b := 16), 19)
который представляет собой не что иное, как тройку.
1.
> >> a = "some_string"
> >> id ( a )
140420665652016
> >> id ( "some" + "_" + "string" ) # Notice that both the ids are same.
140420665652016
2.
> >> a = "wtf"
> >> b = "wtf"
> >> a is b
True
> >> a = "wtf!"
> >> b = "wtf!"
> >> a is b
False
3.
> >> a , b = "wtf!" , "wtf!"
> >> a is b # All versions except 3.7.x
True
> >> a = "wtf!" ; b = "wtf!"
> >> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print ( a is b )
# prints True when the module is invoked!
4.
Вывод (< Python3.7 )
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
Имеет смысл, не так ли?
'wtf'
будет интернирован, но ''.join(['w', 't', 'f'])
не будет интернирован)'wtf!'
не был интернирован из-за !
. Реализацию этого правила в CPython можно найти здесь. a
и b
установлено значение "wtf!"
в той же строке интерпретатор Python создает новый объект, а затем одновременно ссылается на вторую переменную. Если вы делаете это в отдельных строках, он не «знает», что уже есть "wtf!"
как объект (потому что "wtf!"
не интернируется неявно, как указано выше). Это оптимизация времени компиляции. Эта оптимизация не применяется к версиям CPython 3.7.x (подробнее см. этот вопрос).a, b = "wtf!", "wtf!"
это один оператор, тогда как a = "wtf!"; b = "wtf!"
это два утверждения в одной строке. Это объясняет, почему идентификаторы в a = "wtf!"; b = "wtf!"
, а также объясните, почему они одинаковы при вызове в some_file.py
'a'*20
заменяется на 'aaaaaaaaaaaaaaaaaaaa'
во время компиляции, чтобы сэкономить несколько тактов во время выполнения. Постоянное свертывание происходит только для строк длиной менее 21. (Почему? Представьте себе размер файла .pyc
, созданного в результате выражения 'a'*10**10
). Вот источник реализации для того же самого. > >> ( False == False ) in [ False ] # makes sense
False
> >> False == ( False in [ False ]) # makes sense
False
> >> False == False in [ False ] # now what?
True
> >> 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/3/reference/expressions.html#comparisons.
Формально, если 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 == b == c
и 0 <= x <= 100
.
False is False is False
эквивалентно (False is False) and (False is False)
True is False == False
эквивалентно (True is False) and (False == False)
, и поскольку первая часть утверждения ( True is False
) оценивается как False
, общее выражение оценивается как False
.1 > 0 < 1
эквивалентно (1 > 0) and (0 < 1)
что соответствует True
.(1 > 0) < 1
эквивалентно True < 1
и > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
дает значение False
is
Ниже приводится очень известный пример, представленный во всем Интернете.
1.
> >> a = 256
> >> b = 256
> >> a is b
True
> >> a = 257
> >> b = 257
> >> a is b
False
2.
> >> a = []
> >> b = []
> >> a is b
False
> >> a = tuple ()
> >> b = tuple ()
> >> a is b
True
3. Выход
> >> a , b = 257 , 257
> >> a is b
True
Вывод (в частности, Python 3.7.x)
> >> a , b = 257 , 257
> >> a is b
False
Разница между is
и ==
is
проверяет, относятся ли оба операнда к одному и тому же объекту (т. е. он проверяет, совпадают ли идентификаторы операндов или нет).==
сравнивает значения обоих операндов и проверяет, совпадают ли они.is
относится и к ссылочному равенству, а ==
— к равенству значений. Пример, чтобы прояснить ситуацию, > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
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
недостаточно умен, чтобы распознать, что мы уже создали целое число со значением 257,
и поэтому он продолжает создавать в памяти еще один объект.
Аналогичная оптимизация применима и к другим неизменяемым объектам, таким как пустые кортежи. Поскольку списки изменяемы, именно поэтому [] is []
вернет False
, а () is ()
вернет True
. Это объясняет наш второй фрагмент. Перейдем к третьему,
И 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
, вы бы не увидели такого поведения, поскольку файл компилируется сразу. Эта оптимизация не ограничивается целыми числами, она работает и для других неизменяемых типов данных, таких как строки (см. «Строки — сложный пример»), а также для чисел с плавающей запятой.
> >> a , b = 257.0 , 257.0
> >> a is b
True
Почему это не сработало для Python 3.7? Абстрактная причина заключается в том, что такие оптимизации компилятора зависят от реализации (т.е. могут меняться в зависимости от версии, ОС и т. д.). Я все еще выясняю, какие именно изменения реализации вызывают проблему. Вы можете проверить эту проблему на наличие обновлений.
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
Выход:
> >> some_dict [ 5.5 ]
"JavaScript"
> >> some_dict [ 5.0 ] # "Python" destroyed the existence of "Ruby"?
"Python"
> >> some_dict [ 5 ]
"Python"
> >> complex_five = 5 + 0j
> >> type ( complex_five )
complex
> >> some_dict [ complex_five ]
"Python"
Итак, почему Python повсюду?
Уникальность ключей в словаре Python определяется эквивалентностью , а не идентичностью. Таким образом, хотя 5
, 5.0
и 5 + 0j
— это разные объекты разных типов, поскольку они равны, они не могут оба находиться в одном и том же dict
(или set
). Как только вы вставите любой из них, попытка поиска любого отдельного, но эквивалентного ключа будет успешной с исходным сопоставленным значением (вместо того, чтобы потерпеть неудачу с KeyError
):
> >> 5 == 5.0 == 5 + 0j
True
> >> 5 is not 5.0 is not 5 + 0j
True
> >> some_dict = {}
> >> some_dict [ 5.0 ] = "Ruby"
> >> 5.0 in some_dict
True
> >> ( 5 in some_dict ) and ( 5 + 0j in some_dict )
True
Это также применимо при настройке элемента. Поэтому, когда вы выполняете some_dict[5] = "Python"
, Python находит существующий элемент с эквивалентным ключом 5.0 -> "Ruby"
, перезаписывает его значение на месте и оставляет исходный ключ в покое.
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
Итак, как мы можем обновить ключ до 5
(вместо 5.0
)? На самом деле мы не можем сделать это обновление на месте, но мы можем сначала удалить ключ ( del some_dict[5.0]
), а затем установить его ( some_dict[5]
), чтобы получить целое число 5
в качестве ключа вместо плавающего. 5.0
, хотя это может понадобиться в редких случаях.
Как Python нашел 5
в словаре, содержащем 5.0
? Python делает это за постоянное время без необходимости сканирования каждого элемента с помощью хеш-функций. Когда Python ищет ключ foo
в словаре, он сначала вычисляет hash(foo)
(который выполняется в постоянном времени). Поскольку в Python требуется, чтобы объекты, которые сравниваются равными, также имели одинаковое значение хеш-функции (документация здесь), 5
, 5.0
и 5 + 0j
имеют одинаковое значение хеш-функции.
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
Примечание. Обратное не обязательно верно: объекты с одинаковыми значениями хеш-функции сами по себе могут быть неравными. (Это вызывает так называемую коллизию хэшей и ухудшает производительность при постоянном времени, которую обычно обеспечивает хеширование.)
class WTF :
pass
Выход:
> >> WTF () == WTF () # two different instances can't be equal
False
> >> WTF () is WTF () # identities are also different
False
> >> hash ( WTF ()) == hash ( WTF ()) # hashes _should_ be different as well
True
> >> id ( WTF ()) == id ( WTF ())
True
При вызове id
Python создавал объект класса WTF
и передал его функции id
. Функция id
берет свой id
(его местоположение в памяти) и выбрасывает объект. Объект уничтожен.
Когда мы делаем это дважды подряд, Python выделяет ту же область памяти и для этого второго объекта. Поскольку (в CPython) id
использует местоположение памяти в качестве идентификатора объекта, идентификаторы двух объектов одинаковы.
Таким образом, идентификатор объекта уникален только на протяжении всего времени существования объекта. После уничтожения объекта или до его создания что-то еще может иметь тот же идентификатор.
Но почему оператор is
получил значение False
? Давайте посмотрим на этот фрагмент.
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
Выход:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
Как вы можете заметить, порядок, в котором объекты уничтожаются, имеет здесь решающее значение.
from collections import OrderedDict
dictionary = dict ()
dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
ordered_dict = OrderedDict ()
ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
another_ordered_dict = OrderedDict ()
another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
class DictWithHash ( dict ):
"""
A dict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
class OrderedDictWithHash ( OrderedDict ):
"""
An OrderedDict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
Выход
> >> dictionary == ordered_dict # If a == b
True
> >> dictionary == another_ordered_dict # and b == c
True
> >> ordered_dict == another_ordered_dict # then why isn't c == a ??
False
# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
TypeError : unhashable type : 'dict'
# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
> >> dictionary = DictWithHash ()
> >> dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
> >> ordered_dict = OrderedDictWithHash ()
> >> ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
> >> another_ordered_dict = OrderedDictWithHash ()
> >> another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
1
> >> len ({ ordered_dict , another_ordered_dict , dictionary }) # changing the order
2
Что здесь происходит?
Причина, по которой нетранзитивное равенство не соблюдалось между dictionary
, ordered_dict
another_ordered_dict
заключается в том, как метод __eq__
реализован в классе OrderedDict
. Из документов
Тесты равенства между объектами OrderedDict чувствительны к порядку и реализуются как
list(od1.items())==list(od2.items())
. Тесты на равенство между объектамиOrderedDict
и другими объектами Mapping нечувствительны к порядку, как и обычные словари.
Причина такого равенства в поведении заключается в том, что оно позволяет напрямую заменять объекты OrderedDict
везде, где используется обычный словарь.
Хорошо, так почему же изменение порядка повлияло на длину сгенерированного объекта set
? Ответ заключается только в отсутствии нетранзитивного равенства. Поскольку наборы представляют собой «неупорядоченные» коллекции уникальных элементов, порядок вставки элементов не имеет значения. Но в данном случае это имеет значение. Давайте немного разберемся,
> >> some_set = set ()
> >> some_set . add ( dictionary ) # these are the mapping objects from the snippets above
> >> ordered_dict in some_set
True
> >> some_set . add ( ordered_dict )
> >> len ( some_set )
1
> >> another_ordered_dict in some_set
True
> >> some_set . add ( another_ordered_dict )
> >> len ( some_set )
1
> >> another_set = set ()
> >> another_set . add ( ordered_dict )
> >> another_ordered_dict in another_set
False
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
> >> dictionary in another_set
True
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
Таким образом, несогласованность возникает из-за того, что another_ordered_dict in another_set
имеет значение False
, поскольку ordered_dict
уже присутствовал another_set
и, как отмечалось ранее, ordered_dict == another_ordered_dict
имеет значение False
.
def some_func ():
try :
return 'from_try'
finally :
return 'from_finally'
def another_func ():
for _ in range ( 3 ):
try :
continue
finally :
print ( "Finally!" )
def one_more_func (): # A gotcha!
try :
for i in range ( 3 ):
try :
1 / i
except ZeroDivisionError :
# Let's throw it here and handle it outside for loop
raise ZeroDivisionError ( "A trivial divide by zero error" )
finally :
print ( "Iteration" , i )
break
except ZeroDivisionError as e :
print ( "Zero division error occurred" , e )
Выход:
> >> some_func ()
'from_finally'
> >> another_func ()
Finally !
Finally !
Finally !
>> > 1 / 0
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
ZeroDivisionError : division by zero
> >> one_more_func ()
Iteration 0
try
оператора «try…finally» выполняется оператор return
, break
или continue
, на выходе также выполняется предложение finally
.return
. Поскольку предложение finally
выполняется всегда, оператор return
, выполняемый в предложении finally
, всегда будет выполняться последним.return
или break
, временно сохраненное исключение отбрасывается. some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
Выход:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
Оператор for
определяется в грамматике Python как:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
Где exprlist
— цель назначения. Это означает, что эквивалент {exprlist} = {next_value}
выполняется для каждого элемента в итерации. Интересный пример, иллюстрирующий это:
for i in range ( 4 ):
print ( i )
i = 10
Выход:
0
1
2
3
Ожидали ли вы, что цикл запустится только один раз?
Объяснение:
i = 10
никогда не влияет на итерации цикла из-за особенностей работы циклов for в Python. Перед началом каждой итерации следующий элемент, предоставленный итератором (в данном случае range(4)
), распаковывается и ему присваиваются переменные целевого списка (в данном случае i
). Функция enumerate(some_string)
возвращает новое значение i
(растущий счетчик) и символ из some_string
на каждой итерации. Затем он устанавливает (только что назначенный) ключ i
словаря some_dict
на этот символ. Развертывание цикла можно упростить следующим образом:
> >> i , some_dict [ i ] = ( 0 , 'w' )
> >> i , some_dict [ i ] = ( 1 , 't' )
> >> i , some_dict [ i ] = ( 2 , 'f' )
> >> some_dict
1.
array = [ 1 , 8 , 15 ]
# A typical generator expression
gen = ( x for x in array if array . count ( x ) > 0 )
array = [ 2 , 8 , 22 ]
Выход:
> >> print ( list ( gen )) # Where did the other values go?
[ 8 ]
2.
array_1 = [ 1 , 2 , 3 , 4 ]
gen_1 = ( x for x in array_1 )
array_1 = [ 1 , 2 , 3 , 4 , 5 ]
array_2 = [ 1 , 2 , 3 , 4 ]
gen_2 = ( x for x in array_2 )
array_2 [:] = [ 1 , 2 , 3 , 4 , 5 ]
Выход:
> >> print ( list ( gen_1 ))
[ 1 , 2 , 3 , 4 ]
> >> print ( list ( gen_2 ))
[ 1 , 2 , 3 , 4 , 5 ]
3.
array_3 = [ 1 , 2 , 3 ]
array_4 = [ 10 , 20 , 30 ]
gen = ( i + j for i in array_3 for j in array_4 )
array_3 = [ 4 , 5 , 6 ]
array_4 = [ 400 , 500 , 600 ]
Выход:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
В выражении генератора предложение in
оценивается во время объявления, но условное предложение оценивается во время выполнения.
Таким образом, перед выполнением array
переназначается в список [2, 8, 22]
, и поскольку из 1
, 8
и 15
только количество 8
больше 0
, генератор выдает только 8
.
Различия в выводе g1
и g2
во второй части связаны с тем, как переменным array_1
и array_2
переназначаются значения.
В первом случае array_1
привязан к новому объекту [1,2,3,4,5]
, и поскольку предложение in
оценивается во время объявления, оно все еще ссылается на старый объект [1,2,3,4]
(который не уничтожается).
Во втором случае присвоение среза array_2
обновляет тот же старый объект [1,2,3,4]
до [1,2,3,4,5]
. Следовательно, и g2
, и array_2
по-прежнему ссылаются на один и тот же объект (который теперь обновлен до [1,2,3,4,5]
).
Хорошо, если следовать логике, обсуждавшейся до сих пор, не должно ли значение list(gen)
в третьем фрагменте быть [11, 21, 31, 12, 22, 32, 13, 23, 33]
? (потому что array_3
и array_4
будут вести себя так же, как array_1
). Причина, по которой обновлялись (только) значения array_4
, объясняется в PEP-289.
Немедленно вычисляется только самое внешнее выражение for, остальные выражения откладываются до запуска генератора.
is not ...
нет is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
— это один бинарный оператор, и его поведение отличается от использования is
, и not
разделен.is not
оценивается как False
если переменные по обе стороны от оператора указывают на один и тот же объект, и True
в противном случае.(not None)
оценивается как True
поскольку значение None
имеет False
в логическом контексте, поэтому выражение становится 'something' is True
. # 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' , '' , '' ]]
Мы же не поставили три "X"
, не так ли?
Когда мы инициализируем переменную row
, эта визуализация объясняет, что происходит в памяти.
И когда board
инициализируется путем умножения row
, это то, что происходит внутри памяти (каждый из элементов board[0]
, board[1]
и board[2]
является ссылкой на один и тот же список, на который ссылается row
)
Мы можем избежать этого сценария, если не будем использовать переменную row
для создания board
. (Вопрос задан в этом выпуске).
> >> board = [[ '' ] * 3 for _ in range ( 3 )]
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
funcs = []
results = []
for x in range ( 7 ):
def some_func ():
return x
funcs . append ( some_func )
results . append ( some_func ()) # note the function call here
funcs_results = [ func () for func in funcs ]
Вывод (версия Python):
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
Значения x
были разными на каждой итерации до добавления some_func
к funcs
, но все функции возвращают 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 ]
x
в окружающем контексте, а не использует значение x
во время создания функции. Таким образом, все функции используют для вычислений последнее значение, присвоенное переменной. Мы видим, что он использует x
из окружающего контекста (т.е. не локальную переменную) с помощью: > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
Поскольку x
является глобальным значением, мы можем изменить значение, которое funcs
будут искать и возвращать, обновив x
:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
в данный момент времени. funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
Выход:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
Он больше не использует x
в глобальной области видимости:
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
Так какой же «окончательный» базовый класс? Между прочим, путаницы здесь больше,
2.
> >> class A : pass
> >> isinstance ( A , A )
False
> >> isinstance ( type , type )
True
> >> isinstance ( object , object )
True
3.
> >> issubclass ( int , object )
True
> >> issubclass ( type , object )
True
> >> issubclass ( object , type )
False
type
— это метакласс в Python.object
, включая классы, а также их объекты (экземпляры).type
является метаклассом класса object
, и каждый класс (включая type
) прямо или косвенно унаследован от object
.object
и type
нет настоящего базового класса. Путаница в приведенных выше фрагментах возникает потому, что мы думаем об этих отношениях ( issubclass
и isinstance
) с точки зрения классов Python. Отношения между object
и type
невозможно воспроизвести в чистом Python. Точнее, следующие отношения невозможно воспроизвести в чистом Python:object
и type
(которые являются экземплярами друг друга и сами по себе) существуют в Python из-за «обмана» на уровне реализации.Выход:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
Ожидалось, что отношения подклассов будут транзитивными, верно? (т.е. если A
является подклассом B
, а B
является подклассом C
, A
должен быть подклассом C
)
__subclasscheck__
в метаклассе.issubclass(cls, Hashable)
, он просто ищет не-Falsey метод " __hash__
" в cls
или во всем, от чего он наследуется.object
хэшируемый, а list
нехешируемый, это нарушает отношение транзитивности. class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
Выход:
> >> print ( SomeClass . method is SomeClass . method )
True
> >> print ( SomeClass . classm is SomeClass . classm )
False
> >> print ( SomeClass . classm == SomeClass . classm )
True
> >> print ( SomeClass . staticm is SomeClass . staticm )
True
Обращаясь к classm
дважды, мы получаем равный объект, но не тот же самый ? Давайте посмотрим, что происходит с экземплярами SomeClass
:
o1 = SomeClass ()
o2 = SomeClass ()
Выход:
> >> print ( o1 . method == o2 . method )
False
> >> print ( o1 . method == o1 . method )
True
> >> print ( o1 . method is o1 . method )
False
> >> print ( o1 . classm is o1 . classm )
False
> >> print ( o1 . classm == o1 . classm == o2 . classm == SomeClass . classm )
True
> >> print ( o1 . staticm is o1 . staticm is o2 . staticm is SomeClass . staticm )
True
Двойной доступ к classm
или method
создает равные, но не одинаковые объекты для одного и того же экземпляра SomeClass
.
self
в качестве первого аргумента, несмотря на то, что не передаем его явно). > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
который никогда не бывает правдивым. Однако доступ к функциям как атрибутам класса (в отличие от экземпляра) не создает методов; поэтому SomeClass.method is SomeClass.method
правдивым. > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
преобразует функции в методы класса. Методы класса — это дескрипторы, которые при доступе создают объект метода, который связывает класс (тип) объекта, а не сам объект. > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
создает метод также при доступе к нему как к атрибутам класса (в этом случае они привязывают класс, а не его тип). Итак, SomeClass.classm is SomeClass.classm
это ложь. > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
правдив, хотя и не тот же самый объект в памяти.staticmethod
преобразует функции в «неактивный» дескриптор, который возвращает функцию как есть. Никакие объекты-методы никогда не создаются, поэтому сравнение с is
правдиво. > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
производительность. В CPython 3.7 эта проблема решена путем введения новых кодов операций, которые позволяют вызывать методы без создания временных объектов методов. Это используется только тогда, когда фактически вызывается функция, к которой осуществляется доступ, поэтому фрагменты здесь не затрагиваются и все равно генерируют методы :) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
Почему это изменение «Истинно-Ложно»?
Реализация all
функций эквивалентна
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
возвращает True
поскольку итерируемый объект пуст.
all([[]])
возвращает False
поскольку переданный массив содержит один элемент, []
, а в Python пустой список является ложным.
all([[[]]])
и более высокие рекурсивные варианты всегда имеют True
. Это связано с тем, что единственный элемент переданного массива ( [[...]]
) больше не пуст, а списки со значениями правдивы.
Выход (< 3,6):
> >> def f ( x , y ,):
... print ( x , y )
...
> >> def g ( x = 4 , y = 5 ,):
... print ( x , y )
...
> >> def h ( x , ** kwargs ,):
File "<stdin>" , line 1
def h ( x , ** kwargs ,):
^
SyntaxError : invalid syntax
>> > def h ( * args ,):
File "<stdin>" , line 1
def h ( * args ,):
^
SyntaxError : invalid syntax
Выход:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
) обратные косые черты передаются как есть вместе с поведением экранирования следующего символа. > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
) обратная косая черта экранирует конечную кавычку, оставляя анализатор без завершающей кавычки (отсюда и SyntaxError
). Вот почему обратная косая черта не работает в конце необработанной строки. x = True
y = False
Выход:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
имеет более высокий приоритет, чем оператор not
в Python.not x == y
эквивалентно not (x == y)
, что эквивалентно not (True == False)
с окончательной оценкой True
.x == not y
вызывает SyntaxError
, поскольку его можно считать эквивалентным (x == not) y
, а не x == (not y)
чего вы могли ожидать на первый взгляд.not
будет частью оператора not in
(поскольку операторы ==
и not in
имеют одинаковый приоритет), но после того, как он не смог найти токен in
после токена not
, он выдает SyntaxError
.Выход:
> >> print ( 'wtfpython' '' )
wtfpython
> >> print ( "wtfpython" "" )
wtfpython
> >> # The following statements raise `SyntaxError`
>> > # print('''wtfpython')
>> > # print("""wtfpython")
File "<input>" , line 3
print ("""wtfpython")
^
SyntaxError: EOF while scanning triple-quoted string literal
>>> print("wtf" "python")
wtfpython
>>> print("wtf" "") # or "wtf"""
wtf
'''
и """
также являются разделителями строк в Python, что вызывает SyntaxError, поскольку интерпретатор Python ожидал завершающую тройную кавычку в качестве разделителя при сканировании встреченного в данный момент строкового литерала в тройных кавычках.1.
# A simple example to count the number of booleans 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
Выход:
> >> integers_found_so_far
4
> >> booleans_found_so_far
0
2.
> >> some_bool = True
> >> "wtf" * some_bool
'wtf'
> >> some_bool = False
> >> "wtf" * some_bool
''
3.
def tell_truth ():
True = False
if True == False :
print ( "I have lost faith in truth!" )
Выход (< 3.x):
> >> tell_truth ()
I have lost faith in truth !
bool
— это подкласс int
в Python
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
Таким образом, True
и False
являются экземплярами int
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
Целочисленное значение True
— 1
, а False
— 0
.
> >> int ( True )
1
> >> int ( False )
0
См. этот ответ StackOverflow, чтобы узнать причину этого.
Изначально в Python не было типа bool
(люди использовали 0 для ложного и ненулевое значение, например 1 для истинного). True
, False
и тип bool
были добавлены в версиях 2.x, но для обратной совместимости True
и False
нельзя было сделать константами. Это просто были встроенные переменные, и их можно было переназначать.
Python 3 был обратно несовместим, проблема была наконец исправлена, и поэтому последний фрагмент не будет работать с Python 3.x!
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 # C.x changed, but B.x didn't
( 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_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
Вывод (<= 3.7.x):
> >> [ x for x in some_iterable ]
[ 'a' , 'b' ]
> >> [( yield x ) for x in some_iterable ]
< generator object < listcomp > at 0x7f70b0a4ad58 >
> >> list ([( yield x ) for x in some_iterable ])
[ 'a' , 'b' ]
> >> list (( yield x ) for x in some_iterable )
[ 'a' , None , 'b' , None ]
> >> list ( some_func (( yield x )) for x in some_iterable )
[ 'a' , 'something' , 'b' , 'something' ]
yield
в генераторах и генераторах.yield
внутри списка и выдает SyntaxError
.1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
Выход (> 3.3):
> >> list ( some_func ( 3 ))
[]
Куда пропало это "wtf"
? Это связано с каким-то особым эффектом yield from
? Давайте подтвердим это,
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
Выход:
> >> list ( some_func ( 3 ))
[]
Результат тот же, это тоже не сработало.
return
со значениями внутри генераторов (см. PEP380). Официальные документы говорят, что,«...
return expr
в генераторе вызывает вызовStopIteration(expr)
при выходе из генератора».
В случае some_func(3)
StopIteration
вызывается в начале из-за оператора return
. Исключение StopIteration
автоматически перехватывается внутри оболочки list(...)
и цикла for
. Таким образом, приведенные выше два фрагмента приводят к пустому списку.
Чтобы получить ["wtf"]
от генератора some_func
нам нужно перехватить исключение StopIteration
,
try :
next ( some_func ( 3 ))
except StopIteration as e :
some_string = e . value
> >> some_string
[ "wtf" ]
1.
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
2.
> >> x = float ( 'nan' )
> >> y = x / x
> >> y is y # identity holds
True
> >> y == y # equality fails of y
False
> >> [ y ] == [ y ] # but the equality succeeds for the list containing y
True
'inf'
и 'nan'
— это специальные строки (без учета регистра), которые при явном приведении к типу float
используются для представления математической «бесконечности» и «не числа» соответственно.
Поскольку согласно стандартам IEEE NaN != NaN
, соблюдение этого правила нарушает предположение о рефлексивности элемента коллекции в Python, т.е. если x
является частью коллекции, такой как list
, такие реализации, как сравнение, основаны на предположении, что x == x
. Из-за этого предположения при сравнении двух элементов сначала сравнивается идентификатор (поскольку это быстрее), а значения сравниваются только в том случае, если идентификаторы не совпадают. Следующий фрагмент прояснит ситуацию:
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
Поскольку тождества x
и y
различны, рассматриваются значения, которые также различны; следовательно, на этот раз сравнение возвращает False
.
Интересное чтение: Рефлексивность и другие столпы цивилизации
Это может показаться тривиальным, если вы знаете, как работают ссылки в Python.
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/3/reference/datamodel.html.
Неизменяемые последовательности Объект типа неизменяемой последовательности не может измениться после его создания. (Если объект содержит ссылки на другие объекты, эти другие объекты могут быть изменяемыми и могут быть изменены; однако коллекция объектов, на которые непосредственно ссылается неизменяемый объект, не может измениться.)
Оператор +=
изменяет список на месте. Назначение элемента не работает, но при возникновении исключения элемент уже был изменен на месте.
Объяснение также есть в официальном FAQ по Python.
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#Exception
Если исключение было назначено с использованием as
цели, оно очищается в конце предложения except
. Это как если бы
except E as N :
foo
был переведен на
except E as N :
try :
foo
finally :
del N
Это означает, что исключению необходимо присвоить другое имя, чтобы можно было ссылаться на него после предложения исключения. Исключения очищаются, поскольку с присоединенной к ним обратной трассировкой они образуют ссылочный цикл с кадром стека, сохраняя все локальные значения в этом кадре живыми до тех пор, пока не произойдет следующая сборка мусора.
Предложения не ограничены областью действия в Python. Все в примере присутствует в одной области, а переменная e
была удалена из-за выполнения условия except
. Чего нельзя сказать о функциях, которые имеют отдельные внутренние области видимости. Пример ниже иллюстрирует это:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
Выход:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
В Python 2.x имя переменной e
назначается в экземпляр Exception()
, поэтому, когда вы пытаетесь печатать, оно ничего не печатает.
Вывод (Python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
Выход:
> >> type ( list ( some_dict . keys ())[ 0 ])
str
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict # expected: Two different keys-value pairs
{ 's' : 40 }
> >> type ( list ( some_dict . keys ())[ 0 ])
str
Как объект s
так и строка "s"
хэш к одному и тому же значению, потому что SomeClass
унаследовало метод __hash__
класса str
.
SomeClass("s") == "s"
оценивает True
, потому что SomeClass
также наследует метод __eq__
от класса str
.
Поскольку оба объекта хеша к одному и тому же значению и равны, они представлены одним и тем же ключом в словаре.
Для желаемого поведения мы можем переопределить метод __eq__
в SomeClass
class SomeClass ( str ):
def __eq__ ( self , other ):
return (
type ( self ) is SomeClass
and type ( other ) is SomeClass
and super (). __eq__ ( other )
)
# When we define a custom __eq__, Python stops automatically inheriting the
# __hash__ method, so we need to define it as well
__hash__ = str . __hash__
some_dict = { 's' : 42 }
Выход:
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict
{ 's' : 40 , 's' : 42 }
> >> keys = list ( some_dict . keys ())
> >> type ( keys [ 0 ]), type ( keys [ 1 ])
( __main__ . SomeClass , str )
a , b = a [ b ] = {}, 5
Выход:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
Заявление о назначении оценивает список выражений (помните, что это может быть одно выражение или разделенный запятой список, последний дает кортеж) и назначает единственный результирующий объект каждому из целевых списков слева направо.
+
In (target_list "=")+
означает, что может быть один или несколько целевых списков. В этом случае целевыми списками являются a, b
и a[b]
(обратите внимание, что список выражений является именно одним, что в нашем случае {}, 5
).
После того, как список выражений оценивается, его значение распаковывается в целевых списках слева направо . Итак, в нашем случае, сначала {}, 5
корпус распаковывается до a, b
, и теперь у нас есть a = {}
и b = 5
.
a
теперь назначается {}
, который является изменчивым объектом.
Второй список целей - это a[b]
(вы можете ожидать, что это принесет ошибку, потому что как a
, так и b
не были определены в операторах ранее. Но помните, мы только что присвоили a
{}
и b
до 5
).
Теперь мы устанавливаем ключ 5
в словаре на кортеж ({}, 5)
Создание круговой ссылки ( {...}
в выводе относится к тому же объекту, на который уже ссылается a
). Еще один более простой пример круговой ссылки может быть
> >> some_list = some_list [ 0 ] = [ 0 ]
> >> some_list
[[...]]
> >> some_list [ 0 ]
[[...]]
> >> some_list is some_list [ 0 ]
True
> >> some_list [ 0 ][ 0 ][ 0 ][ 0 ][ 0 ][ 0 ] == some_list
True
Похоже, в нашем примере ( a[b][0]
является тем же объектом, что и a
)
Итак, подвести итог, вы можете сломать пример до
a , b = {}, 5
a [ b ] = a , b
И круговая ссылка может быть оправдана тем фактом, что a[b][0]
- тот же объект, что a
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
Выход:
> >> # Python 3.10.6
222222222222222222222222222222222222222222222222222222222222222. ..
>> > # Python 3.10.8
Traceback ( most recent call last ):
...
ValueError : Exceeds the limit ( 4300 ) for integer string conversion :
value has 5432 digits ; use sys . set_int_max_str_digits ()
to increase the limit .
Этот призыв к int()
отлично работает в Python 3.10.6 и повышает значение Phyner 3.10.8. Обратите внимание, что Python все еще может работать с большими целыми числами. Ошибка поднимается только при преобразовании между целыми и строками.
К счастью, вы можете увеличить предел допустимого количества цифр, когда ожидаете, что операция превысит ее. Для этого вы можете использовать одно из следующих действий:
Проверьте документацию для получения более подробной информации об изменении предела по умолчанию, если вы ожидаете, что ваш код превысит это значение.
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
Выход (Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
Да, он работает ровно восемь раз и останавливается.
RuntimeError: dictionary keys changed during iteration
если вы попытаетесь сделать это.del
class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
Вывод: 1.
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
Фу, наконец удалил. Возможно, вы догадались, что спасло __del__
от того, чтобы быть вызванным в нашей первой попытке удалить x
. Давайте добавим больше поворотов в пример.
2.
> >> x = SomeClass ()
> >> y = x
> >> del x
> >> y # check if y exists
< __main__ . SomeClass instance at 0x7f98a1a67fc8 >
> >> del y # Like previously, this should print "Deleted!"
> >> globals () # oh, it didn't. Let's check all our global variables and confirm
Deleted !
{ '__builtins__' : < module '__builtin__' ( built - in ) > , 'SomeClass' : < class __main__ . SomeClass at 0x7f98a1a5f668 > , '__package__' : None , '__name__' : '__main__' , '__doc__' : None }
Хорошо, теперь это удалено?
del x
не напрямую вызывает x.__del__()
.del x
, Python удаляет имя x
из тока и уменьшения на 1 ссылку на объект x
ссылки. __del__()
вызывается только тогда, когда количество ссылок объекта достигает нуля.__del__()
не был вызван, потому что предыдущий оператор ( >>> y
) в интерактивном интерпретаторе создал другую ссылку на тот же объект (в частности, переменную _
Magic, которая ссылается на значение результата последнего ne None
выражение на переписке), таким образом, предотвращая достижение количества эталонов, достигнув нуля при столкновении del y
globals
(или на самом деле, выполнение всего, что будет иметь результат, не None
), вызвало _
для ссылки на новый результат, отбросив существующую ссылку. Теперь количество ссылок достиг 0, и мы можем увидеть «Удаленный!» Печать (наконец -то!).1.
a = 1
def some_func ():
return a
def another_func ():
a += 1
return a
2.
def some_closure_func ():
a = 1
def some_inner_func ():
return a
return some_inner_func ()
def another_closure_func ():
a = 1
def another_inner_func ():
a += 1
return a
return another_inner_func ()
Выход:
> >> some_func ()
1
> >> another_func ()
UnboundLocalError : local variable 'a' referenced before assignment
>> > some_closure_func ()
1
> >> another_closure_func ()
UnboundLocalError : local variable 'a' referenced before assignment
Когда вы делаете назначение переменной по объему, она становится локальной для этой области. Таким образом, a
становится локальным к объему another_func
, но ранее он не был инициализирован в той же сфере, который бросает ошибку.
Чтобы изменить переменную внешней области a
в another_func
, мы должны использовать global
ключевое слово.
def another_func ()
global a
a += 1
return a
Выход:
> >> another_func ()
2
В another_closure_func
a
становится локальным для объема another_inner_func
, но он не был инициализирован ранее в той же области, поэтому он бросает ошибку.
Чтобы изменить переменную внешней области a
в another_inner_func
, используйте nonlocal
ключевое слово. Нелокальное утверждение используется для обозначения переменных, определенных в ближайшей внешней (исключая глобальную) область.
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
Выход:
> >> another_func ()
2
Ключевые слова global
и nonlocal
говорят интерпретатору Python не объявлять новые переменные и смотреть на них на соответствующих внешних областях.
Прочитайте это короткое, но отличное руководство, чтобы узнать больше о том, как работают пространства имен и разрешение на область в Python.
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[:]
делает именно это.
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
Разница между del
, remove
и pop
:
del var_name
просто удаляет привязку var_name
из локального или глобального пространства имен (вот почему list_1
не затронут).remove
удаление первого соответствующего значения, а не конкретного индекса, повышает ValueError
если значение не найдено.pop
удаляет элемент в определенном индексе и возвращает его, повышает IndexError
если указан недопустимый индекс. Почему выход [2, 4]
?
1
из list_2
или list_4
, содержимое списков сейчас [2, 3, 4]
. Оставшиеся элементы сдвинуты вниз, т.е. 2
находится в индексе 0, а 3
находится в индексе 1. Поскольку следующая итерация будет рассмотреть индекс 1 (который 3
), 2
пропускаются полностью. Похожая вещь произойдет с каждым альтернативным элементом в последовательности списка. > >> numbers = list ( range ( 7 ))
> >> numbers
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> first_three , remaining = numbers [: 3 ], numbers [ 3 :]
> >> first_three , remaining
([ 0 , 1 , 2 ], [ 3 , 4 , 5 , 6 ])
>> > numbers_iter = iter ( numbers )
>> > list ( zip ( numbers_iter , first_three ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
# so far so good, let's zip the remaining
>> > list ( zip ( numbers_iter , remaining ))
[( 4 , 3 ), ( 5 , 4 ), ( 6 , 5 )]
Где элемент 3
пошел из списка numbers
?
def zip ( * iterables ):
sentinel = object ()
iterators = [ iter ( it ) for it in iterables ]
while iterators :
result = []
for it in iterators :
elem = next ( it , sentinel )
if elem is sentinel : return
result . append ( elem )
yield tuple ( result )
result
, вызывая next
функцию на них, и останавливается всякий раз, когда любой из иерных исчерпана.result
отброшены. Вот что произошло с 3
в numbers_iter
.zip
был бы, был бы, > >> numbers = list ( range ( 7 ))
> >> numbers_iter = iter ( numbers )
> >> list ( zip ( first_three , numbers_iter ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
> >> list ( zip ( remaining , numbers_iter ))
[( 3 , 3 ), ( 4 , 4 ), ( 5 , 5 ), ( 6 , 6 )]
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
никогда не определялся вне сферы действия для петли ...
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.
Вывод (Python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
Вывод (Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
В Python для петлей используют область, в которой они существуют, и оставляют свою определенную петлю-переменную позади. Это также применимо, если мы явно определили переменную для петли в глобальном пространстве имен раньше. В этом случае это будет восстановить существующую переменную.
Различия в выводе Python 2.x и Python 3.x для перевода для примера понимания списка можно объяснить, следующие изменения, документированные в том, что нового в Python 3.0 Changelog:
«Понимание списка больше не поддерживает синтаксическую форму
[... for var in item1, item2, ...]
. Используйте[... for var in (item1, item2, ...)]
вместо этого. Также обратите внимание на этот список Понимания имеют различную семантику: они находятся ближе к синтаксическому сахару для выражения генератора внутри конструктораlist()
, и, в частности, переменные управления петлями больше не пропускаются в окружающую область ».
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_func
в качестве аргумента, значение по умолчанию переменной default_arg
не использовалось, поэтому функция возвращалась, как и ожидалось.
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Выход:
> >> some_func . __defaults__ #This will show the default argument values for the function
([],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' ],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
> >> some_func ([])
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
Обычная практика, позволяющая избежать ошибок из -за измененных аргументов, состоит в том, чтобы назначать None
значения по умолчанию, а затем проверить, передается ли какое -либо значение функции, соответствующей этому аргументу. Пример:
def some_func ( default_arg = None ):
if default_arg is None :
default_arg = []
default_arg . append ( "some_string" )
return default_arg
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 "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
Чтобы добавить несколько исключений из этого пункта, вам необходимо передать их в качестве скобку в качестве первого аргумента. Второй аргумент - это необязательное имя, которое, когда поставляется, будет связывать экземпляр исключения, который был поднят. Пример,
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
Вывод (Python 2.x):
Caught again!
list.remove(x): x not in list
Вывод (Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
Отделение исключения от переменной с запятой устарело и не работает на Python 3; Правильный способ использовать as
. Пример,
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
Выход:
Caught again!
list.remove(x): x not in list
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 = a + b
Классы могут реализовать операторы op=
по -разному, и списки делают это.
Выражение a = a + [5,6,7,8]
генерирует новый список и устанавливает a
на этот новый список, оставляя b
без изменений.
Выражение a += [5,6,7,8]
фактически отображается с функцией «расширения», которая работает в списке, так что a
и b
все еще указывают на тот же список, который был изменен на месте.
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
Давайте реализуем наивную функцию, чтобы получить средний элемент списка:
def get_middle ( some_list ):
mid_index = round ( len ( some_list ) / 2 )
return some_list [ mid_index - 1 ]
Python 3.x:
> >> get_middle ([ 1 ]) # looks good
1
> >> get_middle ([ 1 , 2 , 3 ]) # looks good
2
> >> get_middle ([ 1 , 2 , 3 , 4 , 5 ]) # huh?
2
> >> len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 # good
2.5
> >> round ( len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 ) # why?
2
Кажется, что Python округл 2,5 до 2.
round()
использует округление банкира, где .5 фракции округлены до ближайшего ровного числа: > >> round ( 0.5 )
0
> >> round ( 1.5 )
2
> >> round ( 2.5 )
2
> >> import numpy # numpy does the same
> >> numpy . round ( 0.5 )
0.0
> >> numpy . round ( 1.5 )
2.0
> >> numpy . round ( 2.5 )
2.0
get_middle([1])
вернулся только 1, потому что индекс был round(0.5) - 1 = 0 - 1 = -1
, возвращая последний элемент в списке.Я не встречал даже ни одного питониста опыта, который не сталкивался с одним или несколькими из следующих сценариев,
1.
x , y = ( 0 , 1 ) if True else None , None
Выход:
> >> x , y # expected (0, 1)
(( 0 , 1 ), None )
2.
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 ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
Выход
> >> len ( ten_words_list )
9
4. Не утверждая достаточно сильно
a = "python"
b = "javascript"
Выход:
# An assert statement with an assertion failure message.
> >> assert ( a == b , "Both languages are different" )
# No AssertionError is raised
5.
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
6.
def some_recursive_func ( a ):
if a [ 0 ] == 0 :
return
a [ 0 ] -= 1
some_recursive_func ( a )
return a
def similar_recursive_func ( a ):
if a == 0 :
return a
a -= 1
similar_recursive_func ( a )
return a
Выход:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
Для 1 правильным утверждением для ожидаемого поведения является x, y = (0, 1) if True else (None, None)
.
Для 2 правильным утверждением для ожидаемого поведения является t = ('one',)
или t = 'one',
(отсутствующая запятая), в противном случае интерпретатор считает t
как str
и итерат над ним характером.
()
это особый токен и обозначает пустой tuple
.
В 3, как вы уже поняли, в списке отсутствует запятая после 5 -го элемента ( "that"
). Итак, путем неявной струнной буквальной конкатенации,
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
В 4 -м фрагменте не было поднято никаких AssertionError
потому что вместо того, чтобы утверждать индивидуальное выражение a == b
, мы утверждаем весь кортеж. Следующий фрагмент прояснит все,
> >> a = "python"
> >> b = "javascript"
> >> assert a == b
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError
>> > assert ( a == b , "Values are not equal" )
< stdin > : 1 : SyntaxWarning : assertion is always true , perhaps remove parentheses ?
>> > assert a == b , "Values are not equal"
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError : Values are not equal
Что касается пятого фрагмента, то большинство методов, которые изменяют элементы объектов последовательности/отображения, таких как list.append
, dict.update
, list.sort
и т. Д. Измените объекты на месте и None
возвращайте. Обоснование этого заключается в улучшении производительности, избегая создания копии объекта, если операция может быть сделана на месте (упоминается отсюда).
Последний должен быть довольно очевидным, изменяемый объект (например, list
) может быть изменен в функции, а переназначение неизменного ( a -= 1
) не является изменением значения.
Знание этих придирков может сэкономить вам часы отладки в долгосрочной перспективе.
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
, но согласно документамЕсли SEP не указан или
None
, применяется другой алгоритм разделения: прогоны последовательных пробелов рассматриваются как один сепаратор, и результат не будет содержать пустых строк с начала или конца, если в строке есть ведущие или следственные пробелы. Следовательно, разделение пустой строки или строки, состоящая только из пробела с разделителем, не возвращается[]
. Если дается SEP, последовательные делимиторы не сгруппированы вместе и считаются делимитом пустыми строками (например,'1,,2'.split(',')
возвращает['1', '', '2']
). Разделение пустой строки с указанным сепаратором возвращает['']
.
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Выход
> >> from module import *
> >> some_weird_name_func_ ()
"works!"
> >> _another_weird_name_func ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name '_another_weird_name_func' is not defined
Часто рекомендуется не использовать импорт подстановочных знаков. Первая очевидная причина этого - в импорте подстановочного знака, имена с ведущим подчеркиванием не импортируются. Это может привести к ошибкам во время выполнения.
Если бы мы использовали from ... import a, b, c
, вышеупомянутое NameError
не произошло.
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
Если вы действительно хотите использовать импорт подстановочных знаков, вам нужно будет определить список __all__
в своем модуле, который будет содержать список публичных объектов, которые будут доступны, когда мы делаем импорт подстановочного знака.
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Выход
> >> _another_weird_name_func ()
"works!"
> >> some_weird_name_func_ ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'some_weird_name_func_' is not defined
> >> x = 7 , 8 , 9
> >> sorted ( x ) == x
False
> >> sorted ( x ) == sorted ( x )
True
> >> y = reversed ( x )
> >> sorted ( y ) == sorted ( y )
False
sorted
метод всегда возвращает список, и сравнение списков и кортежей всегда возвращает False
в Python.
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
В отличие от sorted
, reversed
метод возвращает итератор. Почему? Поскольку сортировка требует, чтобы итератор был либо изменен на месте, либо использовал дополнительный контейнер (список), тогда как реверсирование может просто работать, итерация от последнего индекса до первого.
Таким образом, во время сравнения sorted(y) == sorted(y)
первый вызов sorted()
будет потреблять итератор y
, а следующий вызов просто вернет пустой список.
> >> x = 7 , 8 , 9
> >> y = reversed ( x )
> >> sorted ( y ), sorted ( y )
([ 7 , 8 , 9 ], [])
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 )
Вывод (<3,5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
Полночное время не напечатано.
Перед Python 3.5 логическое значение для объекта datetime.time
считалось False
, если оно представляло собой полночь в UTC. Это подвержено ошибке при использовании if obj:
синтаксис, чтобы проверить, является ли obj
NULL или какой-то эквивалент «пустого».
Этот раздел содержит несколько менее известных и интересных вещей о Python, о которых большинство новичков, таких как я, не знают (ну, больше не).
Ну, вот ты
import antigravity
Вывод: SSHH ... это суперсекретный.
antigravity
- одно из немногих пасхальных яиц, выпущенных разработчиками Python.import antigravity
открывает веб -браузер, указывающий на классический комикс XKCD о Python.goto
, но почему? from goto import goto , label
for i in range ( 9 ):
for j in range ( 9 ):
for k in range ( 9 ):
print ( "I am trapped, please rescue!" )
if k == 2 :
goto . breakout # breaking out from a deeply nested loop
label . breakout
print ( "Freedom!" )
Вывод (Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
в Python была объявлена апрельской шуткой 1 апреля 2004 года.goto
не присутствует в Python.Если вы один из тех, кто не любит использовать Whitespace в Python для обозначения областей, вы можете использовать C-стиль {}, импортируя,
from __future__ import braces
Выход:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
Брекеты? Ни за что! Если вы думаете, что это разочаровывает, используйте Java. Ладно, еще одна удивительная вещь, вы можете найти, где SyntaxError
поднятый в коде модуля __future__
?
__future__
обычно используется для предоставления функций из будущих версий Python. «Будущее» в этом конкретном контексте, однако, иронично.future.c
File.future.c
прежде чем рассматривать его как обычный оператор импорта.Вывод (Python 3.x)
> >> from __future__ import barry_as_FLUFL
> >> "Ruby" != "Python" # there's no doubt about it
File "some_file.py" , line 1
"Ruby" != "Python"
^
SyntaxError : invalid syntax
>> > "Ruby" <> "Python"
True
Вот и все.
Это имеет отношение к PEP-401, выпущенному 1 апреля 2009 года (теперь вы знаете, что это значит).
Цитата из PEP-401
Признался, что оператор неравенства в Python 3.0 был ужасной ошибкой, вызванной пальцами, линейка лививания восстанавливает оператора <> Diamond как единственное правописание.
Было больше вещей, которые дядя Барри должен был поделиться в PEP; Вы можете прочитать их здесь.
Он хорошо работает в интерактивной среде, но при запуске Python будет повысить SyntaxError
(см. Этот вопрос). Тем не менее, вы можете обернуть заявление внутри eval
или compile
, чтобы он работал,
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
Подожди, что это ? this
любовь ❤
Выход:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Это дзен питона!
> >> love = this
> >> this is love
True
> >> love is True
False
> >> love is False
False
> >> love is not True or False
True
> >> love is not True or False ; love is love # Love is complicated
True
this
модуль в Python является пасхальным яйцом для дзен питона (PEP 20).love is not True or False; love is love
, ироничная, но она самоочевидна (если нет, см. Примеры, связанные с is
, и is not
операторами). 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 Block также называется «пункт о завершении» как достижение пункта else
в операторе try
, означает, что блок Try фактически завершился успешно. def some_func ():
Ellipsis
Выход
> >> some_func ()
# No output, No Error
> >> SomeRandomString
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'SomeRandomString' is not defined
> >> Ellipsis
Ellipsis
Ellipsis
является глобально доступным встроенным объектом, который эквивалентен ...
> >> ...
Ellipsis
pass
) > >> import numpy as np
> >> three_dimensional_array = np . arange ( 8 ). reshape ( 2 , 2 , 2 )
array ([
[
[ 0 , 1 ],
[ 2 , 3 ]
],
[
[ 4 , 5 ],
[ 6 , 7 ]
]
])
three_dimensional_array
- это массив массива массивов. Допустим, мы хотим напечатать второй элемент (индекс 1
) из всех внутренних массивов, мы можем использовать Ellipsis, чтобы обойти все предыдущие размеры > >> three_dimensional_array [:,:, 1 ]
array ([[ 1 , 3 ],
[ 5 , 7 ]])
> >> three_dimensional_array [..., 1 ] # using Ellipsis.
array ([[ 1 , 3 ],
[ 5 , 7 ]])
n_dimensional_array[firs_dim_slice, ..., last_dim_slice]
)(Callable[..., int]
или Tuple[str, ...]
))Правописание предназначено. Пожалуйста, не отправляйте патч для этого.
Вывод (Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
-«-10⁵ x π» в Python 3, тогда как «-10⁵ XE» в Python 2.1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
Выход:
> >> Yo (). bro
True
> >> Yo (). __honey
AttributeError : 'Yo' object has no attribute '__honey'
> >> Yo (). _Yo__honey
True
2.
class Yo ( object ):
def __init__ ( self ):
# Let's try something symmetrical this time
self . __honey__ = True
self . bro = True
Выход:
> >> Yo (). bro
True
> >> Yo (). _Yo__honey__
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'Yo' object has no attribute '_Yo__honey__'
Почему Yo()._Yo__honey
работал?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
Выход:
> >> A (). __variable
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'A' object has no attribute '__variable'
> >> A (). some_func ()
'Some value'
__
(двойное подчеркивание, так и не было заканчивающимся, и не заканчивая более чем одним подчеркиванием, добавив _NameOfTheClass
впереди.__honey
в первом фрагменте, нам пришлось добавить _Yo
на фронт, что предотвратит конфликты с тем же атрибутом, определенным в любом другом классе.__variable
в операторе return __variable
было изуродовано для _A__variable
, что также является именем переменной, которую мы объявили во внешней области.Выход:
> >> value = 11
> >> valuе = 32
> >> value
11
WUT?
Примечание. Самый простой способ воспроизвести это - просто скопировать операторы из приведенного выше фрагмента и вставить их в свой файл/оболочку.
Некоторые незападные персонажи выглядят идентичны буквам в английском алфавите, но считаются различными интерпретатором.
> >> ord ( 'е' ) # cyrillic 'e' (Ye)
1077
> >> ord ( 'e' ) # latin 'e', as used in English and typed using standard keyboard
101
> >> 'е' == 'e'
False
> >> value = 42 # latin e
> >> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
> >> value
42
Функция встроенного ord()
возвращает точку кода Unicode симвода и различные кодовые позиции кириллического «E» и латинского «E» оправдывает поведение приведенного выше примера.
# `pip install numpy` first.
import numpy as np
def energy_send ( x ):
# Initializing a numpy array
np . array ([ float ( x )])
def energy_receive ():
# Return an empty numpy array
return np . empty ((), dtype = np . float ). tolist ()
Выход:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
Где Нобелевская премия?
energy_send
, не возвращается, так что пространство памяти свободно перераспределять.numpy.empty()
возвращает следующий слот свободной памяти без повторного его повторного определения. Это место памяти, оказалось, то же самое, которое было просто освобождено (обычно, но не всегда). def square ( x ):
"""
A simple function to calculate the 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):
TabError : inconsistent use of tabs and spaces in indentation
+=
быстрее # 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 += s2 + s3
) не разрушается при расчете полной строки. def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s += "xyz"
assert len ( s ) == 3 * iters
def add_bytes_with_plus ( iters ):
s = b""
for i in range ( iters ):
s += b"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
Выход:
# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
> >> NUM_ITERS = 1000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS )
124 µs ± 4.73 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS )
211 µs ± 10.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS )
61 µs ± 2.18 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS )
117 µs ± 3.21 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS )
10.1 µs ± 1.06 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
Давайте увеличим количество итераций в 10.
> >> NUM_ITERS = 10000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS ) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS ) # Quadratic increase
6.82 ms ± 134 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS ) # Linear increase
645 µs ± 24.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS ) # Linear increase
1.17 ms ± 7.25 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS ) # Linear increase
86.3 µs ± 2 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
Вы можете прочитать больше о Timeit или %Timeit по этим ссылкам. Они используются для измерения времени выполнения деталей кода.
Не используйте +
для создания длинных строк - в Python str
неизменен, поэтому левые и правые строки должны быть скопированы в новую строку для каждой пары конкатенаций. Если вы объедините четыре строки длины 10, вы будете копировать (10+10)+((10+10) +10)+(((10+10) +10) +10) = 90 символов вместо 40 персонажи. Вещи становятся в четыре раза хуже, поскольку число и размер строки увеличиваются (оправдано временем выполнения функции add_bytes_with_plus
)
Поэтому рекомендуется использовать .format.
или %
синтаксиса (однако, они немного медленнее, чем +
для очень коротких строк).
Или, что лучше, если вы уже доступны в форме итерабируемого объекта, то используйте ''.join(iterable_object)
что намного быстрее.
В отличие от add_bytes_with_plus
из -за +=
оптимизации, обсуждаемых в предыдущем примере, add_string_with_plus
не показал квадратичного увеличения времени выполнения. Если бы утверждение было s = s + "x" + "y" + "z"
вместо s += "xyz"
, увеличение было бы квадратичным.
def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s = s + "x" + "y" + "z"
assert len ( s ) == 3 * iters
> >> % timeit - n100 add_string_with_plus ( 1000 )
388 µs ± 22.4 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n100 add_string_with_plus ( 10000 ) # Quadratic increase in execution time
9 ms ± 298 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
Так много способов форматирования и создания гигантской струны несколько в отличие от дзена питона, согласно которому,
Должен быть один-и предпочтительно только один-очевидный способ сделать это.
dict
* some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
Выход:
> >> % timeit some_dict [ '5' ]
28.6 ns ± 0.115 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> some_dict [ 1 ] = 1
> >> % timeit some_dict [ '5' ]
37.2 ns ± 0.265 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> % timeit another_dict [ '5' ]
28.5 ns ± 0.142 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> another_dict [ 1 ] # Trying to access a key that doesn't exist
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
KeyError : 1
> >> % timeit another_dict [ '5' ]
38.5 ns ± 0.0913 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
Почему те же поиск становится медленнее?
str
, int
, любой объект ...), и специализированный для общего случая словарей, состоящих из ключей только str
.lookdict_unicode
в источнике CPYTHON) знает, что все существующие ключи (включая ключ для поиска) являются строками и используют более быстрое и более простое сравнение строк для сравнения клавиш, вместо того, чтобы вызывать метод __eq__
.dict
доступен с ключом без str
, он модифицирован, поэтому в будущих поисках используется общая функция.dict
, и ключ даже не должен существовать в словаре. Вот почему попытка неудачного поиска имеет тот же эффект.dict
S * import sys
class SomeClass :
def __init__ ( self ):
self . some_attr1 = 1
self . some_attr2 = 2
self . some_attr3 = 3
self . some_attr4 = 4
def dict_size ( o ):
return sys . getsizeof ( o . __dict__ )
Вывод: (Python 3.8, другие версии Python 3 могут немного различаться)
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104
> >> dict_size ( o2 )
104
> >> del o1 . some_attr1
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
> >> dict_size ( o1 )
232
Попробуем еще раз ... в новом переводчике:
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104 # as expected
> >> o1 . some_attr5 = 5
> >> o1 . some_attr6 = 6
> >> dict_size ( o1 )
360
> >> dict_size ( o2 )
272
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
Что заставляет эти словаря стать раздутыми? И почему недавно созданные объекты также раздуты?
__init__
самого первого созданного экземпляра, не вызывая «Не смущается»). Если существует несколько экземпляров, когда происходит изменение размера, обмен ключами отключено для всех будущих экземпляров одного и того же класса: CPYTHON не может сказать, больше ли ваши экземпляры используют один и тот же набор атрибутов, и решает выручить попытку поделиться своими Ключи.__init__
! join()
- это операция строки вместо операции списка. (вроде нелогично при первом использовании)
Объяснение: Если join()
- это метод на строке, то он может работать на любом итерационном (список, кортеж, итераторы). Если бы это был метод в списке, он должен был быть реализован отдельно по любому типу. Кроме того, не имеет большого смысла размещать специфический метод, специфичный для строк в общий API объекта list
.
Несколько странных, но семантически правильных утверждений:
[] = ()
- семантически правильное утверждение (распаковка пустого tuple
в пустой list
)'a'[0][0][0][0][0]
также семантически правильный, потому что у Python нет типа данных символов, как на других языках, разветвленных из C., поэтому выбирая один символ из строки возвращает однохарактерная строка.3 --0-- 5 == 8
и --5 == 5
-это семантически правильные утверждения и оценивают True
. Учитывая, что a
-это число, ++a
и --a
-обоих действительных операторов Python, но не ведут себя так же, как по сравнению с аналогичными утверждениями на таких языках, как C, C ++ или Java.
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
Объяснение:
++
. На самом деле это два +
операторы.++a
Подборки как +(+a)
что переводится на a
. Аналогичным образом, вывод оператора --a
может быть оправдан.Вы должны знать о операторе моржа в Python. Но слышали ли вы когда-нибудь об операторе Space Invader ?
> >> a = 42
> >> a -= - 1
> >> a
43
Он используется в качестве альтернативного оператора инкрементации, вместе с другим
> >> a += + 1
> >> a
> >> 44
Объяснение: Эта шутка поступает из твита Рэймонда Хеттингера. Оператор Space Invader на самом деле является просто необработанным a -= (-1)
. Который эквивалентен a = a - (- 1)
. Аналогично для случая a += (+ 1)
.
Python имеет незарегистрированного оператора для подношения обратного действия.
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
Объяснение: Если вы замените False
и True
на 0 и 1 и выполняете математику, таблица истинности эквивалентна оператору с обратным значением. (Источник)
Поскольку мы говорим о операторах, есть также оператор @
для умножения матрицы (не волнуйтесь, на этот раз это на самом деле).
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
Объяснение: Оператор @
был добавлен в Python 3.5, учитывая научное сообщество. Любой объект может перегружать магический метод __matmul__
, чтобы определить поведение для этого оператора.
Начиная с Python 3.8 вы можете использовать типичный синтаксис F-String, такой как f'{some_var=}
для быстрого отладки. Пример,
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
Python использует 2 байта для локального хранилища переменной в функциях. Теоретически это означает, что только 65536 переменных могут быть определены в функции. Тем не менее, Python имеет удобное решение, встроенное, которое можно использовать для хранения более 2^16 имен переменных. Следующий код демонстрирует, что происходит в стеке, когда определяется более 65536 локальных переменных (предупреждение: этот код печатает около 2^18 строк текста, так что будьте готовы!):
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
Несколько потоков Python не запускают ваш код Python одновременно (да, вы слышали это правильно!). Это может показаться интуитивно понятным, чтобы породить несколько потоков и позволить им выполнить ваш код Python одновременно, но из -за глобальной блокировки интерпретатора в Python все, что вы делаете, заставляют ваши потоки выполнять один и тот же поворот ядра на хода. Потоки Python хороши для IO-связанных задач, но для достижения фактической параллелизации в Python для задач, связанных с процессором, вы можете использовать многопроцессорный модуль Python.
Иногда метод print
может не печатать значения сразу. Например,
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
Это будет распечатать wtfpython
через 3 секунды из -за end
аргумента, потому что выходной буфер промывается либо после встречи n
, либо когда программа завершает выполнение. Мы можем заставить буфер промыть путем прохождения flush=True
Arcome.
Список нарезания из -за границ индексов не бросает ошибки
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
Разрешение итерабильной не всегда создает новый объект. Например,
> >> some_str = "wtfpython"
> >> some_list = [ 'w' , 't' , 'f' , 'p' , 'y' , 't' , 'h' , 'o' , 'n' ]
> >> some_list is some_list [:] # False expected because a new object is created.
False
> >> some_str is some_str [:] # True because strings are immutable, so making a new object is of not much use.
True
int('١٢٣٤٥٦٧٨٩')
возвращает 123456789
в Python 3. В Python десятичные символы включают символы цифр и все символы, которые можно использовать для формирования десятичных номеров, например, U+0660, арабской цифровой цифр. Вот интересная история, связанная с этим поведением Python.
Вы можете отделить числовые литералы с подчеркиванием (для лучшей читаемости) от Python 3 и далее.
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
. Вот приблизительная реализация метода count
, который прояснит вещи более ясными
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
Поведение связано с сопоставлением пустой подстроки ( ''
) с ломтиками длины 0 в исходной строке.
Несколько способов, которыми вы можете внести свой вклад в Wtfpython,
Пожалуйста, смотрите Anforming.md для получения более подробной информации. Не стесняйтесь создавать новую проблему, чтобы обсудить вещи.
PS: Пожалуйста, не обращайтесь к запросам обратной связи, ссылки не будут добавлены, если они не имеют значительного отношения к проекту.
Идея и дизайн для этой коллекции были изначально вдохновлены потрясающим проектом Denys Dovhan WTFJS. Подавляющая поддержка Pythonistas придала ему форму, в которой он сейчас находится.
© Satwik Kansal
If you like wtfpython, you can use these quick links to share it with your friends,
Твиттер | Linkedin | Фейсбук
I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.
That's all folks! For upcoming content like this, you can add your email here.