После столь долгого написания Python вы должны научиться использовать ключевое слово yield.

задняя часть Python

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

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

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

В Redis есть списокdatalist, в нем много данных, которые могут быть纯阿拉伯数字,中文数字,字符串"敏感信息". Теперь нужно реализовать: прочитать все данные из Redis, положить все строки敏感信息Все отбрасываются, все китайские цифры конвертируются в арабские цифры, с{'num': 12345, 'date': '2019-10-30 18:12:14'}Такой формат вставляется в MongoDB.

Пример данных выглядит следующим образом:

41234213424
一九八八七二六三
8394520342
七二三六二九六六
敏感信息
80913408120934
敏感信息
敏感信息
95352345345
三三七四六
999993232
234234234
三六八八七七
敏感信息

Как показано ниже:

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

import redis
import datetime
import pymongo


client = redis.Redis()
handler = pymongo.MongoClient().data_list.num

CHINESE_NUM_DICT = {
    '一': '1',
    '二': '2',
    '三': '3',
    '四': '4',
    '五': '5',
    '六': '6',
    '七': '7',
    '八': '8',
    '九': '9'
}

def get_data():
    datas = []
    while True:
        data = client.lpop('datalist')
        if not data:
            break
        datas.append(data.decode())
    return datas

def remove_sensitive_data(datas):
    clear_data = []
    for data in datas:
        if data == '敏感信息':
            continue
        clear_data.append(data)
    return clear_data

def tranfer_chinese_num(datas):
    number_list = []
    for data in datas:
        try:
            num = int(data)
        except ValueError:
            num = ''.join(CHINESE_NUM_DICT[x] for x in data)
        number_list.append(num)
    return number_list

def save_data(number_list):
    for number in number_list:
        data = {'num': number, 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
        handler.insert_one(data)

raw_data = get_data()
safe_data = remove_sensitive_data(raw_data)
number_list = tranfer_chinese_num(safe_data)
save_data(number_list)

Эффект операции показан на следующем рисунке:

Этот код выглядит очень Pythonic, функция делает только одну вещь и, кажется, соответствует спецификации кодирования. Конечный результат также правильный. Что может быть не так?

Проблема в том, что в этом коде каждая функция создает список для хранения обработанных данных. Что делать, если данные в Redis превышают память вашего текущего компьютера? Многократное использование цикла for для одного и того же пакета данных тратит много времени. Можно ли зациклить его только один раз?

Вы можете сказать, что вы можете удалить敏感信息, вся логика преобразования китайских цифр в арабские написана наget_dataфункциональныйwhileВ цикле, разве это не просто цикл один раз?

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

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

то вы должны изменитьget_dataкод.

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

В это время необходимо полагаться на наш генератор.

Давайте сначала посмотрим на результат работы следующего кода:

def gen_num():
    nums = []
    for i in range(10):
        print(f'生成数据:{i}')
        nums.append(i)
    return nums
nums = gen_num()
for num in nums:
    print(f'打印数据:{num}')

Эффект операции показан на следующем рисунке:

Теперь давайте внесем некоторые изменения в код:

def gen_num():
    for i in range(10):
        print(f'生成数据:{i}')
        yield i
nums = gen_num()
for num in nums:
    print(f'打印数据:{num}')

Результат его работы показан на следующем рисунке:

Сравните две иллюстрации выше. На предыдущем рисунке сначала генерируются 10 данных, а затем печатаются 10 данных. Последнее изображение, генерировать данные, печатать данные, генерировать другие данные, печатать другие данные...

Если работающая логика представлена ​​номером строки кода, то код выполняется в соответствии с этим процессом:

1->5->6->2->3->4->6->7->6->2->3->4->6->7->6->2->3->4->6->7....

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

программа работает доyieldпоставит номер после него抛出Дайте цикл for снаружи, а затем введите тело внешнего цикла for.После завершения выполнения внешнего цикла for он войдет снова.gen_numвнутри функцииyield iСледующая строка запускает следующий цикл for и продолжает генерировать новые числа...

Во всем процессе нет необходимости создавать дополнительный список для сохранения промежуточных данных, чтобы достичь цели экономии места в памяти. На протяжении всего процесса, хотя код пишет два цикла for, если вы используете пошаговую отладку, вы обнаружите, что настоящий цикл на самом деле всего лишь один.for i in range(10). и снаружиfor num in numsОн только реализует переключение внутри и снаружи функции и не добавляет новый цикл.

Вернемся к исходному вопросу: как мы используем генераторы для модификации кода? На самом деле вам просто нужно поставитьreturn 列表изменить наyield 每一个元素Только что:

import redis
import datetime
import pymongo


client = redis.Redis()
handler = pymongo.MongoClient().data_list.num_yield

CHINESE_NUM_DICT = {
    '一': '1',
    '二': '2',
    '三': '3',
    '四': '4',
    '五': '5',
    '六': '6',
    '七': '7',
    '八': '8',
    '九': '9'
}

def get_data():
    while True:
        data = client.lpop('datalist')
        if not data:
            break
        yield data.decode()

def remove_sensitive_data(datas):
    for data in datas:
        if data == '敏感信息':
            continue
        yield data

def tranfer_chinese_num(datas):
    for data in datas:
        try:
            num = int(data)
        except ValueError:
            num = ''.join(CHINESE_NUM_DICT[x] for x in data)
        yield num

def save_data(number_list):
    for number in number_list:
        data = {'num': number, 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
        handler.insert_one(data)

raw_data = get_data()
safe_data = remove_sensitive_data(raw_data)
number_list = tranfer_chinese_num(safe_data)
save_data(number_list)

Код показан ниже:

Если вы включите режим отладки PyCharm, вы обнаружите, что поток данных выглядит следующим образом:

  1. Получить 1 часть данных от Redis
  2. Этот фрагмент данных передается в remove_sensitive_data.
  3. Второй шаг — передать данные после обработки в tranfer_chinese_num.
  4. После обработки на шаге 3 передайте его в save_data
  5. вернуться к шагу 1

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

Если эта статья была вам полезна, обратите внимание на мой публичный аккаунт WeChat: