Метка: IndexError

Python: все про del

КДПВ

Инструкция del (от англ. delete), как можно понять из названия, нужна чтобы что-то удалять, а именно имена переменных, атрибуты объектов, элементы списков и ключи словарей.

1. Удаление элемента из списка по индексу:

>>> x = [1, 2, 3, 4, 5]
>>> del x[2]
>>> x
[1, 2, 4, 5]

Также можно удалять по срезам. Пример: удаление первых двух элементов:

>>> x = [1, 2, 3, 4, 5]
>>> del x[:2]
>>> x
[3, 4, 5]

Удаление последних n элементов: del x[n:].

Удаление элементов с четными индексами: del x[::2], нечетными: del x[1::2].

Удаление произвольного среза: del x[i:j:k].

Не путайте del x[2] и x.remove(2). Первый удаляет по индексу (нумерация с 0), а второй по значению, то есть находит в списке первую двойку и удаляет ее.

2. Удаление ключа из словаря. Просто:

>>> d = {"foo": 5, "bar": 8}
>>> del d["foo"]
>>> d
{'bar': 8}

А вот строки, байты и сеты del не поддерживают.

3. Удаление атрибута объекта.

class Foo:
    def __init__(self):
        self.var = 10

f = Foo()
del f.var
print(f.var)  # ошибка! 

Примечание: можно через del удалить метод у самого класса del Foo.method, но нельзя удалить метод у экземпляра класса del Foo().methodAttributeError.

4. Что значит удалить имя переменной? Это просто значит, что надо отвязать имя от объекта (при этом если на объект никто более не ссылается, то он будет освобожден сборщиком мусора), а само имя станет свободно. При попытке доступа к этому имени после удаления будет NameError, пока ему снова не будет что-то присвоено.

>>> a = 5
>>> del a
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

Здесь кроется один нюанс. Если переменная была внутри функции помечена, как global, то после ее удаления глобальная переменная никуда не денется, а имя освободится лишь в зоне видимости функции. Причем если мы снова присвоим ей значение, то она опять окажется глобальной, т.е. del не очищает информацию о global!

g = 100
def f():
    global g
    g = 200
    del g  # g останется вне функции
    g = 300  # та же самая глобальная g

f()
print(g) # 300

Чтобы реально удалить глобальную переменную, можно сделать так: del globals()['g'].

В пунктах 1, 2, 3 в качестве имен могут фигурировать выражения и ссылки, так как операции идут над содержимым объектов, а в пункте 4 должно быть строго формальное имя удаляемого объекта.

>>> x = [1, 2, 3]
>>> y = x
>>> del y  # удаляет именно y, но x остается

Еще одна особенность del – она может удалить несколько вещей за раз, если передать в нее кортеж или список объектов на удаление.

x, y, z = 10, 20, [1, 2, 3]
del x, y, z[2]

Пусть задан список из 5 элементов:

x = [1, 2, 3, 4, 5]
del x[2], x[4]

Казалось бы, что будут удалены 2-й и 4-й элементы списка, но это не так! Удаления происходят один за одним, и сначала будет удален 2-й элемент, размер списка уменьшится на 1, а потом будет попытка удалить 4-й элемент, но она будет неудачна – вылетит исключение IndexError, потому что элемента с индексом 4 больше нет, а сам список будет равен [1, 2, 4, 5]. Так что будьте внимательны в подобных ситуациях!

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

О поиске в словарях

При разборе вложенных структур из словарей и списков (например, конфигов), удобно пользоваться блоком try-except.

Ловим IndexError, если индекс отсутствует в списке, и KeyError, если ключ отсутствует в словаре. Однако, лучше ловить LookupError, который является предком обоих исключений:

>>> issubclass(KeyError, LookupError)
True
>>> issubclass(IndexError, LookupError)
True

Пример:

config = {}

try:
    admin = config['db'][0]['admins']['list'][0]
except LookupError:
    admin = 'all'

Независимо от того, не найден ли будет какой-то ключ словаря или индекс списка – будет поймана одна и та же ошибка LookupError.

Альтернативно, вы можете сразу обновлять записи словаря (если они не найдены) методом dict.setdefault(key, default). Этот метод проверяет, есть ли ключ в словаре, если его нет, то в словарь добавляется значение по умолчанию, и оно же возвращается. А если ключ был в словаре, то вернется значение по этому ключу. Поэтому такой неуклюжий код:

if 'workers' not in config:
    config['workers'] = 8
workers = config['workers']

Может быть переписан как:

workers = config.setdefault('workers', 8) 

Заметьте, что повторный вызов с другим default не поменяет уже записанное в первый раз значение:

>>> d = {}
>>> d.setdefault('foo', 10)
10
>>> d.setdefault('foo', 20)
10

Также, вам будет интересно почитать про defaultdict, который вам создает в себе записи при доступе к ним.

Красивого всем кода!

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈