[Python-ideas] Making it easy to prepare for PEP479

Steven D'Aprano steve at pearwood.info
Tue May 19 11:25:47 CEST 2015


On Tue, May 19, 2015 at 03:46:49PM +1000, Chris Angelico wrote:

> > The only solution I can come up with is to use the inspect module to
> > fetch the generator's source code and scan it for "raise StopIteration".
> > Parsing the AST will also work, or even the byte-code at a pinch, but
> > the source code is easiest:
> >
> > assertFalse("raise StopIteration" in source)
> >
> > That will fail if the generator raises StopIteration directly regardless
> > of version. It doesn't catch *all possible* violations, e.g.:
> 
> More significant example: It doesn't catch a codebase that has some
> functions which are used in generators and others which are used in
> class-based iterators.

Obviously I only sketched a solution. The person writing the tests has 
to distinguish between functions or methods which must not call "raise 
StopIteration", and test them, while avoiding testing those which may 
use raise. They may want to test more than just the generator function 
themselves, e.g. any functions they call.

In principle, if you're reading the code or the AST, you can do a static 
analysis to automatically detect what functions it calls, and scan them 
as well, but that's a lot of effort for mere unit tests, and the chances 
are that your test code will be buggier than your non-test code. Easier 
to just add the called functions to a list of functions to be checked.

The person writing the tests must decide how much he cares about this. 
"Do the simplest thing that can possibly work" applies to tests as well 
as code (tests *are* code).

(In my opinion, just by *reading* this thread, Ram has already exceeded 
the amount of time and energy that these tests are worth.)


> > So, I believe that the whole __future__ directive is a red herring, and
> > doesn't actually help Ram do what he wants, which is to write tests
> > which will fail if his generators call raise StopIteration regardless of
> > what version of Python he runs the test under.
> 
> Okay. So how do you ensure that Python 3.7 and Python 3.4 can both run
> your code?

If I am right that the future directive is irrelevant, then you simply 
*don't include the future directive*.

Or you split the code into parts that don't require the directive, and 
parts that do, and put them in different files, then conditionally 
import the second set, either in a try...except or if version... block.

Or you write one file: test.py, and run your tests with a wrapper 
script which duplicates that file and inserts the future directive:

# untested
cp test.py test479.py
sed -i '1i from __future__ import feature' test479.py
python -m unittest test.py
python -m unittest test479.py


Combine and adjust as needed.


-- 
Steve


More information about the Python-ideas mailing list