Python 新手常犯的 3 个错误
上周末,我开始在exercism.io上指导Python 方向的同学。我之前并不确定会有什么收获,但过去一周,我已经指导了大约 50 个人,帮助他们的解决方案从“测试通过”提升到“测试通过、可读性强、Python风格”。我完全被迷住了。这真是太棒了。我打算专门写一篇关于那段经历的文章,但不是这篇。这篇文章主要想谈谈过去一周我看到的三个最常见的错误,以及一些可能更好的替代方案!那就让我们开始倒计时吧!
1. If 语句或循环的深度嵌套
# Calculating whether or not 'year' is a leap year
if year % 4 == 0:
if year % 100 == 0:
if year % 400 == 0:
return True
else:
return False
else:
return True
else:
return False
很多时候,我会从《Python 之禅》中引用一句话,作为对“受训者”(不要与“manitee”混淆)的反馈。每当我遇到这个问题时,我总是会这样开头:
扁平比嵌套更好。
如果你用不集中的目光看代码,只看形状而不看文字,你会看到一堆箭头出去又回来:
\
\
\
\
\
/
/
/
\
\
\
\
/
/
/
/
/
/
这绝对不是一件坏事,但它是一种“代码异味”,或者是一种蜘蛛感应,感觉某些东西可能被重构。
那么,除了嵌套,你还能做什么呢?有几件事可以尝试。首先,反转你的逻辑,使用“提前返回”来逐个剥离解空间中的小块。
if year % 400 == 0:
return True
if year % 100 == 0:
return False
if year % 4 == 0:
return True
return False
如果该数字能被 400 整除,我们就立即返回 true。否则,对于我们代码的其余部分,我们可以知道该年份肯定不能被 400 整除。因此,此时,任何其他能被 100 整除的年份都不是闰年。因此,我们通过返回 False 来剥开洋葱的这层外皮。
之后,我们可以知道这肯定不是 400或year
100的倍数,并且其余代码遵循相同的模式。
避免嵌套的另一种方法是使用“布尔运算符:” and, or, and not
。我们可以组合if
语句,从而省去一层嵌套!
if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
return True
else:
return False
当然,这引出了我们的第二件事……
2. 从 If 语句返回布尔值
我们从上面的最后一个例子开始:
if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
return True
else:
return False
每当你发现自己在写:
if something:
return True
else:
return False
您应该记住,语句的子句if
本身就是布尔值!
>>> year = 2000
>>> year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
True
那么,为什么不少输入一点并直接返回布尔运算的结果呢?
return (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0))
当然,此时,代码行可能会变得有点长,但是代码现在冗余度减少了一点!
3. 清单就像锤子——并非所有东西都是钉子
出现这种情况的可能方式有两种:
some_numbers = [1, 2, 5, 7, 8, ...]
other_numbers = [1, 3, 6, 7, 9, ...]
# Let's try to combine these two without duplicates
for number in other_numbers:
if number not in some_numbers:
some_numbers.append(number)
或者:
data = [["apple", 4], ["banana", 2], ["grape", 14]]
# What fruits do we have?
for item in data:
print(item[0])
# => "apple" "banana" "grape"
# How many grapes do we have?
for item in data:
if item[0] == "grape":
print(item[1])
# => 14
在第一种情况下,您需要跟踪几组项目,并希望将它们组合在一起而不重复。这时,使用就非常理想了set
。集合本身会跟踪其项目(尽管不会跟踪顺序,因此如果顺序很重要,请不要使用集合)。您可以使用内置set()
函数或花括号 ( {}
) 来声明它们。
some_numbers = {1, 2, 5, 7, 8}
other_numbers = {1, 3, 6, 7, 9}
# Sets use the 'binary or' operator to do "unions"
# which is where they take all of the unique elements
some_numbers | other_numbers
# => {1, 2, 3, 5, 6, 7, 8, 9}
# You can even add single items in!
some_numbers.add(10)
# => {1, 2, 5, 7, 8, 10}
# But adding a duplicate doesn't change anything
some_numbers.add(1)
# => {1, 2, 5, 7, 8, 10}
在第二种情况下,顺序可能同样不重要。您希望通过“标签”或其他方式跟踪一些数据,但能够将它们放在一起并在必要时列出。这次,您可能需要一个。您可以使用内置函数或花括号 ( )dict
创建它们。不过,这次,您需要用冒号分隔标签(键)和值。dict()
{}
fruits = {
"apples": 4,
"bananas": 2,
"grapes": 14,
}
您可以列出所有键(或值!)。
list(fruits.keys())
# => ["apples", "bananas", "grapes"]
list(fruits.values())
# => [4, 2, 14]
# Or both!
list(fruits.items())
# => [("apples", 4), ("bananas", 2), ("grapes", 14)]
您还可以向它询问特定键(或赋予它新值)。
# How many grapes are there?
fruits["grapes"]
# => 14
# Not anymore. I ate some.
fruits["grapes"] = 0
fruits["grapes"]
# => 0
使用列表,你的算法会循环遍历每个元素以找到正确的元素。dict
列表的查找速度非常快,所以,即使你的dict
列表有无数个水果,查找速度grapes
仍然非常快——而且输入起来很容易!没有循环!
行动呼吁
Exercism 需要导师!如果你认为自己可以成为一名优秀的导师(或者仅仅是一些简单练习的优秀导师),那就去他们的导师注册页面注册吧。目前,Rust、Golang 和 Elixir 的学习进度非常紧张,需要你的帮助!
文章来源:https://dev.to/rpalo/3-common-mistakes-that-python-newbies-make-4da7