Rumah >pembangunan bahagian belakang >Tutorial Python >Apakah struktur pemahaman senarai Python dan ungkapan penjana
Ekspresi penjana ialah cara ringkas untuk menjana bekas. Selalunya, anda akan mendengar pemahaman senarai, tetapi terdapat juga pemahaman yang ditetapkan dan pemahaman kamus. Walau bagaimanapun, perbezaan dalam istilah agak penting: jika anda benar-benar membuat senarai, ia hanyalah pemahaman senarai.
Ekspresi penjana disertakan dalam kurungan( )
, manakala pemahaman senarai disertakan dalam kurungan segi empat sama[ ]
. Penghuraian set disertakan dalam pendakap kerinting { }
. Selain daripada perbezaan ini, sintaks adalah sama dalam ketiga-tiga kes!
(Terdapat banyak lagi untuk menghurai kamus, yang akan kita bincangkan kemudian.)
Ungkapan penjana berkaitan dengan penjana, yang akan kita selami dalam bahagian kemudian Bincangkan. Buat masa ini, cukup untuk kita hanya menggunakan takrif rasmi ungkapan penjana:
Ungkapan penjana - ungkapan yang mengembalikan lelaran.
Ungkapan penjana (atau senarai/setkan kumpulan luar) adalah sedikit seperti gelung for
terbalik.
Sebagai contoh mudah, mari semak contoh daripada artikel sebelumnya yang mana kami menukar senarai suhu Fahrenheit kepada Celsius. Saya akan melaraskannya sedikit supaya nombor akan disimpan dalam senarai lain dan bukannya dicetak terus.
temps_f = [67.0, 72.5, 71.3, 78.4, 62.1, 80.6] temps_c = [] def f_to_c(temp): return round((temp - 32) / 1.8, 1) for c in map(f_to_c, temps_f): temps_c.append(c) print(temps_c)
Percaya atau tidak, pemahaman senarai akan mengurangkan keseluruhan program kepada tiga baris! Mari permudahkan satu bahagian pada satu masa supaya anda boleh memahami maksud saya.
Mari kita mulakan dengan menggantikan gelung for dengan pemahaman senarai...
temps_f = [67.0, 72.5, 71.3, 78.4, 62.1, 80.6] def f_to_c(temp): return round((temp - 32) / 1.8, 1) temps_c = [f_to_c(temp) for temp in temps_f] print(temps_c)
Barisan kod yang penting ialah temps_c = [f_to_c(temp) for temp in temps_f]
Ini berkelakuan seperti map()
. temp
Untuk setiap elemen temps_f
dalam senarai, kami menggunakan fungsi f_to_c()
.
Sekarang, jika saya memerlukan fungsi f_to_c()
di tempat lain, saya hanya berhenti di sini dan menamatkannya. Walau bagaimanapun, jika ini adalah satu-satunya tempat saya memerlukan logik penukaran Fahrenheit ke Celsius, saya tidak boleh menggunakan fungsi itu sama sekali dan mengalihkan kod logik terus ke dalam penghuraian:
temps_f = [67.0, 72.5, 71.3, 78.4, 62.1, 80.6] temps_c = [round((temp-32) / 1.8, 1) for temp in temps_f] print(temps_c)
Apa yang saya beritahu anda? Tiga baris! Bukankah hanya tiga baris kod?
Berdasarkan data yang saya dapat, saya boleh mengurangkannya lagi. Mari kita lihat ini dengan contoh lain.
Bayangkan anda mempunyai program yang menerima sekumpulan integer pada baris, dipisahkan oleh ruang, seperti 5 4 1 9 5 7 5
. Anda ingin mencari jumlah semua integer ini. (Untuk kesederhanaan, anggap anda tidak berisiko untuk ralat menaip.)
Mari kita mulakan dengan menulisnya dengan cara yang jelas, tanpa pemahaman senarai.
user_input = input() values = user_input.split(' ') total = 0 for v in values: n = int(v) total += n print(total)
Jelas sekali kan? Kami mendapat input pengguna sebagai rentetan dan kemudian membahagikan senarai pada ruang untuk mendapatkan nombor individu. Kami mencipta pembolehubah untuk menyimpan jumlah, kemudian menggunakan gelung untuk melalui setiap nilai, menukarnya kepada integer dan menambahnya kepada jumlah. Sekarang setelah kita mempunyai logik kod, mari permudahkan dan optimumkannya.
Mari mudahkan beberapa kod yang jelas di sini dahulu. Kami telah membincangkan semua konsep ini sebelum ini, jadi lihat jika anda dapat melihat tahap yang saya telah bertambah baik.
values = input().split(' ') total = 0 for v in values: total += int(v) print(total)
Kami tidak boleh menjadi lebih mudah daripada ini melainkan kami menggunakan pemahaman senarai, jadi mari mulakan sekarang!
values = input().split(' ') total = sum(int(v) for v in values) print(total)
Ungkapan penjana di sini ialah (int(v) for v in values)
. v
sepadan dengan setiap nilai values
dalam senarai, yang kami tukar kepada integer ( int(v)
).
Perhatikan cara saya menggunakan fungsi sum()
, menghantar ekspresi penjana terus ke dalamnya. Memandangkan ungkapan itu diluluskan terus sebagai satu-satunya hujah, saya tidak memerlukan tanda kurung tambahan di sekelilingnya.
Sekarang, jika saya tidak memerlukan values
sebarang senarai lain, saya sebenarnya boleh mengalihkan logik itu terus ke dalam ungkapan penjana!
total = sum(int(v) for v in input().split(' ')) print(total)
Semudah pai, bukan?
Bagaimana jika sebaliknya, kami mahukan jumlah petak setiap nombor yang kami masukkan? Sebenarnya, terdapat dua cara untuk melakukan ini. Cara mudah untuk melakukannya ialah ini:
total = sum(int(v)**int(v) for v in input().split(' ')) print(total)
Ini berfungsi, tetapi entah bagaimana rasanya tidak betul, bukan? Kami mahu menukar dua kali v
kepada integer.
我们可以通过将列表解析嵌套到我们的生成器表达式中来解决这个问题!
total = sum(n**2 for n in [int(v) for v in input().split(' ')])
列表解析和生成器表达式从内部到外部进行解析。最里面的表达式 ,int(v) for v in input().split(' ')
首先运行,并且封闭的方括号[ ]
将其转换为列表(可迭代)。
接下来,n**2 for n in [LIST]
运行外部表达式,[LIST]
就是我们刚才生成的列表。
这种嵌套不是很容易理解,也不直观。尽量少用它。当我需要嵌套时,我将每个列表理解写在单独的行上并将其存储...
the_list = [int(v) for v in input().split(' ')] total = sum(n**2 for n in the_list) print(total)
...测试一下,然后通过复制和粘贴开始嵌套。
如果我们只想要列表中奇数的总和怎么办?生成器表达式和列表解析也可以做到这一点。
我们将在此示例中使用嵌套,但我们将首先从非嵌套版本开始,为了使新逻辑更易于查看。
the_list = [int(v) for v in input().split(' ')] total = sum(n**2 for n in the_list if n%2==0) print(total)
新部分在第二行。在生成器表达式的末尾,我添加了if n%2==0
. 你可能认识模运算符 ( %
),它为我们提供了除法的余数。任何偶数都可以被2
整除,这意味着它没有余数。因此,n%2==0
仅适用于偶数。
将条件放在语句之后而不是之前,感觉有点奇怪。理解它的最简单方法是和没有生成器表达式的相同代码逻辑做对比......
output = [] for n in the_list: if n%2==0: output.append(n**2)
基本上,要将其转换为生成器表达式,你只需知道append()
,从这儿开始......
n**2 for n in the_list: if n%2==0:
然后从for
和if
语句中删除冒号 ( :
)、换行符缩进...
n**2 for n in the_list if n%2==0
我们还可以使用生成器表达式和列表解析一次循环遍历多个可迭代对象,其方式与嵌套循环相同。
考虑以下逻辑:
num_a = [1, 2, 3, 4, 5] num_b = [6, 7, 8, 9, 10] output = [] for a in num_a: for b in num_b: output.append(a*b)
我们也可以按照我刚才给出的相同步骤将其转换为列表解析!我们把append()
的参数放在前面...
a*b for a in num_a: for b in num_b:
...然后我们将其余部分折叠成一行,删除冒号。
a*b for a in num_a for b in num_b
最后,将其包裹在方括号中,并将其复制给变量output输出。
output = [a*b for a in num_a for b in num_b]
正如我在文章开头提到的,就像你可以使用包含在方括号[ ]
中的生成器表达式来创建列表一样,你也可以使用花括号{ }
来创建集合。
例如,让我们生成通过100 除以小于 100 的奇数得到的所有余数组成的集合。通过使用集合,我们确保没有重复项,使结果更容易理解。
odd_remainders = {100%n for n in range(1,100,2)} print(odd_remainders)
运行该代码给我们...
{0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 29, 30, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49}
这里真的没有什么特别的地方。集合解析的工作方式与列表解析相同,只是创建了不同的容器。
字典解析遵循与其他形式的生成器表达式几乎相同的结构,但有一个区别:冒号。
如果你还记得,当你创建集合或字典时,你使用花括号{ }
。唯一的区别是,在字典中,你使用冒号:
来分隔键值对,这是在集合中不会执行的操作。同样的原则在这里也适用。
例如,如果我们想创建一个字典,将 1 到 100 之间的整数存储为键,并将该数字的平方作为值...
squares = {n : n**2 for n in range(1,101)} print(squares)
这就是字典解析!除了冒号之外:
,其他所有内容都与任何其他生成器表达式相同。
对所有情况都使用列表解析或生成器表达式可能非常诱人。它们相当容易上瘾,部分原因是写出来时看起来非常高端优雅。强大的单行代码让程序员非常兴奋——我们真的很喜欢优雅地地处理我们的代码。
但是,我必须提醒你不要过分追求优雅。记住The Zend Of Python,这里有一些与此主题相关的部分:
美丽总比丑陋好。
...
简单胜于复杂。
复杂胜于复杂。
平面优于嵌套。
稀疏比密集好。
可读性很重要。
...
列表解析可能很优雅,但如果使用不当,它们也可能成为密集的、垃圾的逻辑片段。
我从互联网上的一项调查中借用了这个例子
primary = [ c for m in status['members'] if m['stateStr'] == 'PRIMARY' for c in rs_config['members'] if m['name'] == c['host'] ] secondary = [ c for m in status['members'] if m['stateStr'] == 'SECONDARY' for c in rs_config['members'] if m['name'] == c['host'] ] hidden = [ m for m in rs_config['members'] if m['hidden'] ]
你能说出这段代码的意思?如果你阅读一会,你可能可以,但你为什么要阅读?代码一清二楚。(在调查中,这被评为最不可读的示例。)当然,你可以添加注释来解释正在执行的逻辑 - 事实上,他们在原始示例中做了 - 但任何时候你需要注释来解释代码正在做什么,这几乎可以肯定太复杂了。
列表解析和生成器表达式固然很强大,但如果大量使用会像刚刚那样变得难以阅读。
不一定非得像刚刚那样做。只需将列表理解拆分为多行,你就可以重新获得很多可读性,其结构与传统循环类似。
primary = [ c for m in status['members'] if m['stateStr'] == 'PRIMARY' for c in rs_config['members'] if m['name'] == c['host'] ] secondary = [ c for m in status['members'] if m['stateStr'] == 'SECONDARY' for c in rs_config['members'] if m['name'] == c['host'] ] hidden = [ m for m in rs_config['members'] if m['hidden'] ]
请注意,这并不能完全证明上述说法是正确的。我仍然会使用传统的循环而不是前面的示例,只是因为它们更易于阅读和维护。
如果你仍然不相信这是 Python 的一个容易被滥用的特性?我的有一个朋友分享他遇到的这个真实案例。我们根本不知道它做了什么。
cropids = [self.roidb[inds[i]]['chip_order'][ self.crop_idx[inds[i]] % len(self.roidb[inds[i]]['chip_order'])] for i in range(cur_from, cur_to)]
光是看着,我的可能都会有点烦躁了。
看看以下情况。(此代码是虚构的,仅供参考。)
some_list = getTheDataFromWhereever() [API.download().process(foo) for foo in some_list]
对于未经训练的人来说,这看起来没啥问题,但请注意请仔细看砍......数据<strong>some_list</strong>
正在被直接修改(变异),但结果没有被存储。这是列表解析甚至生成器表达式被滥用来代替循环的情况。它使阅读变得困难,更不用说调试了。
无论你想使用生成器表达式来变得优雅,这都是你应该坚持使用循环的一种情况:
some_list = getTheDataFromWhereever() for foo in some_list: API.download().process(foo)
想一想列表解析的本质:你将所有内容打包成一个巨大的语句。这样做的好处是你消除了一堆中间步骤。缺点也是……你消除了一堆中间步骤。
考虑调试一个典型的循环。你可以单步执行它,一次迭代,使用调试器随时观察每个变量的状态。你还可以使用错误处理来处理异常的边缘情况。
相比之下,这些都不适用于生成器表达式或列表解析。一切要么有效,要么无效!你可以尝试解析错误和输出以找出你做错了什么,但我可以向你保证,这是一种令人困惑的体验。
你可以通过避免在你的第一个代码版本中使用列表解析来避免这种疯狂!使用传统的循环和迭代器工具以显而易见的方式编写逻辑。一旦你知道它有效,那么并且只有在那时你才应该将逻辑折叠到生成器表达式中,并且只有在你可以这样做而不避免错误处理的情况下。
这听起来像是很多额外的工作,但我在平常的代码编写中中遵循了这个确切的模式。我对生成器表达式的理解通常是我对抗经验不足的竞争对手的主要优势,但我总是先编写标准循环:我不能把浪费时间在调试生成器表达式中错误逻辑上。
Atas ialah kandungan terperinci Apakah struktur pemahaman senarai Python dan ungkapan penjana. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!