Python: коллекции, часть 1/4: классификация, общие подходы и методы, конвертация

Boolean-aware operators

This suggestion is fundamentally the same as adding a no-value protocol, and so
the discussion above also applies.

Similar behavior to the ?? operator can be achieved with an or
expression, however or checks whether its left operand is false-y and not
specifically None. This approach is attractive, as it requires fewer changes
to the language, but ultimately does not solve the underlying problem correctly.

Assuming the check is for truthiness rather than None, there is no longer a
need for the ?? operator. However, applying this check to the ?. and
?[] operators prevents perfectly valid operations applying

Consider the following example, where get_log_list() may return either a
list containing current log messages (potentially empty), or None if logging
is not enabled:

lst = get_log_list()
lst?.append('A log message')

If ?. is checking for true values rather than specifically None and the
log has not been initialized with any items, no item will ever be appended. This
violates the obvious intent of the code, which is to append an item. The
append method is available on an empty list, as are all other list methods,
and there is no reason to assume that these members should not be used because
the list is presently empty.

Further, there is no sensible result to use in place of the expression. A
normal lst.append returns None, but under this idea lst?.append may
result in either [] or None, depending on the value of lst. As with
the examples in the previous section, this makes no sense.

Использование ключевого слова yield

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

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

Опять же, давайте сначала посмотрим, что возвращает наша функция, если не использовать ключевое слово yield. Выполните следующий сценарий:

В этом скрипте создается функция cube_numbers, которая принимает список чисел, вычисляет их куб и возвращает вызывающему объекту список целиком. При вызове этой функции список кубов возвращается и сохраняется в переменную cubes. Как видно из вывода, возвращаемые данные – это список целиком:

Теперь, изменим сценарий, так чтобы он возвращал генератор.

В приведенном выше скрипте функция cube_numbers возвращает генератор вместо списка кубов чисел. Создать генератор с помощью ключевого слова yield очень просто. Здесь нам не нужна временная переменная cube_list для хранения куба числа, поэтому даже наш метод cube_numbers проще. Кроме того, не используется оператор return, но вместо него используется слово yield для возвращения куба числа внутри цикла.

Теперь, когда функция cube_number возвращает генератор, проверим его, запустив код:

Несмотря на то, что был произведён вызов функции cube_numbers, она фактически не выполняется на данный момент времени, и в памяти еще нет элементов.

Получение значение из генератора:

Вышеуказанная функция возвратит «1». Теперь, когда снова вызывается next генератора, функция cube_numbers возобновит выполнение с того места, где она ранее остановилась на yield. Функция будет продолжать выполняться до тех пор, пока снова не найдет yield. Следующая функция будет продолжать возвращать значение куба по одному, пока все значения в списке не будут проитерированы.

Как только все значения будут проитерированы, следующий вызов функции создаст исключение StopIteration

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

Это делает генераторы идеально подходящими для ресурсоемких задач.

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

Создание итератора: объектно-ориентированный путь

Итак, мы увидели, что итераторы могут сэкономить нам память, и сэкономить процессорное время.

Давайте создадим наш собственный итератор. А начнем мы с созданием объекта итератора такого же как itertools.count.

Вот итератор Count, реализованный с использованием класса:

class Count:

    """Iterator that counts upward forever."""

    def __init__(self, start=0):
        self.num = start

    def __iter__(self):
        return self

    def __next__(self):
        num = self.num
        self.num += 1
        return num

Этот класс имеет конструктор, который инициализирует наше текущее число в 0 (или что-либо переданное в качестве start). То, что делает этот класс в качестве итератора, это методы __iter__ и __next__.

Когда итерируемый объект передается во встроенную функцию str, вызывается его метод __str__. Когда объект передается во встроенную функции len, вызывается его метод __len__.

>>> numbers = 
>>> str(numbers), numbers.__str__()
('', '')
>>> len(numbers), numbers.__len__()
(3, 3)

Встроенной функции iter для итерируемого объекта попытается вызвать его метод __iter__. Встроенная функции next для объекта попытается вызвать его метод __next__.

Предполагается, что функция iter возвращает итератор. Поэтому наша функция __iter__ должна возвращать итератор. Но наш объект сам является итератором, поэтому должен вернуть себя. Поэтому наш объект Count возвращает self из своего метода __iter__.

Функция next должна возвращать следующий элемент в нашем итераторе или вызывать исключение StopIteration, когда элементов больше нет. Мы возвращаем текущий номер и увеличиваем его, чтобы он был больше во время следующего вызова __next__.

Мы можем вручную запустить наш класс итератора Count следующим образом:

>>> c = Count()
>>> next(c)
0
>>> next(c)
1

Мы также можем использовать объект Count в цикле for, как с любым другим итератором:

>>> for n in Count():
...     print(n)
...
0
1
2
(это будет продолжаться бесконечно)

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector