编写优雅代码的7个不太为人所知的Python技巧

673 阅读10分钟

最近,我参加了一个Python编程训练营,由mathspp博客的作者Rodrigo Girão Serrão组织。这个Bootcamp使用了Code Advent of Code平台,这是一个很酷的编程活动,每年从12月1日至25日举行。每天,该网站都会发布两个新的谜题供大家解决。

总的来说,这是一个很好的学习经历。我不仅从组织者那里学到了大量的技巧,而且还从其他训练营学员那里学到了大量的技巧。大家都强调要最大限度地利用Python的内置函数和模块,以及编写干净和优雅的代码。

这篇文章既是我记录这些我学到的新技巧的方式,也是我与你分享它们的努力。它们帮助我大大提高了Python编程技能,我希望它们也能以同样的方式帮助你。你准备好了吗?让我们直接进入!

目录

  1. 被低估的enumerate()
  2. int() 函数比你想象的更有用
  3. 让我们断言:assert语句!
  4. 解压和打包--*操作符的力量
  5. 从字典中检索值?使用.get() 并抛弃[]!
  6. defaultdict - 用默认值创建字典
  7. 用任何你想要的方式排序sorted

1.被低估的enumerate()

循环遍历可迭代对象,如列表、图元或字符串,是非常常见的。我们中的一些人可能会做这样的事情。

>>> lst = ["a", "b", "c", "d"]>>> for idx in range(len(lst)):...     print(f"Index: {idx} --> Element: {lst[idx]}")

如果我们只是简单地通过列表,那么计算其长度范围是多余的。相反,我们可以使用enumerate() ,它是一个内置的函数,就是为了这样一个目的。enumerate() 函数返回一个枚举对象,它持有一个可迭代对象的索引和元素对。

>>> enumerate(lst)<enumerate at 0x1dde1211e80>

如果你把它包在list() ,你可以看到这些对。

>>> list(enumerate(lst))[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]

使用enumerate() ,先前的代码可以重写为。

>>> lst = ["a", "b", "c", "d"]>>> for idx, item in enumerate(lst):...     print(f"Index: {idx} --> Element: {item}")

假设你希望起始索引是1,而不是0。你可以用可选的start 参数来指定。

>>> lst = ["a", "b", "c", "d"]>>> for idx, item in enumerate(lst, start=1):...     print(f"Index: {idx} --> Element: {item}")

2.int()函数比你想象的更有用

你可能使用过int(),将一个字符串或一个浮点数转换成一个整数。

# Converting a string into an integer>>> var = "5">>> print(int(var))>>> print(type(int(var)))

对于一个字符串来说,它必须是一个有效的整数的字符串表示,否则会引发错误。

# Converting a string into an integer>>> var = "5.4321">>> print(int(var))>>> print(type(int(var)))

有趣的是,当把一个字符串表示的整数转换为整数时,int() 函数允许存在空白处。

>>> int("5\n") == int("   5  ") == int("\t\n 5 \n\t ") == 5

对于浮点数,int()函数会将小数点截去。

# Converting a float into an integer>>> var = 5.4321>>> print(int(var))>>> print(type(int(var)))

除了字符串和浮点数,你知道吗,int()也可以用来将二进制数字解析为整数?我们只需要指定base=2 参数。

# Converting binary numbers into an integer>>> print(int("1000", base=2))>>> print(int("111110100", base=2))

3.让我们来断言那个断言语句!

assert 语句是一个布尔表达式,检查一个条件是否为真。条件在assert 关键字之后。如果条件为真,什么都不会发生,程序将进入下一行代码。

>>> x = "Hello, World!">>> assert x == "Hello, World!">>> print(x)

然而,如果条件为假,代码就会停止,并触发一个错误。

>>> x = "Hello, World!">>> assert x == "Hi, World!">>> print(x)

你还可以在条件后面指定一个自定义的错误信息。

>>> x = "Hello, World!">>> assert x == "Hi, World!", "Uh oh, the condition is not met!">>> print(x)

所以,一般的语法是这样的。

assert <insert condition>, <insert optional error message>

因为只要不满足条件,程序就会停止,所以assert语句是一个很好的调试工具--可以看到你的代码的哪些部分失败了,为什么?例如,你可以用它来检查数据类型或进入函数的特定输入值,或者在给定某些固定输入的情况下,检查函数的输出值。

4.解压和打包--*运算符的力量

解压和打包是Python中有用且方便的功能。你可以将存储在迭代器中的值解压,如赋值运算符右边的图元、字符串和列表,并将它们赋给赋值运算符左边的变量。这些变量将根据它们在迭代表中的相对位置被赋值。

# Unpacking a tuple>>> a, b, c = (1, 2, 3)>>> print(a)>>> print(b)>>> print(c)

另一方面,打包使用* 操作符,它允许你在一个变量中打包多个值。

# Packing with a tuple>>> a, *b = (1, 2, 3)>>> print(a)>>> print(b)

除了图元、列表和字符串,打包和解包也可以适用于生成器对象、集合和字典。当你想在变量之间交换数值或同时进行多个赋值时,这是一个很方便的工具。它使你的代码更有可读性。

# Swapping values between variables>>> a = 1>>> b = 2>>> a, b = b, a>>> print(a)>>> print(b)

如果你有兴趣了解更多,这里有一篇关于打包和解包的文章,解释得很清楚。

5.从字典中检索值?使用 .get() 和 ditch []!

假设你有一个字典,它将一个键映射到它相应的值。在下面的例子中,为了检索num_to_words 字典中一个键的英文单词,你可能想使用方括号。

>>> num_to_words = {1: 'one', 2: 'two', 3: 'three'}>>> print(num_to_words[2])

如果你想检索num_to_words 词典中不存在的键的英文单词,该怎么办?是的,你猜对了--它引发了一个错误!

>>> num_to_words = {1: 'one', 2: 'two', 3: 'three'}>>> print(num_to_words[4])

与方括号相比,.get() 方法将是一个更稳健和实用的选择。.get() 方法返回字典中存在的键的值,与使用方括号没有区别。

>>> num_to_words = {1: 'one', 2: 'two', 3: 'three'}>>> print(num_to_words.get(2))

现在,当查询字典中一个不存在的键的值时,.get() 的好处就明显了。它不会触发KeyError 并破坏你的代码,而是默认返回一个值None 并保持你的代码运行。

>>> num_to_words = {1: 'one', 2: 'two', 3: 'three'}>>> print(num_to_words.get(4))

.get() 的好处是,你甚至可以指定一个自定义的值,在找不到键的时候返回。

>>> num_to_words = {1: 'one', 2: 'two', 3: 'three'}>>> print(num_to_words.get(4, "Uh oh... key is not found!"))

.get() 的一个用例是在用字典中的相应值替换列表中的值时非常有用。在下面的例子中,我们想用num_to_words 字典将num_list 中的每个元素替换成其相应的英文单词。如果使用方括号,代码就会中断,因为4 不在字典的键中。

# Using square brackets>>> num_to_words = {1: 'one', 2: 'two', 3: 'three'}>>> num_list = [1, 2, 3, 4, 5, 6]>>> word_list = [num_to_words[num] for num in num_list]>>> print(word_list)

然而,如果我们使用.get() 方法来代替,代码就可以工作。

# Using .get() method with default value of None>>> num_to_words = {1: 'one', 2: 'two', 3: 'three'}>>> num_list = [1, 2, 3, 4, 5, 6]>>> word_list = [num_to_words.get(num) for num in num_list]>>> print(word_list)

也许,.get() 对不存在的键返回None ,还是不理想。我们可以指定.get() 方法的第二个参数,这样它就会返回键本身,如果它在num_to_words 字典中找不到。

# Using .get() method with customised default value>>> num_to_words = {1: 'one', 2: 'two', 3: 'three'}>>> num_list = [1, 2, 3, 4, 5, 6]>>> word_list = [num_to_words.get(num, num) for num in num_list]>>> print(word_list)

6.defaultdict - 创建具有默认值的字典

另一种避免因查询字典中不存在的键而出现KeyError 的方法是使用内置的collections 模块中的defaultdict

使用defaultdict ,你可以指定一个 "默认值工厂",这个函数可以为任何不存在的键返回我们想要的默认值。这里,我们在初始化一个defaultdict 对象时,使用一个lambda 函数来做这件事。

>>> num_to_words_dd = defaultdict(lambda: "Uh oh... key is not found in `defaultdict`!")>>> num_to_words_dd[1] = 'one'>>> num_to_words_dd[2] = 'two'>>> num_to_words_dd[3] = 'three'>>> print(num_to_words_dd)

当查询一个defaultdict对象中存在的键时,它与普通的字典对象没有什么区别。为了说明defaultdict 的工作原理,下面的代码片断使用了方括号,而不是.get() 方法。

>>> num_to_words_dd[2]

然而,如果你查询一个不存在的键,默认值会被返回。

>>> num_to_words_dd[5]

你也可以用intlist 关键字来初始化一个defaultdict 。如果你用int 来初始化它,那么就会创建一个默认值为0的defaultdict对象。当你查询一个不存在的键时,它会返回0。

>>> counter = defaultdict(int)>>> lst = [0, 1, 2, 2, 3, 1, 1, 0]>>> for num in lst:...     counter[num] += 1>>> print(counter[0]) # Key that exists>>> print(counter[5]) # Key that does not exist

同样地,如果你用list 来初始化它,那么一个defaultdict对象被创建,默认值为空列表,查询一个不存在的键会返回[]

>>> country_list = [('AU','Australia'), ('CN','China'), ...                 ('FR','France'), ('SG', 'Singapore'), ...                 ('US', 'United States'), ('PT', 'Portugal')]>>> country_dict = defaultdict(list)>>> for code, country in country_list:...     country_dict[code].append(country)>>> print(country_dict['AU']) # Key that exists>>> print(country_dict['BX']) # Key that does not exist

总而言之,只要你需要创建一个字典,并且每个元素的值都需要有一定的默认值,那么defaultdict 就是一个很好的选择。

7.用sorted() 以任何方式排序

如果你想对一个可迭代的东西进行排序,比如一个列表、一个字符串或一个元组,你可以使用sorted() 函数。它返回一个带有原始元素的排序方式的列表,而不改变原始序列。

对于字符串,它返回一个字符列表,以标点符号或空格为先,然后是按字母顺序排列的大写字母和小写字母。

>>> sorted("Hello, World!")

对于数字列表,它返回一个按升序排序的序列。

>>> sorted([5, 2, 4, 1, 3])

对于字符串列表,它返回一个按字母顺序排序的序列,以第一个字符为基础。

>>> fruits = ["apple", "watermelon", "pear", ...           "banana", "grapes", "rockmelon"]>>> sorted(fruits)

你也可以通过在sorted() 函数中指定reverseTrue 来反转这个序列。

>>> sorted(fruits, reverse=True)

你还知道你可以自定义你希望对迭代器进行排序的方式吗?你可以指定一个函数并将其分配给sorted 函数中的key 参数。例如,如果你想按每个词的长度以升序对fruits 列表进行排序,你可以这样做。

>>> sorted(fruits, key=len)

或者,如果你想按每个词中字母 "n "的数量对水果进行排序,你可以这样指定一个lambda 函数。

>>> sorted(fruits, key=lambda x: x.count('n'))

我们也可以通过引用另一个迭代器来进行排序。在下面的例子中,我们根据prices 字典将水果按价格递增进行排序。

>>> prices = {"apple": 1.4, "watermelon": 4, "pear": 1.2, ...           "banana": 2.3, "grapes": 3.5, "honeydew": 2.6}>>> sorted(fruits, key=lambda x: prices.get(x))

好了!现在就到此为止了。谢谢你走到这一步。我希望你已经学会了编写优雅代码的简单技巧,并最大限度地利用了 Python 中鲜为人知的内置函数和模块。

对于其中的一些技巧,我只是触及了表面;它们可以单独写成一篇文章。因此,我强烈建议你查看更多的相关资源以了解更多。

参考资料

  1. Python问题解决训练营2021,Rodrigo Girão Serrão
  2. Enumerate me, Pydon't, 2021年4月6日, Rodrigo Girão Serrão
  3. Python中的解包。超越平行赋值,2021年9月19日,Leodanis Pozo Ramos
Want to Connect?