This is a valid RSS feed.
This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.
have.</p><div class="figurep"><a href="https://nedbatchelder.com/code/misc/r ...
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="https://nedbatchelder.com/rssfull2html.xslt" media="screen" ?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://purl.org/rss/1.0/">
<channel rdf:about="https://nedbatchelder.com/blog">
<title>Ned Batchelder's blog</title>
<link>https://nedbatchelder.com/blog</link>
<description>Ned Batchelder's personal blog.</description>
<dc:language>en-US</dc:language>
<image rdf:resource="https://nedbatchelder.com/pix/rss-banner.gif"/>
<items>
<rdf:Seq>
<rdf:li resource="https://nedbatchelder.com/blog/202403/is_this_for_autistic_people.html"/><rdf:li resource="https://nedbatchelder.com/blog/202403/does_python_have_pointers.html"/><rdf:li resource="https://nedbatchelder.com/blog/202402/updated_multiparameter_interactive_jupyter_notebook.html"/><rdf:li resource="https://nedbatchelder.com/blog/202402/one_way_to_package_python_code_right_now.html"/><rdf:li resource="https://nedbatchelder.com/blog/202401/i_am_at_liberty.html"/><rdf:li resource="https://nedbatchelder.com/blog/202401/you_probably_dont_need_to_learn_c.html"/><rdf:li resource="https://nedbatchelder.com/blog/202401/randomly_subsetting_test_suites.html"/><rdf:li resource="https://nedbatchelder.com/blog/202312/coveragepy_with_sysmonitoring.html"/><rdf:li resource="https://nedbatchelder.com/blog/202312/realworld_matchcase.html"/><rdf:li resource="https://nedbatchelder.com/blog/202311/say_it_again_values_not_expressions.html"/>
</rdf:Seq>
</items>
</channel>
<image rdf:about="https://nedbatchelder.com/pix/rss-banner.gif">
<title>Ned Batchelder's blog</title>
<link>https://nedbatchelder.com/blog</link>
<url>https://nedbatchelder.com/pix/rss-banner.gif</url>
</image>
<item rdf:about="https://nedbatchelder.com/blog/202403/is_this_for_autistic_people.html">
<title>Is this for autistic people?</title>
<link>https://nedbatchelder.com/blog/202403/is_this_for_autistic_people.html</link>
<dc:date>2024-03-20T11:46:00-04:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>Special Olympics swimming season started this past weekend. A new athlete
joined us, a young boy I’ll call Bryan. He asked me a question that has stuck
with me.</p><p>Bryan is 12 or so, with the slightly goofy look of a boy growing into his
body. He has braces on his too-large teeth. It was his first time swimming with
us, so we needed to show him the locker room, how to get out to the pool, and so
on. He was serious and inquisitive about all of these things that were new to
him.</p><p>We got out on the deck and started stretching with the other athletes, most
of whom don’t look like Bryan. They are older and have a variety of
intellectual disabilities. Bryan surveyed the group then turned to me and asked
the question: “is this for autistic people?”</p><p>I had only just met Bryan. I didn’t know his formal diagnosis (or if he even
had one), and I didn’t know how he thought of himself. When he asked the
question, I didn’t know if he was including himself in the category of autistic
people or not, so I wanted to answer carefully.</p><p>Did he mean, “are autistic people allowed here?” or, “is this only for
autistic people?” Maybe he meant, “are all of these swimmers autistic?” or even,
“will being here mean I am autistic?”</p><p>I told him that it was for autistic people, that my own son Nat was here and
Nat is autistic. Bryan accepted this in his sober way and continued on with the
practice.</p><p>Later I talked with his mom about the question and asked her if Bryan
identified as autistic. She said that he did, but it was a recent awareness for
him. In school he’s in a typical integrated classroom.</p><p>Bryan did well at the practice, and called me “Coach Ned.” His mom was
really appreciative of the group as a whole and was clearly pleased.</p><p>I’ve been thinking about Bryan and his question: “is this for autistic
people?” He’s young, and finding his way in the world in so many ways. We all
need to figure out who we are, what groups we belong to, where we fit. We all
encounter difficulties in one way or another working all that out, and it’s
life-long process. Bryan has a lot to work on. I hope it isn’t too hard.</p>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202403/does_python_have_pointers.html">
<title>Does Python have pointers?</title>
<link>https://nedbatchelder.com/blog/202403/does_python_have_pointers.html</link>
<dc:date>2024-03-11T07:27:22-04:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>People sometimes ask, “Does Python have pointers?” I hate to be the typical
senior engineer, but this is one of those questions where the answer is, it
depends what you mean by pointer.</p><p>The classic definition of a pointer is: a variable that holds the address
of something else and that you can use to work with that something else.
In very broad pseudo-code, it would be something like this:
</p><blockquote class="code"><pre>myvar = SOMETHING;<br>mypointer = get_address_of(myvar);<br>print(get_value_via_pointer(mypointer));<br>## output is SOMETHING<br></pre></blockquote><p>This is useful because we can use a pointer to refer to data, setting the
pointer in one part of the code with whatever logic we need to decide what data
it should point to. Then elsewhere we can use the pointer without having to know
what it’s referring to or how the decision was made. The pointer gives us an
indirection that lets us separate concerns and write more modular code.</p><p>Many programming languages provide a pointer facility like this. For
example, in C, the get_address_of() operation is ampersand, and the
get_value_via_pointer() operation is star, and our code snippet would be:</p><blockquote class="code"><pre class="c"><span class="kt">int</span><span class="w"> </span><span class="n">myvar</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">17</span><span class="p">;</span><span class="w"></span>
<br><span class="kt">int</span><span class="w"> </span><span class="o">*</span><span class="n">mypointer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&</span><span class="n">myvar</span><span class="p">;</span><span class="w"></span>
<br><span class="n">print_int</span><span class="p">(</span><span class="o">*</span><span class="n">mypointer</span><span class="p">);</span><span class="w">      </span><span class="c1">// outputs 17</span>
<br></pre></blockquote><p>Other languages like C++, C#, Go, Rust, Pascal, and even Fortran have similar
capabilities.</p><p>OK, so what about Python? In one sense, Python doesn’t have a pointer
concept like this. You could say that get_address_of() is provided by Python’s
id() function, since (in CPython at least) it returns the memory address of the
data:</p><blockquote class="code"><pre class="python"><span class="n">myvar</span> <span class="o">=</span> <span class="mi">17</span>
<br><span class="n">mypointer</span> <span class="o">=</span> <span class="nb">id</span><span class="p">(</span><span class="n">myvar</span><span class="p">)</span>   <span class="c1"># ** not useful</span>
<br></pre></blockquote><p>But Python has no inverse operation: there’s no get_value_via_pointer()
that can get you myvar given mypointer.</p><p>So Python doesn’t have the classic pair of operations to be able to work with
pointers explicitly. But on the other hand, <em>every</em> variable in Python
is a pointer, because variables in Python are <a href="https://nedbatchelder.com/text/names1.html">names
that refer to objects</a>.</p><p>In Python, our simple example looks like this:</p><blockquote class="code"><pre class="python"><span class="n">myvar</span> <span class="o">=</span> <span class="mi">17</span>
<br><span class="n">mypointer</span> <span class="o">=</span> <span class="n">myvar</span>
<br><span class="nb">print</span><span class="p">(</span><span class="n">mypointer</span><span class="p">)</span>    <span class="c1"># outputs 17</span>
<br></pre></blockquote><p>When someone asks, does Python have pointers, perhaps the best answer is: it
doesn’t have explicit pointers like some other languages, but everything is
implicitly a pointer. So you have the power of pointers to use when you need
them: you can have multiple data structures, then assign a variable to one you
choose, and use the variable later. You’ve achieved the separation of “which
data” from “work with the data” that pointers provide.</p><p>Maybe this is yet another case of
<a href="https://nedbatchelder.com/blog/202301/same_words_different_meanings.html">Same words, different meanings</a>.</p><p>Note:</p><ul>
<li>Some languages like C also allow pointer arithmetic to adjust a pointer from
one item in an array to another. Python’s references don’t allow for that.</li>
<li>Python’s standard library provides <a rel="external noopener" href="https://docs.python.org/3/library/ctypes.html">ctypes</a>, which
is great for interfacing with native C code, exposing details there including C
pointers. This does not count as Python having explicit pointers.</li>
</ul>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202402/updated_multiparameter_interactive_jupyter_notebook.html">
<title>Updated multi-parameter interactive Jupyter notebook</title>
<link>https://nedbatchelder.com/blog/202402/updated_multiparameter_interactive_jupyter_notebook.html</link>
<dc:date>2024-02-12T14:21:11-05:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>A few years ago I wrote
<a href="https://nedbatchelder.com/blog/201610/multiparameter_jupyter_notebook_interaction.html">Multi-parameter Jupyter notebook interaction</a> about a
Jupyter notebook. It worked at the time, but when I dusted it off recently, it
didn’t. I’ve renovated it and cleaned it up a little, and now it works
again.</p><p>It’s a <a rel="external noopener" href="/code/misc/retirement_sample2.ipynb">Jupyter notebook</a> with a simulation of
late-career money flows to figure out possibilities for retirement. It uses
widgets to give you sliders to adjust parameters to see how the outcome changes.
It also lets you pick one of the parameters to auto-plot with multiple values,
which gives a more visceral way to understand the effect different variables
have.</p><div class="figurep"><a href="https://nedbatchelder.com/code/misc/retirement_sample2.ipynb"><figure><picture><source type="image/webp" srcset="https://nedbatchelder.com/iv/webp/pix/retirement_sliders2.png.webp"><img src="https://nedbatchelder.com/pix/retirement_sliders2.png" alt="Screenshot of the sliders and resulting plot of outcomes" width="1122" height="1456"></picture></figure></a></div><p>You can <a rel="external noopener" href="/code/misc/retirement_sample2.ipynb">get the notebook itself</a> if you like.</p>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202402/one_way_to_package_python_code_right_now.html">
<title>One way to package Python code right now</title>
<link>https://nedbatchelder.com/blog/202402/one_way_to_package_python_code_right_now.html</link>
<dc:date>2024-02-10T06:50:00-05:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>A year or so ago, I couldn’t find a step-by-step guide to packaging a Python
project that didn’t get bogged down in confusing options and choices, so I wrote
my own: <a rel="external noopener" href="https://github.com/nedbat/pkgsample">pkgsample</a>. After I wrote it, I found the
<a rel="external noopener" href="https://packaging.python.org/en/latest/tutorials/packaging-projects/">PyPA Packaging Python Projects tutorial</a>, which is
very good, so I never made a post here about my sample.</p><p>Since then, I’ve shown my sample to people a number of times, and they liked
it, so I guess it’s helpful. Here’s what I wrote about it back when I first created it:</p><p class="bulletsep">• • •</p><p>The Python packaging world is confusing. There are decades of history and
change. There are competing tools, with new ones arriving frequently. I don’t
want to criticize anyone, let’s just take it as a fact of life right now.</p><p>But I frequently see questions from people who have written some Python code,
and would like to get it packaged. They have a goal in mind, and it is not to
learn about competing tools, intricate standards, or historical artifacts.
They are fundamentally uninterested in the mechanics of packaging. They just
want to get their code packaged.</p><p>There are lots of pages out there that try to explain things, but they all
seem to get distracted by the options, asking our poor developer to choose
between alternatives they don’t understand, with no clear implications.</p><p>I’m also not criticzing the uninterested developer. I am that developer! I
don’t know what all these things are, or how they compete and overlap: build,
twine, hatch, poetry, flit, wheel, pdm, setuptools, distutils, pep517, shiv,
<a rel="external noopener" href="https://packaging.python.org/en/latest/key_projects/">etc</a>, <a rel="external noopener" href="https://chadsmith.dev/python-packaging/">etc</a>.</p><p>I just want someone to tell me what to do so my code will install on users’
machines. Once that works, I can go back to fixing bugs, adding features,
writing docs, and so on.</p><p>So I wrote <a rel="external noopener" href="https://github.com/nedbat/pkgsample">pkgsample</a> to be the instructions I
couldn’t find. It’s simple and stripped down, and does not ask you to make
choices you don’t care about. It tells you what to do. It gives you one way to
make a simple Python package that works right now. It isn’t THE way. It’s A way.
It will probably work for you.</p>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202401/i_am_at_liberty.html">
<title>I am at liberty</title>
<link>https://nedbatchelder.com/blog/202401/i_am_at_liberty.html</link>
<dc:date>2024-01-30T09:08:00-05:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>As of a few weeks ago, I am between gigs. Riffing on some corporate-speak
from a <a rel="external noopener" href="https://2u.com/newsroom/2u-inc-announces-leadership-transition/">recent press release</a>: “2U and I have mutually
determined that 2U is laying me off.”</p><p>I feel OK about it: work was becoming increasingly frustrating, and I have
some severance pay. 2U is in a tough spot as a company so at least these
layoffs seemed like an actual tactic rather than another pointless
please-the-investors move by companies flush with profits and cash. 2U
struggling also makes being laid off a more appealing option than remaining
there after a difficult cut.</p><p><a href="https://edx.org" rel="external noopener">edX</a> was a good run for me. We had a noble
mission: educate the world. The software was mostly open source
(<a href="http://openedx.org" rel="external noopener">Open edX</a>), which meant our efforts could
power education that we as a corporation didn’t want to pursue.</p><p>Broadly speaking, my job was to oversee how to do open source well. I loved
the mission of education combined with the mission of open source. I loved
seeing the community do things together that edX alone could not. I have many
good friends at 2U and in the community. I hope they can make everything work
out well, and I hope I can do a good job staying in touch with them.</p><p>I don’t know what my next gig will be. I like writing software. I like
having developers as my customers. I am good at building community both inside
and outside of companies. I am good at helping people. I’m interested to hear
ideas.</p>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202401/you_probably_dont_need_to_learn_c.html">
<title>You (probably) don’t need to learn C</title>
<link>https://nedbatchelder.com/blog/202401/you_probably_dont_need_to_learn_c.html</link>
<dc:date>2024-01-24T06:38:49-05:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>On <a href="https://hachyderm.io/@nedbat/111789013210403320" rel="external noopener">Mastodon I
wrote</a> that I was tired of people saying, “you should learn C so you can
understand how a computer really works.” I got a lot of replies which did not
change my mind, but helped me understand more how abstractions are inescapable
in computers.</p><p>People made a number of claims. C was important because syscalls are defined
in terms of C semantics (they are not). They said it was good for exploring
limited-resource computers like Arduinos, but most people don’t program for
those. They said it was important because C is more performant, but Python
programs often offload the compute-intensive work to libraries other people have
written, and these days that work is often on a GPU. Someone said you need it to
debug with strace, then someone said they use strace all the time and don’t know
C. Someone even said C was good because it explains why NUL isn’t allowed in
filenames, but who tries to do that, and why learn a language just for that
trivia?</p><p>I’m all for learning C if it will be useful for the job at hand, but you can
write lots of great software without knowing C.</p><p>A few people repeated the idea that C teaches you how code “really” executes.
But C is an abstract model of a computer, and modern CPUs do all kinds of things
that C doesn’t show you or explain. Pipelining, cache misses, branch
prediction, speculative execution, multiple cores, even virtual memory are all
completely invisible to C programs.</p><p>C is an abstraction of how a computer works, and chip makers work hard to
implement that abstraction, but they do it on top of much more complicated
machinery.</p><p>C is far removed from modern computer architectures: there have been 50 years
of innovation since it was created in the 1970’s. The gap between C’s model and
modern hardware is the root cause of famous vulnerabilities like Meltdown and
Spectre, as explained in
<a href="https://dl.acm.org/doi/pdf/10.1145/3212477.3212479" rel="external noopener">C is <i>Not</i> a
Low-level Language</a>.</p><p>C can teach you useful things, like how memory is a huge array of bytes, but
you can also learn that without writing C programs. People say, C teaches you
about memory allocation. Yes it does, but you can learn what that means as a
concept without learning a programming language. And besides, what will Python
or Ruby developers do with that knowledge other than appreciate that their
languages do that work for them and they no longer have to think about it?</p><p>Pointers came up a lot in the Mastodon replies. Pointers underpin concepts in
higher-level languages, but you can
<a href="https://nedbatchelder.com/text/names1.html" rel="external noopener">explain those concepts as
references</a> instead, and skip pointer arithmetic, aliasing, and null pointers
completely.</p><p>A question I asked a number of people: what mistakes are
JavaScript/Ruby/Python developers making if they don’t know these things (C,
syscalls, pointers)?”. I didn’t get strong answers.</p><p>We work in an enormous tower of abstractions. I write programs in Python,
which provides me abstractions that C (its underlying implementation language)
does not. C provides an abstract model of memory and CPU execution which the
computer implements on top of other mechanisms (microcode and virtual memory).
When I made a wire-wrapped computer, I could pretend the signal travelled
through wires instantaneously. For other hardware designers, that abstraction
breaks down and they need to consider the speed electricity travels. Sometimes
you need to go one level deeper in the abstraction stack to understand what’s
going on. Everyone has to find the right layer to work at.</p><p><a href="https://hachyderm.io/@agocke/111791030850237121" rel="external noopener">Andy Gocke said
it well</a>:</p><blockquote><div><p>When you no longer have problems at that layer, that’s when you can
stop caring about that layer. I don’t think there’s a universal level of
knowledge that people need or is sufficient.</p></div></blockquote><p><a href="https://toot.cat/@idlestate/111793957024682587" rel="external noopener">“like jam or
bootlaces” made another excellent point</a>:</p><blockquote><div><p>There’s a big difference between “everyone should know this” and
“someone should know this” that seems to get glossed over in these kinds of
discussions.</p></div></blockquote><p>C can teach you many useful and interesting things. It will make you a
better programmer, just as learning any new-to-you language will because it
broadens your perspective. Some kinds of programming need C, though other
languages like Rust are ably filling that role now too. C doesn’t teach you how
a computer really works. It teaches you a common abstraction of how computers
work.</p><p>Find a level of abstraction that works for what you need to do. When you
have trouble there, look beneath that abstraction. You won’t be seeing how
things really work, you’ll be seeing a lower-level abstraction that could be
helpful. Sometimes what you need will be an abstraction one level up. Is your
Python loop too slow? Perhaps you need a C loop. Or perhaps you need numpy array
operations.</p><p>You (probably) don’t need to learn C.</p>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202401/randomly_subsetting_test_suites.html">
<title>Randomly sub-setting test suites</title>
<link>https://nedbatchelder.com/blog/202401/randomly_subsetting_test_suites.html</link>
<dc:date>2024-01-14T09:39:32-05:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>I needed to run random subsets of my test suite to narrow down the cause of
some mysterious behavior. I didn’t find an existing tool that worked the way I
wanted to, so I cobbled something together.</p><p>I wanted to run 10 random tests (out of 1368), and keep choosing randomly
until I saw the bad behavior. Once I had a selection of 10, I wanted to be able
to whittle it down to try to reduce it further.</p><p>I tried a few different approaches, and here’s what I came up with, two tools
in the coverage.py repo that combine to do what I want:</p><ul> <li>A pytest plugin (<a rel="external noopener" href="https://github.com/nedbat/coveragepy/blob/master/tests/select_plugin.py">select_plugin.py</a>) that
lets me run a command to output the names of the exact tests I want to
run,</li>
<li>A command-line tool (<a rel="external noopener" href="https://github.com/nedbat/coveragepy/blob/master/lab/pick.py">pick.py</a>) to select random
lines of text from a file. For convenience, blank or commented-out lines are
ignored.</li>
</ul><p>More details are in the comment at the top of
<a rel="external noopener" href="https://github.com/nedbat/coveragepy/blob/master/lab/pick.py">pick.py</a>, but here’s a quick example:</p><ol>
<li>Get all the test names in tests.txt. These are pytest “node” specifications:
<blockquote class="code"><pre>pytest --collect-only | grep :: > tests.txt<br></pre></blockquote>
</li>
<li>Now tests.txt has a line per test node. Some are straightforward:
<blockquote class="code"><pre>tests/test_cmdline.py::CmdLineStdoutTest::test_version<br>tests/test_html.py::HtmlDeltaTest::test_file_becomes_100<br>tests/test_report_common.py::ReportMapsPathsTest::test_map_paths_during_html_report<br></pre></blockquote>
but with parameterization they can be complicated:
<blockquote class="code"><pre>tests/test_files.py::test_invalid_globs[bar/***/foo.py-***]<br>tests/test_files.py::FilesTest::test_source_exists[a/b/c/foo.py-a/b/c/bar.py-False]<br>tests/test_config.py::ConfigTest::test_toml_parse_errors[[tool.coverage.run]\nconcurrency="foo"-not a list]<br></pre></blockquote>
</li>
<li>Run a random bunch of 10 tests:
<blockquote class="code"><pre>pytest --select-cmd="python pick.py sample 10 < tests.txt"<br></pre></blockquote>
We’re using <code>--select-cmd</code> to specify the shell command that
will output the names of tests. Our command uses <code>pick.py</code>
to select 10 random lines from tests.txt.
</li>
<li>Run many random bunches of 10, announcing the seed each time:
<blockquote class="code"><pre>for seed in $(seq 1 100); do<br> echo seed=$seed<br> pytest --select-cmd="python pick.py sample 10 $seed < tests.txt"<br>done<br></pre></blockquote>
</li>
<li>Once you find a seed that produces the small batch you want, save that batch:
<blockquote class="code"><pre>python pick.py sample 10 17 < tests.txt > bad.txt<br></pre></blockquote>
</li>
<li>Now you can run that bad batch repeatedly:
<blockquote class="code"><pre>pytest --select-cmd="cat bad.txt"<br></pre></blockquote>
</li>
<li>To reduce the bad batch, comment out lines in bad.txt with a hash character,
and the tests will be excluded. Keep editing until you find the small set of
tests you want.</li>
</ol><p>I like that this works and I understand it. I like that it’s based on the
bedrock of text files and shell commands. I like that there’s room for
different behavior in the future by adding to how pick.py works. For example,
it doesn’t do any bisecting now, but it could be adapted to it.</p><p>As usual, there might be a better way to do this, but this works for me.</p>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202312/coveragepy_with_sysmonitoring.html">
<title>Coverage.py with sys.monitoring</title>
<link>https://nedbatchelder.com/blog/202312/coveragepy_with_sysmonitoring.html</link>
<dc:date>2023-12-27T18:03:33-05:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>New in Python 3.12 is <a rel="external noopener" href="https://docs.python.org/3/library/sys.monitoring.html">sys.monitoring</a>, a
lighter-weight way to monitor the execution of Python programs.
<a href="https://pypi.org/project/coverage/7.4.0" rel="external noopener">Coverage.py 7.4.0</a> now
can optionally use sys.monitoring instead of
<a rel="external noopener" href="https://docs.python.org/3/library/sys.html#sys.settrace">sys.settrace</a>, the facility that has underpinned
coverage.py for nearly two decades. This is a big change, both in Python and in
coverage.py. It would be great if you could try it out and provide some
feedback.</p><p>Using sys.monitoring should reduce the overhead of coverage measurement,
often lower than 5%, but of course your timings might be different. One of the
things I would like to know is what your real-world speed improvements are
like.</p><p>Because the support is still a bit experimental, you need to define an
environment variable to use it: <code>COVERAGE_CORE=sysmon</code>.
Eventually, sys.monitoring will be automatically used where possible, but for
now you need to explicitly request it.</p><p>Some things won’t work with sys.monitoring: plugins and dynamic contexts
aren’t yet supported, though eventually they will be. Execution will be faster
for line coverage, but not yet for branch coverage. Let me know how it works
for you.</p><p>This has been in the works since at least March. I hope I haven’t forgotten
something silly in getting it out the door.</p>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202312/realworld_matchcase.html">
<title>Real-world match/case</title>
<link>https://nedbatchelder.com/blog/202312/realworld_matchcase.html</link>
<dc:date>2023-12-10T13:21:00-05:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>Python 3.10 brought us structural pattern matching, better known as
<b>match/case</b>. At first glance, it looks like a switch statement from C or
JavaScript, but it’s very different.</p><p>You can use match/case to match specific literals, similar to how switch
statements work, but their point is to match patterns in the structure of data,
not just values. <a href="https://peps.python.org/pep-0636/" rel="external noopener">PEP 636:
Structural Pattern Matching: Tutorial</a> does a good job explaining the
mechanics, but feels like a toy example.</p><p>Here’s a real-world use: at work we have a GitHub bot installed as a
webhook. When something happens in one of our repos, GitHub sends a payload of
JSON data to our bot. The bot has to examine the decoded payload to decide what
to do.</p><p>These payloads are complex: they are dictionaries with only 6 or 8 keys, but
they are deeply nested, eventually containing a few hundred pieces of data.
Originally we were picking them apart to see what keys and values they had, but
match/case made the job much simpler.</p><p>Here’s some of the code for determining what to do when we get a “comment
created” event:</p><blockquote class="code"><pre class="py"><span class="c1"># Check the structure of the payload:</span>
<br><span class="k">match</span> <span class="n">event</span><span class="p">:</span>
<br>    <span class="k">case</span> <span class="p">{</span>
<br>        <span class="s2">"issue"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"closed_at"</span><span class="p">:</span> <span class="n">closed</span><span class="p">},</span>
<br>        <span class="s2">"comment"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"created_at"</span><span class="p">:</span> <span class="n">commented</span><span class="p">},</span>
<br>        <span class="p">}</span> <span class="k">if</span> <span class="n">closed</span> <span class="o">==</span> <span class="n">commented</span><span class="p">:</span>
<br>        <span class="c1"># This is a "Close with comment" comment. Don't do anything for the</span>
<br>        <span class="c1"># comment, because we'll also get a "pull request closed" event at</span>
<br>        <span class="c1"># the same time, and it will do whatever we need.</span>
<br>        <span class="k">pass</span>
<br>
<br>    <span class="k">case</span> <span class="p">{</span><span class="s2">"sender"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"login"</span><span class="p">:</span> <span class="n">who</span><span class="p">}}</span> <span class="k">if</span> <span class="n">who</span> <span class="o">==</span> <span class="n">get_bot_username</span><span class="p">():</span>
<br>        <span class="c1"># When the bot comments on a pull request, it causes an event, which</span>
<br>        <span class="c1"># gets sent to webhooks, including us.  We don't have to do anything</span>
<br>        <span class="c1"># for our own comment events.</span>
<br>        <span class="k">pass</span>
<br>
<br>    <span class="k">case</span> <span class="p">{</span><span class="s2">"issue"</span><span class="p">:</span> <span class="p">{</span><span class="s2">"pull_request"</span><span class="p">:</span> <span class="n">_</span><span class="p">}}:</span>
<br>        <span class="c1"># The comment is on a pull request. Process it.</span>
<br>        <span class="k">return</span> <span class="n">process_pull_request_comment</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
<br></pre></blockquote><p>The first case matches if the dict has an “issue” key containing a dict with
a “closed_at” key and also a “comment” key containing a dict with a “created_at”
key, and if those two leaves in the dict are equal. Writing out that condition
without match/case would be more verbose and confusing.</p><p>The second case examines the event to see if the bot was the originator of
the event. This one wouldn’t have been so hard to write in a different way, but
match/case makes it nicer.</p><p>This is just what match/case is good at: checking patterns in the structure
of data.</p><p>It’s also interesting to see the bytecode generated. For that first case, it
looks like this:</p><blockquote class="code"><pre> 2 0 LOAD_GLOBAL 0 (event)<br><br> 3 2 MATCH_MAPPING<br> 4 POP_JUMP_IF_FALSE 67 (to 134)<br> 6 GET_LEN<br> 8 LOAD_CONST 1 (2)<br> 10 COMPARE_OP 5 (>=)<br> 12 POP_JUMP_IF_FALSE 67 (to 134)<br><br> 4 14 NOP<br><br> 5 16 NOP<br><br> 3 18 LOAD_CONST 8 (('issue', 'comment'))<br> 20 MATCH_KEYS<br> 22 POP_JUMP_IF_FALSE 65 (to 130)<br> 24 DUP_TOP<br> 26 LOAD_CONST 4 (0)<br> 28 BINARY_SUBSCR<br><br> 4 30 MATCH_MAPPING<br> 32 POP_JUMP_IF_FALSE 64 (to 128)<br> 34 GET_LEN<br> 36 LOAD_CONST 5 (1)<br> 38 COMPARE_OP 5 (>=)<br> 40 POP_JUMP_IF_FALSE 64 (to 128)<br> 42 LOAD_CONST 9 (('closed_at',))<br> 44 MATCH_KEYS<br> 46 POP_JUMP_IF_FALSE 62 (to 124)<br> 48 DUP_TOP<br> 50 LOAD_CONST 4 (0)<br> 52 BINARY_SUBSCR<br> 54 ROT_N 7<br> 56 POP_TOP<br> 58 POP_TOP<br> 60 POP_TOP<br> 62 DUP_TOP<br> 64 LOAD_CONST 5 (1)<br> 66 BINARY_SUBSCR<br><br> 5 68 MATCH_MAPPING<br> 70 POP_JUMP_IF_FALSE 63 (to 126)<br> 72 GET_LEN<br> 74 LOAD_CONST 5 (1)<br> 76 COMPARE_OP 5 (>=)<br> 78 POP_JUMP_IF_FALSE 63 (to 126)<br> 80 LOAD_CONST 10 (('created_at',))<br> 82 MATCH_KEYS<br> 84 POP_JUMP_IF_FALSE 61 (to 122)<br> 86 DUP_TOP<br> 88 LOAD_CONST 4 (0)<br> 90 BINARY_SUBSCR<br> 92 ROT_N 8<br> 94 POP_TOP<br> 96 POP_TOP<br> 98 POP_TOP<br> 100 POP_TOP<br> 102 POP_TOP<br> 104 POP_TOP<br> 106 STORE_FAST 0 (closed)<br> 108 STORE_FAST 1 (commented)<br><br> 6 110 LOAD_FAST 0 (closed)<br> 112 LOAD_FAST 1 (commented)<br> 114 COMPARE_OP 2 (==)<br> 116 POP_JUMP_IF_FALSE 70 (to 140)<br><br> 10 118 LOAD_CONST 0 (None)<br> 120 RETURN_VALUE<br><br> 3 >> 122 POP_TOP<br> >> 124 POP_TOP<br> >> 126 POP_TOP<br> >> 128 POP_TOP<br> >> 130 POP_TOP<br> 132 POP_TOP<br> >> 134 POP_TOP<br> 136 LOAD_CONST 0 (None)<br> 138 RETURN_VALUE<br><br> 6 >> 140 LOAD_CONST 0 (None)<br> 142 RETURN_VALUE<br></pre></blockquote><p>That’s a lot, but you can see roughly what it’s doing: check if the value is
a mapping (dict) with at least two keys (bytecodes 2–12), then check if
it has the two specific keys we’ll be examining (18–22). Look at the
value of the first key, check if it’s a dict with at least one key
(24–40), etc, and so on.</p><p>Hand-writing these sorts of checks might result in shorter bytecode. For
example, I already know the event value is a dict, since that is what the GitHub
API promise me, so there’s no need to check it explicitly each time. But the
Python code would be twistier and harder to get right. I was initially a skeptic
about match/case, but this example shows a clear benefit.</p>
]]></description>
</item>
<item rdf:about="https://nedbatchelder.com/blog/202311/say_it_again_values_not_expressions.html">
<title>Say it again: values not expressions</title>
<link>https://nedbatchelder.com/blog/202311/say_it_again_values_not_expressions.html</link>
<dc:date>2023-11-29T18:30:46-05:00</dc:date>
<dc:creator>Ned Batchelder</dc:creator>
<description><![CDATA[<p>Sometimes you can explain a simple thing for the thousandth time,
and come away with a deeper understanding yourself. It happened to me the
other day with Python mutable argument default values.
</p><p>This is a classic Python “gotcha”: you can provide a default value for a
function argument, but it will only be evaluated once:</p><blockquote class="code"><pre class="pycon"><span class="gp">>>> </span><span class="k">def</span> <span class="nf">doubled</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">the_list</span><span class="o">=</span><span class="p">[]):</span>
<br><span class="gp">... </span>    <span class="n">the_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
<br><span class="gp">... </span>    <span class="n">the_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
<br><span class="gp">... </span>    <span class="k">return</span> <span class="n">the_list</span>
<br><span class="gp">...</span>
<br><span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">doubled</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
<br><span class="go">[10, 10]</span>
<br><span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">doubled</span><span class="p">(</span><span class="mi">99</span><span class="p">))</span>
<br><span class="go">[10, 10, 99, 99]    # WHAT!?</span>
<br></pre></blockquote><p>I’ve seen people be surprised by this and ask about it countless times.
And countless times I’ve said, “Yup, the value is only calculated once, and
stored on the function.”</p><p>But recently I heard someone answer with, “it’s a value, not an expression,”
which is a good succinct way to say it. And when a co-worker brought it up
again the other day, I realized, it’s right in the name: people ask about
“default values” not “default expressions.” Of course it’s calculated only once,
it’s a default value, not a default expression. Somehow answering the question
for the thousandth time made those words click into place and make a connection
I hadn’t realized before.</p><p>Maybe this seems obvious to others who have been fielding this question, but
to me it was a satisfying alignment of the terminology and the semantics. I’d
been using the words for years, but hadn’t seen them as so right before.</p><p>This is one of the reasons I’m always interested to help new learners: even
well-trodden paths can reveal new insights.</p>
]]></description>
</item>
</rdf:RDF>
If you would like to create a banner that links to this page (i.e. this validation result), do the following:
Download the "valid RSS" banner.
Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)
Add this HTML to your page (change the image src
attribute if necessary):
If you would like to create a text link instead, here is the URL you can use:
http://www.feedvalidator.org/check.cgi?url=https%3A//www.nedbatchelder.com/blog/rss.xml