未加星标

CSI: Python Type System, episode 2

字体大小 | |
[开发(python) 所属分类 开发(python) | 发布者 店小二03 | 时间 2018 | 作者 红领巾 ] 0人收藏点击收藏
CSI: python Type System, episode2 [s02e02] Dealing With the Contravariance RelatedBug
CSI: Python Type System, episode 2
Illustration by Justyna Mieleszko

This is the second episode of the CSI: Python Type System series. The first episode can be foundhere.

In the first episode, we got to the bottom of the error reported by mypy: we understood exactly what was wrong with the initial code and why it wasn’t type-safe. Now, we need to do something about it.

The goal of this episode is not to give the ultimate solution to the problem, but to approach it from different perspectives and provide some (fairly simple) suggestions. Choosing and implementing the right one depends on the specific use case.

Strategy 0: Ignoring the Error and Just MovingOn a “do nothing” strategy

You can do that. It may lead to bugs. We are all adults here, I’m not stopping you :stuck_out_tongue_winking_eye: If you are sure the code won’t be exploited, just use # type: ignore and move on.

But what if we really wanted to make the code type-safe? Before I demonstrate ideas how to do so, I will expand the code with Human animal, which eats only Chocolate (is it paradise or hell? ):

Now, the “chocolate” exploit is real.

Strategy 1: Using `isinstance` Checks an awful anti-pattern strategy

How about adding a series of isinstance checks inside Animal.eat() method? They would delegate “eating” to eat method in an appropriate class (e.g. if food is a Meat instance, Dog.eat() would be called, etc.) or handle eating by itself, if nothing is matched. Something like this:

Let me say this once and for all: this code is hacky, unpythonic and just awful . It’s an anti-pattern that adds some kind of a method resolution algorithm on top of the Python’s built-in one. Having this in a codebase would be hell in terms of readability and maintenance. The fact that it wouldn’t properly fix our code (just like the next strategy, see below) is the least of our problems. So, just don’t do it, please :neutral_face:

Strategy 2: Adding an Abstract BaseClass a limited strategy

This strategy will use the abstract base class (or ABC) pattern. I will use ABC module . So, begin with changing the code by turning Animal class into an abstract base class: BaseAnimal . Now, make BaseAnimal.eat() method untyped (it’s safe, see below). Next, in the BaseAnimal put the rest of the things common to all animals.

I assume we want to instantiate animals other than Dog and Human as well. In this case, we need an additional class, e.g. OtherAnimal . food parameter of its eat() method is to be annotated to Food .

The code would look like this:

You can also implement the abstract base class pattern “manually” by adding `raise NotImplementedError` in `BaseAnimal.eat()`.

BaseAnimal.eat() is left unannotated. This is basically the same as annotating it with Any . It’s done to silence mypy. Is it safe? Yes, this is fine in this case ― it’s not a class that can be instantiated, so BaseAnimal.eat() will never be called. A food of a wrong type won’t be passed to it, then.

Now, instantiating a generic BaseAnimal is not possible. Thus, it’s impossible to feed a wrong food by passing a Dog (or a Human ) instance to a function where the instance of “other animal” (i.e. neither a dog nor a human) is expected. Now, it’s the OtherAnimal class that supports those “other animals”. Type of eat ’s food parameter is Food , but nothing inherits from this class, so the original issue is not repeated at a lower level.

Unfortunately, the chocolate exploit is still possible . We still can define a function that expects a BaseAnimal instance, that is an instance of one of the concrete classes inheriting from it:

So now, using forbidden Food subtype is possible. Sadly, using completely unrelated types is possible as well. It’s because BaseAnimal.eat ’s food type is unannotated ― its type is Any :

Also, within this approach we can create a BaseAnimal subtype with eat ’s food parameter of whichever type, e.g.:

Now, monkeys can eat only instances of Railroad , which is not a Food subtype. Believe me, they won’t be happy about it! :speak_no_evil:

Therefore, this solution is limited . To make the code type-safe, we additionally need to:

remember not to annotate anything with BaseAnimal , that is: do not make any function expect a BaseAnimal instance; keep track of the proper type of eat ’s food parameters in all new BaseAnimal subtypes ― mypy won’t do that for us; remember not to subclass OtherAnimal class (or, if you really want to, don’t make its instances eat subtypes of Food ).

Thanks to Pawe Stiasny and Bartosz Stalewski for helping me better understand the pitfalls of this strategy.

Strategy 3: E liminating one of the class hierarchies a strategy changing an initial assumption

So far, I did not challenge the following assumption behind the original code: it’s suitable to use class hierarchies to model both animals and food . It’s true that the assumption was related to the original issue. Yet, I think the flaw was not in the assumption, but in the code that misused both hierarchies by incorrectly mixing them. Strategy 4 and Strategy 5 ― which avoid the problem while keeping both hierarchies ― will, in my opinion, show that.

Now, I will give up implementing animals as a class hierarchy. Dog and Human can be implemented like this:

The infamous “chocolate exploit” just isn’t possible now. Right, but we just lost all connection between them. To restore it, we can do at least two things.

First, in the typing realm, we can reconnect both types using a Union type. With type defined this way, we can properly annotate all the places where an animal is expected:

Now, mypy reports the following error for the marked line:

error: Argument 1 to "eat" of "Dog" has incompatible

本文开发(python)相关术语:python基础教程 python多线程 web开发工程师 软件开发工程师 软件开发流程

代码区博客精选文章
分页:12
转载请注明
本文标题:CSI: Python Type System, episode 2
本站链接:https://www.codesec.net/view/611459.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 开发(python) | 评论(0) | 阅读(80)