10 причуд Python, о которых вы должны знать в своем коде

10 причуд Python Программирование и разработка

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

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

Примечание: в этой статье мы будем иметь в виду только причуды, относящиеся к Python 3, поскольку Python 2 устарел в январе 2020 года.

Переменные, пространство имен и область действия

Когда речь идет о Python изнутри, нам нужно поговорить о двух вещах: пространство имен и область видимости.

Пространство имен

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

function_namespace = { name_of_obj_a: obj_1, name_of_obj_b: obj_2 }
for_loop_namespace = { name_of_obj_a: obj_3, name_of_obj_b: obj_4 }

Мы можем рассматривать пространства имен как просто словари Python, где имя переменной для объекта является ключом, а значение — самим объектом. Мы создаем новое независимое пространство имен каждый раз, когда определяем цикл, функцию или класс. Каждое пространство имен имеет свою собственную иерархию, называемую областью действия.

Сфера

Область видимости на очень высоком уровне — это иерархия, в которой интерпретатор Python может «видеть» определенный объект. Интерпретатор начинает с наименьшей области видимости, локальной, и смотрит наружу, если не может найти объявленную переменную в закрытой области. Если интерпретатор не может найти его в закрытой области видимости, он обращается к глобальной области видимости.

Читайте также:  Vim или Emacs

Возьмем этот пример:

i = 1
def foo():
   i = 5
   print(i, ‘in foo()’)
   print(«local foo() namespace», locals())
   return i
print(«global namespace», globals())
foo()

Здесь у нас есть globalпространство имен, и у нас есть foo()пространство имен. Вы можете взглянуть на отдельные пространства имен, распечатав globals()и распечатав locals()в заданных местах кода.

Локальное пространство имен довольно простое. Хорошо видно iи его значение. Глобальное пространство имен немного отличается тем, что оно также включает некоторые посторонние вещи из Python.

Здесь функция foo отображается как место в памяти, а не как фактическое значение функции, а также значение for iв globalпространстве имен.

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

i = 1
def foo():
   global i
   i = 5
   print(i, ‘in foo()’)
   print(«local namespace», locals())
   return i
print(«global i before func invocation», globals()[«i»])
foo()
print(«global i after func invocation», globals()[«i»])

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

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

del ключевое слово

delКлючевое слово только удаляет экземпляр этого элемента в локальном пространстве имен, но не фактический элемент сам по себе в глобальном пространстве имен. Таким образом, глобально определенное list_1не затронуто.

list_1 = [«apples», «oranges», «bananas», «strawberries»]
for item in list_1:
   del item
print(«list_1: «,list_1); # [‘apples’, ‘oranges’, ‘bananas’, ‘strawberries’]

remove() метод

В этом remove()методе, как только Python удаляет элемент из списка, все остальные элементы сдвигаются влево один раз, но итерация не происходит до тех пор, пока все не будет перемещено.

list_2 = [«apples», «oranges», «bananas», «strawberries»]
for item in list_2:
   list_2.remove(item)
print(«list_2: «,list_2)# [‘oranges’, ‘strawberries’]

Вот пошаговое описание того, как это происходит:

  1. Первая итерация: удалить apples. orangesперемещается влево и теперь является текущим индексом. bananasперемещается влево и становится следующим индексом. strawberriesфильмы слева, и цикл переходит к следующему индексу.
  2. Вторая итерация: bananas находится в текущем индексе, поэтому метод удаляется bananas. strawberriesперемещается влево и теперь является текущим индексом. Значений индекса больше нет, поэтому итерация выполнена.
  3. Результат: останется orangesи strawberriesв списке.

pop(idx) метод

По той же причине, по которой мы не используем метод remove при просмотре списка, мы не используем этот pop(idx)метод. Если индекс не передается в качестве аргумента, Python удаляет последний индекс в списке.

list_3 = [«apples», «oranges», «bananas», «strawberries»]
for item in list_3:
   list_3.pop()
print(«list_3: «,list_3) # [‘apples’, ‘oranges’]
  • Первая итерация: удалить strawberries, поэтому длина списка теперь равна трем. Перейти к следующей итерации.
  • Вторая итерация: удалить bananas, поэтому длина списка теперь равна двум. Значений индекса больше нет, итерация выполнена.
  • Результат: останется applesи orangesв списке.

Примечание. Если в pop()метод передан индекс, но он не существует, он вызовет расширение IndexError.

Итак, что работает?

Секрет перебора и манипулирования списком в Python заключается в разрезании или создании копии списка. Это так же просто, как использовать [:]:.

list_4 = [«apples», «oranges», «bananas», «strawberries»]
for item in list_4[:]:
      list_4.remove()  #pop() would also work here.
print(«list_4: «,list_4) # []

list_4[:]Этот оператор делает копию списка в памяти. Исходный список не изменяется, когда мы просматриваем его, но влияет на исходный, когда все готово.

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

Словари Python могут быть сложными объектами для работы. Тем не менее, одно можно сказать с полной уверенностью: эти словари вообще не могут быть изменены, когда они зацикливаются.

В зависимости от имеющейся у вас версии Python вы либо получите ошибку времени выполнения, либо цикл будет выполняться определенное количество раз (от 4 до 8), пока не потребуется изменить размер словаря.

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

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

Разрешение имени без учета области класса

По словам создателя Python Гвидо ван Россума, в Python 2 были некоторые «грязные маленькие секреты», которые допускали определенные утечки. Одна из этих утечек позволила переменной управления циклом изменить значение a в понимании списка.

Это было исправлено в Python 3, предоставив представлениям списков собственную ограничивающую область. Когда понимание списка не находит определения для aв охватывающей области видимости, оно обращается к глобальной области, чтобы найти значение. Вот почему Python 3 игнорирует a = 17область видимости класса.

a = 5
class Example:
   # global a
   a = 17
   b = [a for i in range(20)]
print(Example.y[0])

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

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

def num_list(nums=[]):
   num = 1
   nums.append(num)
   return nums
print(num_list())
print(num_list())
print(num_list([]))
print(num_list())
print(num_list([4]))

Первые два раза num_list()вызываются, оба раза 1к numsсписку будет добавлен a. Результат есть [1, 1]. Чтобы сбросить список, вы должны передать пустой список следующему вызову.

Same operands, different story

Переназначения в Python могут быть сложными, если вы не знаете, как они работают. Операторы =и +=имеют два разных значения в Python при использовании в сочетании со списками.

# reassignment
a = [1, 2, 3, 4]
b = a
a = a + [5, 6, 7, 8]
print(a)
print(b)
# extends
a = [1, 2, 3, 4]
b = a
a += [5, 6, 7, 8]
print(a)
print(b)

При манипулировании списками =оператор просто означает переназначение. Когда bназначен как a, он создает копию того, aчто было в то время. При aпереназначении для a + [5, 6, 7, 8], он объединяет оригинал aс [5, 6, 7, 8]to create [1, 2, 3, 4, 5, 6, 7, 8]. bСписок остается неизменным от его первоначального назначения.

С +=оператором, когда он относится к спискам, это ярлык для extends()метода. Это приводит к тому, что список меняется на месте, давая нам [1, 2, 3, 4, 5, 6, 7, 8]как aи b.

Что не так с логическими значениями?

Когда дело доходит до логических значений, это кажется довольно простым. Сколько у нас логических значений и сколько целых значений в этом смешанном массиве?

mixed_type_list = [False, 4.55, «educative.io», 3, True, [], False, dict()]
integers_count = 0
booleans_count = 0
for item in mixed_type_list:
   if isinstance(item, int):
       integers_count += 1
   elif isinstance(item, bool):
       booleans_count += 1
print(integers_count)
print(booleans_count)

Почему на выходе 4-0? Короче говоря, логическое значение в Python является подклассом целых чисел. Истина в Python приравнивается к 1, а ложь равняется 0.

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

В объектно-ориентированном Python класс — это шаблон, а экземпляр — это новый объект, основанный на этом шаблоне. Что бы произошло, если бы мы попытались изменить или смешать присвоения переменных класса и переменных экземпляра?

class Animal:
   x = «tiger»
class Vertebrate(Animal):
   pass
class Cat(Animal):
   pass
print(Animal.x, Vertebrate.x, Cat.x)
Vertebrate.x = «monkey»
print(Animal.x, Vertebrate.x, Cat.x)
Animal.x = «lion»
print(Animal.x, Vertebrate.x, Cat.x)
a = Animal()
print(a.x, Animal.x)
a.x += «ess»
print(a.x, Animal.x)

Здесь мы имеем три класса: Animal, Vertebrateи Cat. Когда мы назначаем переменную в классе Animal, а другие классы являются расширениями класса Animal, эти другие классы имеют доступ к переменной, созданной в классе Animal.

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

split() метод

Этот split()метод имеет некоторые уникальные свойства в Python. Взгляните на этот пример:

print(‘         foo ‘.split(» «)) # [», », », », », », », », », ‘foo’, »]
print(‘ foo        bar   ‘.split()) # [‘foo’, ‘bar’]
print(».split(‘ ‘)) #[»]

Когда мы даем методу разделения разделитель, в данном случае ( » «), и используем его в строке любой длины, он будет разделен на пробелы. Независимо от того, сколько пробелов у вас в строке, он будет разделен на каждый из них.

Если разделитель не указан, интерпретатор Python сожмет все повторяющиеся пробельные символы в один и разделит этот символ, оставив только группы непробельных символов разделенными.

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

Wild импорт

Импорт подстановочных знаков может быть полезен, если вы знаете, как их использовать. У них есть некоторые особенности, которые могут сбивать их с толку чаще, чем нет. Возьмем этот пример:

def hello_world(str):
   return str;
def _hello_world(str):
   return str

 

from helpers import *
hello_world(«hello world — WORKS!»)
_hello_world(«_hello_world — WORKS!»)

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

Для этих методов вам придется либо напрямую импортировать функцию, либо использовать __all__список, чтобы использовать импорт с подстановочными знаками.

__all__ = [hello_world, _private_hello_world]
def hello_world(str):
   return str;
def _private_hello_world(str):
   return str

Примечание:__all__ переменная окружена двумя символами подчеркивания с обеих сторон.

Оцените статью
bestprogrammer.ru
Добавить комментарий