リンクします。https://zhuanlan.zhihu.com/p/68128557
出典:ほとんど知っている
著者が著作権を保有。商業転載は非商用の転載は、ソースを明記してください、権限の作者に連絡してください。
ループは、共通のプログラム制御構造です。私たちはしばしば、人間のマシンに比べて最大の利点の一つは、つまり、機械が何かをする時計の周りに繰り返すことができると言うが、人々はそうではありません。**「ループ」**は、重要な概念は、機械繰り返し作業を可能にしています。
伝統的でない文法サイクルでは、Pythonのパフォーマンスの伝統。それは一般的に放棄しながら、for (init; condition; incrment)
三段構造が、選択したfor
とwhile
サイクルを表現するために、2つの古典的なキーワード。ほとんどの場合、私たちの需要サイクルを使用することができますfor <item> in <iterable>
満たすために、while <condition>
そしていくつかのコントラストがさらに少ないです。
けれども循環構文は非常に単純ですが、本当に書くのは簡単ではありません。この記事では、ループコードの「典型的」であるものを探求し、それらをどのように書くこと。
「本物」のサイクルとは何ですか?
「本物」は、多くの場合、何かをするために誰かを記述するために使用される言葉で、それは非常によく行う、地元の伝統と一致しています。比喩、あなたが友人の集まりに行く、同じテーブル広東、他の開口部があった、すべての文が標準北京のアクセント、音の完璧な子です。次に、あなたが彼女に言うことができます:「あなたの北京真の言うように本物を。」
「本物」は、多くの場合、アクセントを記述するための言葉ですので、料理のこのタイプは、ループコードの「典型的な」という、本物を味わう、それが何を意味するのでしょうか?私が説明する古典的な例を取得してみましょう。
あなただけのPython月学習者に尋ねる場合:「リストを横断しながら、どのように現在のインデックスを取得します?。」彼は、このコードを引き渡すことがあります。
index = 0
for name in names:
print(index, name)
index += 1
ループは、上記の事実であるが、それはそれではありませんんが「本物」。Pythonは、コードは次のようになります言う人の開発における3年間の経験を持っています:
for i, name in enumerate(names):
print(i, name)
enumerate()
Pythonは組み込み関数であり、パラメータとして「反復してもよい」オブジェクトを受信し、連続的に生成する戻り(当前下标, 当前元素)
、新しいオブジェクトは、繰り返しができます。このシナリオでは、それが最も適切な使用しています。
したがって、上記の例では、我々は以上のループコードの2番目の段落の最初の段落と思います「本物」。それがより直感的なコードを使用しているため、よりスマートに作業を完了しました。
)(代表プログラミングのアイデアを列挙
ただし、コードは、特定のサイクル本物かどうか、だけではなく、または標準としてビルトイン方式の知識なしに決定されます。私たちは、上記の例から、より深い何かを掘ることができます。
あなたが見ることができるように、Pythonのfor
循環が唯一であるfor <item> in <iterable>
-このような構造、および上半期の構造アイテムに割り当てられて -ではない、あまりにも多くのトリックプレイ可能。だから、後半のiterableは、私たちが物事の大騒ぎを作ることができる唯一のものです。そして、するために、enumerate()
「修正機能」* *の代表として機能し、ちょうど新しいアイデアを提供します:反復可能オブジェクトを変更することによって、ループ自体を最適化します。
これが私の最初の提案に私をもたらします。
推奨1:ループの繰り返しオブジェクトを最適化するように変更する機能を使用し
反復可能オブジェクト変更機能処理を使用し、それが様々な面でのループコードに影響を与えることができます。そして、あまりにも遠く取らないこの方法を実証するための適切な例を見つけるために、内蔵のモジュールitertoolsは完璧な例です。
簡潔には、itertools関数は、反復回数を含むツールセットは、オブジェクトを配向することができるです。私は記事の前のシリーズにあった「戸口コンテナ」、それに言及しました。
あなたはitertoolsを勉強したい場合は、公式Pythonドキュメントはあなたの選択である、非常に詳細なモジュール関連情報があります。しかし、この記事では、焦点がわずかに異なると公式文書になります。私はそれがコードの循環を改善する方法を詳細に説明するために、いくつかの共通のコードのシナリオを渡します。
1.ネストされたループ製品を平ら
我々はすべての*「良いよりもフラットなネストされたコード」*ことを知っていますが。しかし、時には特定の需要が、ネストされたループの仕事を書くようになったようです。たとえば、次の一節:
def find_twelve(num_list1, num_list2, num_list3):
"""从 3 个数字列表中,寻找是否存在和为 12 的 3 个数
"""
for num1 in num_list1:
for num2 in num_list2:
for num3 in num_list3:
if num1 + num2 + num3 == 12:
return num1, num2, num3
ネストされたループのコードのためのこの必要性は我々が使用することができ、多層複数のオブジェクトを横断する製品()を最適化する機能を。product()
複数のオブジェクトを受信すると、反復すること、及びその後、デカルト連続的に彼らの製品に基づいて結果を生成することができます。
from itertools import product
def find_twelve_v2(num_list1, num_list2, num_list3):
for num1, num2, num3 in product(num_list1, num_list2, num_list3):
if num1 + num2 + num3 == 12:
return num1, num2, num3
使用前のコードと比較するとproduct()
、タスクを完了するために、1サイクルのみに使用する機能を、コードはより洗練なります。
2. isliceインターレースを達成するために、内部ループ
外部データファイルは、コンテンツのフォーマットはこれですRedditのポストのタイトルを、持っている含ま:
python-guide: Python best practices guidebook, written for humans.
---
Python 2 Death Clock
---
Run any Python Script with an Alexa Voice Command
---
<... ...>
おそらく美的、このドキュメント間のすべての2つのタイトルのために、そこにある"---"
セパレータは。さて、あなたはこれらの無意味なセパレーターをスキップしなければならない、我々はタイトルのリスト内のすべての文書を取得する必要があるので、彼らはファイルの内容をトラバース。
リファレンス前のenumerate()
機能を理解し、私たちは現在のサイクル数に基づいて、サイクル期間中に追加することができif
、これを行うために判決:
def parse_titles(filename):
"""从隔行数据文件中读取 reddit 主题名称
"""
with open(filename, 'r') as fp:
for i, line in enumerate(fp):
# 跳过无意义的 '---' 分隔符
if i % 2 == 0:
yield line.strip()
しかしサイクルで処理インターレースのこの種の需要は、itertoolsを使用する場合にislice()関数は、変更されたオブジェクトの循環で、ループコードをより単純かつ直接的行うことができます。
islice(seq, start, end, step)
アレイスライス*の機能と動作(リストが[開始:停止:ステップ]) ほとんど同じパラメータを有しています。インターレースがループ内必要な場合に限り、配達の進展として2に第三のステップサイズパラメータ値を提供される(デフォルト1)*。
from itertools import islice
def parse_titles_v2(filename):
with open(filename, 'r') as fp:
# 设置 step=2,跳过无意义的 '---' 分隔符
for line in islice(fp, 0, None, 2):
yield line.strip()
代替break文takewhile 3.
時には、我々は、各サイクルの開始時に、サイクルが早すぎる終わりかどうかを判断する必要がある必要があります。たとえば、次のよう:
for user in users:
# 当第一个不合格的用户出现后,不再进行后面的处理
if not is_qualified(user):
break
# 进行处理 ... ...
事前に、このような中断の循環のために、私たちは使用することができますtakewhile() 、それを簡素化する機能を。takewhile(predicate, iterable)
反復があろうiterable
呼び出しプロセスのパラメータとして現在のオブジェクトを使用し続けるpredicate
機能試験をし、その結果を返し、関数がtrueを返した場合、現在のオブジェクトが生成され、サイクルが継続します。そうでない場合は、すぐに現在のサイクルを中断。
使用takewhile
このサンプルコードを:
from itertools import takewhile
for user in takewhile(is_qualified, users):
# 进行处理 ... ...
他の興味深いユーティリティ関数もあるitertools、それらが使用サイクルに使用することができ、そのような平坦な二重鎖の機能の使用は、複数のオブジェクト等を循環させながら機能zip_longestを使用して、ループを入れ子になりました。
限られたスペースには、ここで私が紹介するつもりはありません。あなたが興味を持っている場合は、自分自身についての詳細を学ぶために公式ドキュメントに行くことができます。
4.独自の変更機能を記述するために発電機を使用して、
itertoolsが提供されるものの機能に加えて、我々は非常に簡単に自分のサイクル変更機能を定義するためにビルダーを使用することができます。
のは、例えば、単純な関数を見てみましょう:
def sum_even_only(numbers):
"""对 numbers 里面所有的偶数求和"""
result = 0
for num in numbers:
if num % 2 == 0:
result += num
return result
上記の機能には、追加のすべての奇数、導入フィルタリングするために、ループの本体if
判決文を。ループの内容を簡素化するために、我々は、特別なファンクション・ジェネレータフィルタの偶数を定義することができます。
def even_only(numbers):
for num in numbers:
if num % 2 == 0:
yield num
def sum_even_only_v2(numbers):
"""对 numbers 里面所有的偶数求和"""
result = 0
for num in even_only(numbers):
result += num
return result
numbers
使用して、変数even_only
の装飾機能は、sum_even_only_v2
内部関数は論理「でもフィルタリング」、および単に完了するために、合計に注力し続けなければならないであろう。
ヒント:もちろん、上記の機能は本当に実用的ではありません。現実の世界では、直接発電機に最適なこのシンプルな需要が/式のリストを取得します:
sum(num for num in numbers if num % 2 == 0)
提案2:in vivoでの機能コードブロック巡回複合体による解体
私はいつもサイクルは、黒魔術を開いたかのようにあなたは、新しいループコードブロックを書くたびに、配列内のすべての内容が繰り返さ無限開始され、より素晴らしいものであることを感じています。
私もメリットに加えて、黒魔術のこの作品が見つかりましたが、それはまた、陣内がそうで無効な要素、データを前処理、印刷ログとをフィルタリングなど、より多くのコードを、搾りあなたを誘惑していきます。いくつかのも同じ抽象コンテンツに属していなかった、それは黒魔術の同じ部分に詰めされます。
あなたは私たちが急務バフ配列であり、これは当然のすべての問題だと思うかもしれません。あなたは彼らが行く置くことができ、ループの詰め体にロジックの多くを入れていない場合は?
のは、次のビジネスシナリオを見てみましょう。ウェブサイトでは、1は、スクリプトの実施期間30日に1回あり、そのタスクは、ユーザーがログオフした後、彼らのボーナスポイントを送信するために一定の期間で、過去30日間毎週末に照会することです。
コードは以下の通りであります:
import time
import datetime
def award_active_users_in_last_30days():
"""获取所有在过去 30 天周末晚上 8 点到 10 点登录过的用户,为其发送奖励积分
"""
days = 30
for days_delta in range(days):
dt = datetime.date.today() - datetime.timedelta(days=days_delta)
# 5: Saturday, 6: Sunday
if dt.weekday() not in (5, 6):
continue
time_start = datetime.datetime(dt.year, dt.month, dt.day, 20, 0)
time_end = datetime.datetime(dt.year, dt.month, dt.day, 23, 0)
# 转换为 unix 时间戳,之后的 ORM 查询需要
ts_start = time.mktime(time_start.timetuple())
ts_end = time.mktime(time_end.timetuple())
# 查询用户并挨个发送 1000 奖励积分
for record in LoginRecord.filter_by_range(ts_start, ts_end):
# 这里可以添加复杂逻辑
send_awarding_points(record.user_id, 1000)
上記機能は、主に2つのサイクルから構成されています。外側のループの責任、過去30日間の要件を満たし、そしてUNIXタイムスタンプに変換するために主に取得時間。積分インナーループ送信から、これらの2つのタイムスタンプを使用した後。
前に述べたように、プラグは、外側ループ黒魔術の縁に開放されています。しかし、観察を通じて、我々は見つけることができるループの本体全体が実際にからなる2つの完全に無関係なタスクで構成されています。「日付と時刻のスタンプが用意選択」と「ボーナスポイントを送ります」。
複雑なループの新たな需要に対処する方法
このコードは、任意の害していますか?私はあなたを教えてみましょう。
ある日、一部のユーザーは、当社のウェブサイトを磨くそれでも、週末夜に寝ていない製品を見ている、我々は彼らが早く後にベッドに行くように注意してください、それらを与える必要があります。だから、新しい需要が生まれ:「過去30日間に、週末5時00分に三時にログインしているユーザーに通知を送信」。
新たな問題が巻き起こっています。あなたのようにシャープ、確かに1は、この新しい需要は、ユーザーの一部をスクリーニングし、需要は非常に、非常に似ているの前にあることを見つけることができます。あなたは、ループ本体の外観の前にそのグループを開いた場合は、あなたは、内側のループ、完全に異なるロジックがあるため、単純に、コードを再利用することができないということでしょう結合一緒に。☹️
コンピュータの世界では、私たちはしばしば、物事の間の関係を表現するために** **「結合」という言葉を使います。上記の例では、*「時間を選ぶ」と「不可欠送信」*同じループ本体に住んでこれら二つのことは、非常に強力な結合関係を確立しました。
より良いコードの再利用するためには、我々は、ループの本体から切り離さ*「時間を選ぶ」*セクションで機能する必要があります。そして、私たちの古くからの友人、**「ジェネレータ関数」**この作業を実施するための最良の選択肢です。
ジェネレータ関数切り離さループを使用して
なければならない「時間を選択し、」内部ループアウトから分離セクション、我々は新しいジェネレータ関数を定義する必要がありgen_weekend_ts_ranges()
、具体的に必要なUNIXタイムスタンプを生成するように設計、:
def gen_weekend_ts_ranges(days_ago, hour_start, hour_end):
"""生成过去一段时间内周六日特定时间段范围,并以 UNIX 时间戳返回
"""
for days_delta in range(days_ago):
dt = datetime.date.today() - datetime.timedelta(days=days_delta)
# 5: Saturday, 6: Sunday
if dt.weekday() not in (5, 6):
continue
time_start = datetime.datetime(dt.year, dt.month, dt.day, hour_start, 0)
time_end = datetime.datetime(dt.year, dt.month, dt.day, hour_end, 0)
# 转换为 unix 时间戳,之后的 ORM 查询需要
ts_start = time.mktime(time_start.timetuple())
ts_end = time.mktime(time_end.timetuple())
yield ts_start, ts_end
このジェネレータ関数を使用すると、「送信ボーナスポイント」と新たな需要のための古い需要は「通知の送信」は、タスクを完了するために、ループの本体でそれを再利用することができます。
def award_active_users_in_last_30days_v2():
"""发送奖励积分"""
for ts_start, ts_end in gen_weekend_ts_ranges(30, hour_start=20, hour_end=23):
for record in LoginRecord.filter_by_range(ts_start, ts_end):
send_awarding_points(record.user_id, 1000)
def notify_nonsleep_users_in_last_30days():
"""发送通知"""
for ts_start, ts_end in gen_weekend_ts_range(30, hour_start=3, hour_end=6):
for record in LoginRecord.filter_by_range(ts_start, ts_end):
notify_user(record.user_id, 'You should sleep more')
概要
本稿では、まず簡単に「本物」リサイクルコードの定義を説明します それから彼は最初の提案をした:循環を改善するために修正機能を使用します。仮想私のビジネスシナリオの後、プレスコード内でデューティ・サイクルを解体することの重要性を説明。
いくつかの重要なポイントを要約したものです。
- 関数は環状オブジェクト自体を使用して変更され、ループ本体のコードを向上させることができます
- 関数は、循環を改善するために使用することができますitertools多くのツールがあります。
- ジェネレータ関数を使用すると、簡単に独自の関数を定義するように変更することができます
- ループ内で、非常に「コードの膨張」サイトで発生しました
- デカップリング異なるコードブロックのデューティ・サイクルのジェネレータ関数を使用して、より優れた柔軟性
Tencentの技術・エンジニアリングは、ほとんどの友人を知るようになりました。この番号は、スカウトの友人だけでなく、研究者に最新の技術記事のガチョウの工場をお届けしますプロの技術的な愛好家、洞察力の技術的な話題を提供することを目的とし、特に辺境探査では、コンピュータ技術やインターネット関連のトピック領域に基づいています技術のエコシステムを構築し、議論に参加し、オープンなプラットフォームを提供します。
Tencentの技術・エンジニアリングは、今後も、私たちがスカウトの友人のための混乱の中で技術的な仕事に答えるタンクが思うように、ガチョウの工場の技術者の過半数を招待するだけでなく、「いいね」、私たちを聞かせて慎重に準備するために私たちのために多くのポイントをお答えくださいます普通株式と進歩を促します。、任意の提案を持っているプライベートの手紙私たちを喜ばせます!
より多くの技術的な乾燥には注意を持続してください「Tencentの技術とエンジニアリングは、」ほとんどの番号を知っている、と私たちの列にサブスクライブ「テンセント・テクノロジー」。