Python: малоизвестные функции (часть 1)

Python

На GitHub есть проект под названием «Что за х*й Python!», этот интересный проект направлен на то, чтобы собрать эти непонятные и контринтуитивные примеры и малоизвестные функции в Python, и попытаться обсудить причины этих явлений. ! Оригинальный адрес:GitHub.com/Sat Проверьте SA для меня…

Недавно он был переведен на китайский автором по имени «Му Чен». Адрес китайской версии:GitHub.com/leasure стоит…

Первоначально каждое название было на английском языке в оригинальной версии. Некоторые названия были странными и неинтуитивными. Я изменил их на китайскую форму, которая может описать тему. Некоторые из них являются моими собственными мыслями. Пожалуйста, поправьте меня. ДругиеPythonЯ удалил пасхальные яйца в файле .

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

Опубликована следующая статья:Python: малоизвестные функции (часть 2)

1. Резиденция строки

>>> a = '!'
>>> b = '!'
>>> a is b
True

>>> a = 'some_string'
>>> id(a)
140420665652016
>>> id('some' + '_' + 'string') # 注意两个的id值是相同的.
140420665652016

>>> a = 'wtf'
>>> b = 'wtf'
>>> a is b
True

>>> a = 'wtf!'
>>> b = 'wtf!'
>>> a is b
False

>>> a, b = 'wtf!', 'wtf!'
>>> a is b
True

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False

проиллюстрировать:Эти действия обусловленыCPythonПри компиляции и оптимизации в некоторых случаях вы будете пытаться создать новый объект вместо того, чтобы каждый раз создавать новый объект. Такое поведение называется резиденцией строкиstring interning. После того, как происходит резидентность, многие переменные могут указывать на один и тот же строковый объект в памяти для экономии памяти.

Есть несколько методов, которые можно использовать для определения того, будет ли строка резидентной:

  • Все строки длины 0 и длины 1 являются резидентными (строки в ① являются резидентными)
  • Строки реализуются во время компиляции ('wtf'будет резидентом, но''.join(['w', 't', 'f']Не будет резидентом)
  • будет находиться, когда строка содержит только буквы, цифры или символы подчеркивания, поэтому'wtf!'из-за включения'!'не остаться
  • когда на той же линии будетaа такжеbЗначение установлено на'wtf!'Переводчик Python создает новый объект, и оба переменных указывают на этот объект одновременно. Если вы выполните назначение на другую строку, это не будет «знать», что уже есть один'wtf!'предмет (потому что'wtf!'не является неявным резидентом, как указано выше).
  • постоянное складывание (constant folding)ДаPythonОптимизация глазка в (peephole optimization)Технология. Это означает, что во время компиляции выражение'a' * 20будет заменен на'aaaaaaaaaaaaaaaaaaaa'для уменьшения тактов времени выполнения. Константное свертывание происходит только для строк длиной менее 20. (Почему? Представьте, что благодаря выражению'a' * 10 ** 10и размер результирующего файла .pyc).

Если вы попробуете этот пример в файле .py, вы не увидите того же поведения, потому что файл компилируется за один раз.

2. Ключи словаря

>>> 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Словарные проверки на равенство ключей определяются путем сравнения хэшей на равенство. Если два объекта равны при сравнении, то их хеш-значения должны быть равны, иначе хеш-таблица не будет работать должным образом. Например, если1 == 1.0правда, тоhash(1) == hash(1.0)Тоже должно быть верно, но внутренняя структура двух чисел (целое и число с плавающей запятой) совершенно разная.

3. return в предложении finally

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

Output:

>>> some_func()
'from_finally'

проиллюстрировать:Возвращаемое значение функции определяется последним выполненнымreturnРешение приговора. из-заfinallyпункт должен быть выполнен, поэтомуfinallyв пунктеreturnвсегда будет последним выполненным оператором.

4. Тот же объект

class WTF:
    pass

Output:

>>> WTF() == WTF() # 两个不同的对象应该不相等
False
>>> WTF() is WTF() # 也不相同
False
>>> hash(WTF()) == hash(WTF()) # 哈希值也应该不同
True
>>> id(WTF()) == id(WTF())
True

проиллюстрировать:при звонкеid()функция,PythonсоздалWTFобъект класса и передаетсяid()функция, тоid()Функция получает значение своего идентификатора (то есть адрес памяти), затем отбрасывает объект, и объект уничтожается.

Когда мы делаем это дважды подряд, Python присваивает один и тот же адрес памяти второму объекту, потому что вCPythonсерединаid()Функция использует адрес памяти объекта в качестве значения идентификатора объекта, поэтому значение идентификатора обоих объектов одинаково.

Подводя итог, можно сказать, что значение идентификатора объекта уникально только в течение жизненного цикла объекта.После уничтожения объекта или до его создания другие объекты могут иметь такое же значение идентификатора.

class WTF(object):
  def __init__(self): print("I")
  def __del__(self): print("D")

Output:

>>> WTF() is WTF()
I
I
D
D
False
>>> id(WTF()) == id(WTF())
I
D
I
D
True

Как видите, порядок, в котором уничтожаются объекты, имеет значение.

5. для назначения петли целевое назначение

>>> some_string = "wtf"
>>> some_dict = {}
>>> for i, some_dict[i] in enumerate(some_string): pass
>>> some_dict
{0: 'w', 1: 't', 2: 'f'}

проиллюстрировать:Это легко понять, если внимательно присмотреться.forКаждая итерация цикла присваивает значение цели присваивания,some_dict[i] = valueЭто эквивалентно добавлению пар ключ-значение в словарь. Интересен приведенный ниже пример. Вы когда-нибудь думали, что цикл будет выполняться только один раз?

for i in range(4):
    print(i)
    i = 10

6. Разница во времени выполнения

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

>>> array_1 = [1, 2, 3, 4]
>>> g1 = (x for x in array_1)
>>> array_1 = [1, 2, 3, 4, 5]

>>> array_2 = [1, 2, 3, 4]
>>> g2 = (x for x in array_2)
>>> array_2[:] = [1, 2, 3, 4, 5]

>>> list(g1)
[1, 2, 3, 4]

>>> list(g2)
[1, 2, 3, 4, 5]

проиллюстрировать:в выражении генератораinПредложения выполняются во время объявления, а условные предложения выполняются во время выполнения. ① в, перед запускомarrayбыл переведен на[2, 8, 22], поэтому для предыдущих 1, 8, 15 толькоcount(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].

7. Предварительно выделенное целое число

>>> 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означает ту же ссылку,==Репрезентативное значение равно. Следующий пример может быть хорошо описан:

>>> [] == []
True
>>> [] is []  # 这两个空列表位于不同的内存地址
False

256 — это существующий объект, а 257 — нет.

при запускеPython, значения от -5 до 256 уже присвоены. Эти номера подходят для подготовки заранее, потому что они часто используются.

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

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

>>> 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

>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296

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

8. Непреднамеренное присвоение ссылочных типов

>>> row = [''] * 3
>>> board = [row] * 3
>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

проиллюстрировать:Выведем id и посмотрим:

>>> id(row[0])
7536232
>>> id(row[1])
5143216
>>> id(row[2])
5143216
>>> id(board[0])
7416840
>>> id(board[1])
7416840
>>> id(board[2])
7416840

rowпредставляет собой список со всеми тремя элементами, указывающими на адрес 5143216, когдаboard[0][0]После назначения,row的第一个元素指向 7536232。 а такжеboardВсе три элемента вrow,rowАдрес не изменился.

Мы можем сделать это, не используя переменныеrowгенерироватьboardчтобы избежать этого.

>>> board = [[''] * 3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]

Здесь используется вывод, и каждая итерация будет генерировать новый_,такboardТри элемента указывают на разные переменные.

9. Функция закрытия

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]

Output:

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

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

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

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

Output:

>>> funcs_results = [func() for func in funcs]
>>> funcs_results
[0, 1, 2, 3, 4, 5, 6]

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

>>> print("\\ C:\\")
\ C:\
>>> print(r"\ C:")
\ C:
>>> print(r"\ C:\")

    File "<stdin>", line 1
      print(r"\ C:\")
                     ^
SyntaxError: EOL while scanning string literal

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

11. Приоритет операторов == и not

>>> not x == y
True
>>> x == not y
  File "<input>", line 1
    x == not y
           ^
SyntaxError: invalid syntax

проиллюстрировать:Одним словом,==оператор имеет более высокий приоритет, чемnotоператор.

12. Тройные кавычки

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # 下面的语句会抛出 `SyntaxError` 异常
>>> # print('''wtfpython')
>>> # print("""wtfpython")

проиллюстрировать: '''а также"""существуетPythonтакже является разделителем строк,PythonКогда интерпретатор сначала встретит три кавычки, он попытается найти три завершающих кавычки в качестве разделителей, и если они не существуют, это приведет кSyntaxErrorаномальный.

В то время как Python обеспечивает неявную цепочку строк:

>>> print("wtf" "python")
wtfpython
>>> print("wtf""")  # 相当于 "wtf" ""
wtf

13. Исчезающая полночь

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)

Output:

Time at noon is 12:00:00

midnight_timeне выводится.

проиллюстрировать:существуетPython 3.5раньше, еслиdatetime.timeОбъект сохраняется в полночь UTC, тогда его логическое значение будет считаться False. я сделал это специальноpython 3.4Подтвердите, это правда.

14. логическое значение

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

Output:

>>> booleans_found_so_far
0
>>> integers_found_so_far
4

проиллюстрировать:логическое значениеintподкласс

>>> isinstance(True, int)
True
>>> isinstance(False, int)
True

В фактическом введенииboolДо типов 0 и 1 были официальным представлением значений истинности. Для обратной совместимости новыйboolТипы должны работать как 0 и 1.

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

class A:
    x = 1

class B(A):
    pass

class C(A):
    pass

Output:

>>> 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)

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]

Output:

>>> 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

проиллюстрировать:

  • Переменные класса и переменные экземпляра обрабатываются внутри через словарь объектов класса (__dict__атрибут), если он не найден в словаре текущего класса, он будет искать в своем родительском классе.
  • +=Операторы изменяют изменяемые объекты на месте, а не создают новые. Следовательно, изменение свойства одного экземпляра влияет на другие свойства экземпляра и класса.

16. Ошибка доходности

some_iterable = ('a', 'b')

def some_func(val):
    return "something"

Output:

>>> [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']

проиллюстрировать:ЭтоCPythonОбработка вложений и выражений генератораyieldошибка вPython 3.8ремонт вPython 3.7Есть предупреждения об устаревании. видетьPythonотчеты об ошибках иPython 3.7а такжеPython 3.8новая запись.

Источник и объяснение можно найти здесь:stackoverflow.com/questions/3…Связанные отчеты об ошибках:bugs.python.org/issue10544

17. Относительная неизменность кортежей

>>> 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) # 这里不出现错误
>>> 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])

проиллюстрировать:Идентификатор неизменяемого элемента в кортеже (то есть адрес элемента). Если элемент является ссылочным типом, значение кортежа будет меняться при изменении ссылочного изменяемого объекта. такanother_tuple[2].append(1000)разрешено.+=Оператор изменяет список на месте. Назначение элемента не работает, но при возникновении исключения элемент был изменен на месте.+=не атомарные операции, аextendа также=два действия, здесь=Хотя операция выдает исключение,extendОперация успешно изменена.

18. Исчезающие внешние переменные

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

Output: python2

>>> print(e)
# prints nothing

Output: python3

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

проиллюстрировать:когда используешьasПри назначении исключения цели оно будет вexceptИсключение очищается в конце предложения.

Это как:

except E as N:
    foo

будет переведено в:

except E as N:
    try:
        foo
    finally:
        del N

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

оговорка вPythonВ . Все в примере находится в одной области видимости, поэтому переменнаяeбудет осуществляться за счетexceptпункт удален. Ситуация отличается для функций с отдельными внутренними областями. Следующий пример иллюстрирует это:

def f(x):
    del(x)
    print(x)

x = 5
y = [5, 4, 3]

Output:

>>>f(x)
UnboundLocalError: local variable 'x' referenced before assignment
>>>f(y)
UnboundLocalError: local variable 'x' referenced before assignment
>>> x
5
>>> y
[5, 4, 3]

19. тип бул

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

Output:

I've lost faith in truth!

проиллюстрировать:исходный,PythonнисколькоboolТип (люди используют 0 для ложных значений и ненулевые значения, такие как 1, для истинных значений). Позже они добавилиTrue, False, а такжеboolтип, однако для обратной совместимости они не могутTrueа такжеFalseУстановите константу, просто установите встроенную переменную.Python 3Поскольку обратная совместимость больше не требуется, это наконец-то можно исправить, поэтому этот пример нельзя использовать вPython 3.xв исполнении.

20. Ловушка метода append

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})

Output:

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

проиллюстрировать:Большинство методов, которые изменяют объекты последовательности/карты, напримерlist.append,dict.update,list.sortИ так далее, все модифицируем объект на месте и возвращаемNone, что повышает производительность за счет предотвращения создания копии объекта.


扫码关注我的个人公众号