Python 中的 Null 模式未得到充分利用?
我时不时地会遇到这样的代码:
foo = Foo()
...
if foo.bar is not None and foo.bar.baz == 42:
shiny_happy(...)
在我看来,这似乎不太像Python。
在 Objective-C 中,你可以向 nil 发送消息并得到 nil 作为答案。我一直认为这非常方便。当然,可以在 Python 中实现 Null 模式,但是从 Google 给出的结果来看我,看来这不是很广泛使用。这是为什么呢?
或者甚至更好 - 让 None.whatever 返回 None 而不是引发异常是一个坏主意吗?
Every now and then I come across code like this:
foo = Foo()
...
if foo.bar is not None and foo.bar.baz == 42:
shiny_happy(...)
Which seems, well, unpythonic, to me.
In Objective-C, you can send messages to nil and get nil as the answer. I've always thought that's quite handy. Of course it is possible to implement a Null pattern in Python, however judging from the results Google's given me, it seems this is not very widely used. Why's that?
Or even better—would it be a bad idea to let None.whatever return None instead of raising an exception?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
PEP 336 - Make None Callable 提出了类似的功能:
被拒绝的原因很简单,“它被认为是一个在调用时 None 不会引发错误的功能”。
PEP 336 - Make None Callable proposed a similar feature:
The reason for why it was rejected was simply "It is considered a feature that None raises an error when called."
抱歉,该代码是 pythonic 的。我认为大多数人都会同意 Python 中“显式优于隐式”。与大多数语言相比,Python 是一种易于阅读的语言,人们不应该通过编写神秘的代码来打败它。把意思说得很清楚。
在此代码示例中,很明显 foo.bar 在此代码路径上有时为 None,并且我们仅在它不是 None 且 .baz == 42 时才运行shiny_happy()。任何人都非常清楚这里发生了什么以及为什么。对于空模式或 try ... except 此处发布的答案之一中的代码来说,情况并非如此。如果您的语言(例如 Objective-C 或 javascript)强制执行 null 模式,这是一回事,但在根本不使用它的语言中,它只会造成混乱和难以阅读的代码。使用 Python 编程时,请像 Python 专家那样做。
I'm sorry, but that code is pythonic. I think most would agree that "explicit is better than implicit" in Python. Python is a language that is easy to read compared to most, and people should not defeat that by writing cryptic code. Make the meaning very clear.
In this code sample, it is clear that foo.bar is sometimes None on this code path, and that we only run shiny_happy() if it is not None, and .baz == 42. Very clear to anyone what is going on here and why. The same can not be said for the null pattern, or the try ... except code in one of the answers posted here. It's one thing if your language, like Objective-C or javascript enforces a null pattern, but in a language where it is not used at all, it will just create confusion and code that is difficult to read. When programming in python, do as the pythonistas do.
你就不能尝试一下吗? Pythonic 方式说请求宽恕比请求许可更容易。
所以:
或者在不可能消除比预期更多的异常的情况下执行此操作:
Couldn't you do a try except? The Pythonic way says It is Easier to Ask for Forgiveness than Permission.
So:
Or do it without possibly silencing more exceptions than desired:
这种便利性的代价是无法尽早检测到愚蠢的错误,尽可能接近有缺陷的代码行。
我认为这个特殊功能的便利性充其量只是偶尔出现,而愚蠢的错误总是会发生。
当然,类似 SQL 的 NULL 不利于测试,因为测试实际上依赖于命题的真假。考虑来自 unittest.py 的这段代码:
现在假设我有一个执行此操作的测试:
假设
connect
错误地返回 null。如果我使用“空模式”,或者语言内置了它,并且conn
为 null,则conn.isOpen()
为 null,并且 < code>not conn.isOpen() 也为 null,因此断言通过,即使连接显然未打开。我倾向于认为 NULL 是 SQL 最糟糕的功能之一。事实上,
null
在其他语言中默默地传递任何类型的对象也好不了多少。 (托尼·霍尔(Tony Hoare)称空引用“我的十亿美元错误”。)我们需要更少的诸如此类,仅此而已。The handiness comes at the expense of dumb mistakes not being detected at the earliest possible time, as close to the buggy line of code as possible.
I think the handiness of this particular feature would be occasional at best, whereas dumb mistakes happen all the time.
Certainly a SQL-like NULL would be bad for testing, which really banks on propositions being either true or false. Consider this code, from unittest.py:
Now suppose I have a test that does this:
Suppose
connect
erroneously returns null. If I'm using the "null pattern", or the language has it built-in, andconn
is null, thenconn.isOpen()
is null, andnot conn.isOpen()
is null too, so the assertion passes, even though the connection clearly is not open.I tend to think
NULL
is one of SQL's worst features. And the fact thatnull
silently passes for an object of any type in other languages is not much better. (Tony Hoare called null references “my billion-dollar mistake”.) We need less of that sort of thing, not more.正如其他人所说,PEP 336 描述了为什么会出现这种行为。
添加类似 Groovy 的“安全导航操作符”(< code>?.) 在某些情况下也许可以使事情变得更加优雅:
As others have said, PEP 336 describes why this is the behavior.
Adding something like Groovy's "safe navigation operator" (
?.
) could perhaps make things more elegant in some cases:我认为这不是一个好主意。原因如下。假设您
假设
getValue()
返回一个数字,如果未找到,则返回None
。现在假设
foo
不是偶然的,也不是错误。因此,即使存在错误,它也会返回None
并继续。换句话说,您无法再区分是否没有值(返回
None
)或是否存在错误(foo
wasNone
开始)。您正在使用不受例程本身控制的事实来更改例程的返回契约,最终覆盖其语义。I don't think it's a good idea. Here's why. suppose you have
Suppose
getValue()
returns a number, orNone
if not found.Now suppose
foo
was none by accident or a bug. As a result, it would returnNone
, and continue, even if there's a bug.In other words, you are no longer able to distinguish if there's no value (returns
None
) or if there's an error (foo
wasNone
to begin with). You are altering the return contract of a routine with a fact that is not under control of the routine itself, eventually overwriting its semantics.这就是为什么我认为这不是一个好主意:
你会如何处理这个案子?
Here's why I don't think that's a good idea:
How would you handle this case?
虽然我完全同意这里的其他答案,即在请求成员时
None
引发异常是一件好事,但这是我有时使用的一个小模式:While I wholeheartedly agree with other answers here that say that it's a good thing that
None
raises an exception when asked for a member, this is a little pattern I sometimes use:我个人认为抛出异常是应该发生的。假设由于某种原因你正在用 Python 编写导弹软件。想象一下原子弹,假设有一个名为explode(timeToExplode)的方法,并且timeToExplode作为None传入。我想当你输掉战争的时候你会不高兴,因为你在测试中没有发现这一点。
Personally I believe throwing an exception is what should happen. Lets say for some reason you are writing software for missiles in Python. Imagine the atomic bomb and lets say there was a method called explode(timeToExplode) and timeToExplode got passed in as None. I think you would be unhappy at the end of the day when you lost the war, because you didn't find this in testing.
可以使用 None 解析为 False 的事实来清理上面的内容:
The above can be cleaned up using the fact that None resolves to False:
我不是一个Python程序员(刚刚开始学习这门语言),但这似乎是关于何时返回错误或抛出异常的永无休止的讨论,事实是(当然这只是一个意见)这取决于。
异常应该用于特殊情况,因此将对罕见情况的检查移到主代码块之外。当未找到值是常见情况时,不应使用它们(想象一下请求树的左子节点,在许多情况下——所有叶子——它将为空)。对于返回值集的函数,还有第三种情况,您可能只想返回一个有效的空集以简化代码。
我相信遵循上述建议,代码会更加清晰。当这种情况很少见时,您无需担心 null(将调用异常),因此较少使用的代码会移出主块。
当 null 是有效返回值时,处理它是在主控制流内,但这很好,因为它是常见情况,并且是主算法的一部分(不要遵循图中的 null 边)。
在第三种情况下,当从可能不返回值的函数请求值时,返回空集可以简化代码:您可以假设返回的容器存在并处理所有找到的元素,而无需在代码中添加额外的检查。
再说一次,这只是一个观点,但作为我的观点,我倾向于遵循它:)
I am not a python programmer (just starting to learn the language) but this seems like the never-ending discussion on when to return error or throw exception, and the fact is that (of course this is only an opinion) it depends.
Exceptions should be used for exceptional conditions, and as such move the checks for rare situations outside of the main block of code. They should not be used when a not-found value is a common condition (think of requesting the left child of a tree, in many cases --all leaves-- it will be null). There is yet again a third situation with functions that return sets of values, where you might just want to return a valid empty set to ease the code.
I believe that following the above advice, code is much more legible. When the situation is rare you do not need to worry about null (an exception will be called) so less used code is moved out of the main block.
When null is a valid return value, processing it is within the main flow of control, but that is good, as it is a common situation and as such part of the main algorithm (do not follow a null edge in a graph).
In the third case, when requesting values from a function that can possibly return no values, returning an empty set simplifies the code: you can assume that the returned container exists and process all found elements without adding the extra checks in the code.
Then again, that is just an opinion, but being mine I tend to follow it :)
免责声明:这可能不是一个好主意,因为它不太可读,但我只是将其添加到此处以保持完整性。
利用
and
的工作原理如果您想将任意函数
transform
应用于可为 null 的变量var:Optional[T]
,这里有一个表达式。 ,如果变量不是None
,则返回该计算的结果,否则返回None
(本着 Java 的Optional
的精神):这适用于任何函数,而不仅仅是布尔上下文中的函数(例如
if
子句的条件)。原因是,and
运算符定义如下(来自文档):因此,如果
var
为None
,则var 和 transform(var)
将计算为None
(不计算>transform(var)
,因为 Python 中的布尔运算符是短路的),如果不是,它将计算出transform(var)
的结果并返回。不像正确的 null 模式那么简洁,但仍然比
示例
链接
更简洁在 Python 3.8+ 中,借助赋值表达式,您甚至可以链接此模式,即您可以组成一系列函数并返回
None
如果任何计算返回None
,否则返回最终结果:一旦任何中间步骤(或
>var
本身)返回None
。作为副作用,最终结果也将存储在r
中。示例
如果我们尝试一次性应用
f
、g
和h
组合,如果其中一个中间步骤返回无:
但是,我们可以使用上面的技巧:
Disclaimer: this is probably not a very good idea, as it isn't very readable, but I'm just adding it here for completeness.
Exploit how
and
worksHere is an expression that works if you want to apply an arbitrary function
transform
to a nullable variablevar: Optional[T]
, returning the result of that computation if the variable is notNone
, or elseNone
(in the spirit of Java'sOptional
):This works for any function, not just in a boolean context (e.g. the condition to an
if
clause). The reason is, theand
operator is defined as follows (from the docs):Thus, if
var
isNone
,var and transform(var)
will evaluate toNone
(without evaluatingtransform(var)
, since boolean operators in Python are short-circuiting), and if it is not, it will evaluate to the result oftransform(var)
and return that.Not quite as concise as a proper null pattern, but still less verbose than
Example
Chaining
In Python 3.8+, with the help of assignment expressions, you can even chain this pattern, i.e. you can compose a sequence of functions and return
None
if any of the computations returnedNone
, or return the final result otherwise:This will evaluate to
None
as soon as any of the intermediate steps (orvar
itself) returnsNone
. As a side effect, the final result will also be stored inr
.Example
If we try apply
f
,g
andh
composed in one go, we will get an error if one of the intermediate steps returnsNone
:But, instead, we can use the above trick: