Студенты, которые писали код в течение определенного периода времени, должны хорошо понимать это предложение: использование времени и использование пространства в программе часто противоречат друг другу Время может использоваться для пространства, а пространство может использоваться для времени, но это трудно улучшить программу одновременно с использованием времени и пространства.
Но если вы попытаетесь использовать генераторы для рефакторинга кода, возможно, вы обнаружите, что в определенной степени вы можете улучшить использование времени и пространства.
Давайте возьмем простой проект по очистке данных в качестве примера, чтобы проиллюстрировать, как генераторы могут сделать ваш код более эффективным.
В 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 часть данных от Redis
- Этот фрагмент данных передается в remove_sensitive_data.
- Второй шаг — передать данные после обработки в tranfer_chinese_num.
- После обработки на шаге 3 передайте его в save_data
- вернуться к шагу 1
Весь процесс подобен сборочной линии, и данные обрабатываются и архивируются один за другим. Нет необходимости создавать дополнительный список, просто зацикливайте столько раз, сколько есть фрагментов данных, и не делайте избыточных циклов.
Если эта статья была вам полезна, обратите внимание на мой публичный аккаунт WeChat: