MyTetra Share
Делитесь знаниями!
Странности языка Python, которые могут вас укусить
14.01.2018
19:34
Раздел: Python

Странности языка Python, которые могут вас укусить

ОтMontgomeri

-

23.12.2017

0

7467

У языка Python, как и у любого другого, есть совершенно непонятные моменты, которые неплохо бы разобрать «по кирпичикам». Этим и займемся.

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

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

  1. Проверьте, совпадает ли он с тем, что вы ожидали.
  2. Попробуйте подумать, что именно стало причиной такого результата?
  • Если не смогли, прочтите объяснение.
  • Если смогли, скажите себе, что вы молодец, и переходите следующему примеру.

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

$ npm install -g wtfpython


1

$ npm install -g wtfpython

Теперь просто запустите wtfpython в командной строке, которая откроет эту коллекцию в выбранном $PAGER.

Примеры странностей языка Python

Ну, что-то сомнительное…

def square(x): """ Простая функция для вычисления квадрата числа путем сложения. """ sum_so_far = 0 for counter in range(x): sum_so_far = sum_so_far + x return sum_so_far


1

2

3

4

5

6

7

8

def square(x):

    """

    Простая функция для вычисления квадрата числа путем сложения.

    """

    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


1

2

>>> square(10)

10

Для языка Python нет невозможного, но… Разве здесь не должно быть 100?

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

Объяснение:

  • Не смешивайте табы и отступы! Символ, предшествующий return является «табом», а в другом месте код имеет отступ в «4 пробела».
  • Так Python обрабатывает табы: табы заменяются на один-восемь пробелов.
  • Таким образом, «таб» в последней строке заменяется на восемь пробелов, что создает цикл.
  • Python 3 достаточно хорош, чтобы автоматически выдавать ошибку для таких случаев.

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

TabError: inconsistent use of tabs and spaces in indentation


1

TabError: inconsistent use of tabs and spaces in indentation

Время для некоторых «хашбраунов»!

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


1

2

3

4

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"


1

2

3

4

5

6

>>> some_dict[5.5]

"Ruby"

>>> some_dict[5.0]

"Python"

>>> some_dict[5]

"Python"

Дискриминация со стороны языка Python? «Python» уничтожил «JavaScript»?

Объяснение:

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

>>> hash(5) == hash(5.0) True


1

2

>>> hash(5) == hash(5.0)

True

Этот ответ из StackOverflow прекрасно объясняет причины.

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

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


1

2

3

4

5

6

x = {0: None}

 

for i in x:

    del x[i]

    x[i+1] = None

    print(i)

Выход:

0 1 2 3 4 5 6 7


1

2

3

4

5

6

7

8

0

1

2

3

4

5

6

7

Объяснение:

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

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

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)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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]


1

2

3

4

5

6

7

8

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


1

2

3

4

5

>>> 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 для языка Python:

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

Почему на выходе [2, 4]?

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

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

Выход:

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


1

2

3

4

5

6

7

8

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

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

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

 

    File "<stdin>", line 1

      print(r"\ some string \")

                             ^

SyntaxError: EOL while scanning string literal

Объяснение:

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

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

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

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


1

2

3

4

5

6

7

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


1

2

3

4

5

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


1

2

3

4

5

6

try:

    pass

except:

    print("Exception occurred!!!")

else:

    print("Try block executed successfully...")

Выход:

Try block executed successfully...


1

Try block executed successfully...

Объяснение:

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

is is not what it is!

Ниже представлен известный во всем Интернете пример:

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


1

2

3

4

5

6

7

8

9

10

11

12

13

>>> 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 используется, если оба операнда относятся к одному и тому же объекту (то есть он проверяет соответствие совпадений операндов).
  • == сравнивает значения двух операндов.

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

>>> [] == [] True >>> [] is [] # Это два пустых списка в двух разных ячейках памяти. False


1

2

3

4

>>> [] == []

True

>>> [] is [] # Это два пустых списка в двух разных ячейках памяти.

False

is not … это не is (not …)

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


1

2

3

4

>>> 'something' is not None

True

>>> 'something' is (not None)

False

Объяснение:

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

Функция внутри цикла и результат

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


1

2

3

4

5

6

7

8

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]

Выход:

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


1

2

3

4

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


1

2

3

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

Объяснение:

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

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


1

2

3

4

5

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]


1

2

3

>>> funcs_results = [func() for func in funcs]

>>> funcs_results

[0, 1, 2, 3, 4, 5, 6]

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

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


1

2

3

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


1

2

3

4

5

6

7

8

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


1

2

3

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'],)


1

2

3

4

5

6

7

8

9

10

11

>>> 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 not default_arg: default_arg = [] default_arg.append("some_string") return default_arg


1

2

3

4

5

def some_func(default_arg=None):

    if not default_arg:

        default_arg = []

    default_arg.append("some_string")

    return default_arg

Одинаковые операнды, разные истории!

1.

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


1

2

3

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]


1

2

3

4

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


1

2

3

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]


1

2

3

4

>>> a

[1, 2, 3, 4, 5, 6, 7, 8]

>>> b

[1, 2, 3, 4, 5, 6, 7, 8]

Объяснение:

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

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

a = 1 def some_func(): return a def another_func(): a += 1 return a


1

2

3

4

5

6

7

a = 1

def some_func():

    return a

 

def another_func():

    a += 1

    return a

Выход:

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


1

2

3

4

>>> some_func()

1

>>> another_func()

UnboundLocalError: local variable 'a' referenced before assignment

Объяснение:

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

def another_func() global a a += 1 return a


1

2

3

4

def another_func()

    global a

    a += 1

    return a

Выход:

>>> another_func() 2


1

2

>>> another_func()

2

Return return everywhere!

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


1

2

3

4

5

def some_func():

    try:

        return 'from_try'

    finally:

        return 'from_finally'

Выход:

>>> some_func() 'from_finally'


1

2

>>> some_func()

'from_finally'

Объяснение:

  • Когда в try-блоке «try … finally» выполняется оператор return, break или continue, finally также выполняется «на выходе».
  • Возвращаемое значение функции определяется последним выполненным оператором return. Поскольку finally выполняется всегда, оператор return, выполняемый в finally, всегда будет последним.
Так же в этом разделе:
 
MyTetra Share v.0.52
Яндекс индекс цитирования