CINXE.COM

Planet Python

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>Planet Python</title> <link media="screen" href="/static/styles/screen-switcher-default.css" type="text/css" id="screen-switcher-stylesheet" rel="stylesheet" /> <link rel="stylesheet" type="text/css" media="sc&#82;een" href="/static/styles/netscape4.css" /> <link rel="stylesheet" type="text/css" media="print" href="/static/styles/print.css" /> <link rel="alternate stylesheet" type="text/css" media="screen" href="/static/styles/largestyles.css" title="large text" /> <link media="screen" href="/static/styles/defaultfonts.css" type="text/css" rel="alternate stylesheet" title="default fonts" /> <meta name="generator" content="Planet/2.0 +http://www.planetplanet.org" /> <meta name="keywords" content="Python weblog blog blogs blogger weblogger aggregator rss" /> <meta name="description" content="Recent postings from Python-related blogs." /> <link rel="alternate" type="application/rss+xml" title="RSS" href="rss20.xml" /> <style> /* Make images responsive */ img { border: 0; height: auto; max-width: 100%; display: block; padding-top: 5px; padding-bottom: 35px; } </style> </head> <body> <!-- Logo --> <h1 id="logoheader"> <a href="/" id="logolink" accesskey="1"><img id="logo" src="/static/images/python-logo.gif" alt="homepage" border="0" /></a> </h1> <!-- Skip to Navigation --> <div class="skiptonav"><a href="#left-hand-navigation" accesskey="2"><img src="/static/images/trans.gif" id="skiptonav" alt="skip to navigation" border="0" /></a></div> <div class="skiptonav"><a href="#content-body" accesskey="3"><img src="/static/images/trans.gif" id="skiptocontent" alt="skip to content" border="0" /></a></div> <div id="content-body"> <div id="body-main"> <h1 class="pageheading">Planet Python</h1> <p>Last update: April 11, 2025 07:43 PM UTC <h2>April 11, 2025</h2> <hr /><h3 class="post"><a href="https://pycon.blogspot.com/" title="The PyCon US Blog">PyCon</a></h3> <h4><a href="https://pycon.blogspot.com/2025/04/pycon-us-2025-call-for-volunteers.html">PyCon US 2025: Call for Volunteers &amp; Hatchery Lineup!</a></h4> <p> <h1>Call for Volunteers!</h1>Looking to make a meaningful contribution to the Python community? Look no further than PyCon US 2025! Whether you're a seasoned Python pro or a newcomer to the community and looking to get involved, there's a volunteer opportunity that's perfect for you. <br /><br />Sign-up for volunteer roles is done directly through the <a href="http://us.pycon.org/2025/">PyCon US website</a>. This way, you can view and manage shifts you sign up for through your personal dashboard! You can read up on <a href="https://us.pycon.org/2025/volunteer/volunteering/">the different roles to volunteer for and how to sign up on the PyCon US website</a>.<br /><br />PyCon US is largely organized and run by volunteers. Every year, we ask to fill over 300 onsite volunteer hours to ensure everything runs smoothly at the event. And the best part? You don't need to commit a lot of time to make a difference– some shifts are as short as 45 minutes long! You can sign up for as many or as few shifts as you’d like. Even a couple of hours of your time can go a long way in helping us create an amazing experience for attendees.<br /><br />Keep in mind that you need to be <a href="https://us.pycon.org/2025/attend/information/">registered for the conference</a> to sign up for a volunteer role.<br /><br />One important way to get involved is to sign up as a <a href="https://us.pycon.org/2025/volunteer/session-staff/">Session Chair or Session Runner</a>. This is an excellent opportunity to meet and interact with speakers while helping to ensure that sessions run smoothly. And who knows, you might just learn something new along the way! You can sign up for these roles directly on the <a href="https://us.pycon.org/2025/schedule/talks/">Talks schedule</a>.<br /><br />Volunteer your time at PyCon US 2025 and you’ll be part of a fantastic community that's passionate about Python programming and help us make this year's conference a huge success. <a href="https://us.pycon.org/2025/schedule/volunteering/">Sign up today for the shifts that call to you and join the fun!</a><div><h1>Hatchery Program</h1>First introduced in 2018, <a href="https://us.pycon.org/2025/events/hatchery/">Hatchery program</a> offers the pathways for PyCon US attendees to introduce new tracks, activities, summits, demos, etc., at the conference—activities that all share and fulfill the<a href="https://www.python.org/psf/mission/"> Python Software Foundation’s mission</a> within the PyCon US schedule.<br /><br />Since its introduction, this program has “hatched” several new tracks that are now staples of our conference, including PyCon US Charlas, Mentored Sprints, and the Maintainer’s Summit. This year, we’ve received eight very compelling proposals. After careful consideration, we have selected three returning programs and one new program, each of them unique and focused on different aspects of the Python community.<div><h1>New Hatchery Program</h1><h2><a href="https://us.pycon.org/2025/events/hometown-heroes/">Hometown Heroes</a> - Saturday, May 17, 2025</h2>The Hometown Heroes track at PyCon US 2025 highlights individuals and organizations doing interesting and innovative work with Python right here in our host city of Pittsburgh, Pennsylvania. Organized by leaders from the local Python community, the Hometown Heroes track features a curated selection of presentations, panel discussions, and lightning talks from Pittsburgh-based software engineers, data scientists, researchers, educators, and more.<br /><br />Hometown Heroes strengthens the relationship between PyCon US and its host city and forges connections within the local community that will last long after the curtain closes on the conference.<br /><br />Organized by: Patrick Harrison, Colin Dean, Nathan Brouwer, Doug Nicola, Madison Ebersole</div><div><h1>Returning Hatchery Programs</h1></div><div><h2><a href="https://feeds.feedburner.com/ https://us.pycon.org/2025/events/flaskcon/">FlaskCon</a> - Friday, May 16, 2025</h2>Join us in a mini conference dedicated to Flask, its community and ecosystem, as well as related web technologies. Meet maintainers and community members, learn about how to get involved, and join us during the sprint days to contribute. Submit your talk proposal today!<br /><br />Organized by: David Lord, Adam Englander, David Carmichael, Phil Jones, Pamela Fox</div><div><h2><a href="https://us.pycon.org/2025/events/community-organizers-summit/">Community Organizers Summit</a> - Saturday, May 17, 2025</h2>Do you organize a Conference, Meetup, User Group, Hackathon, or other community event in your area? Are you trying to start a group but don't know where to start? Whether you have 30 years experience or are looking to create a new event, this summit is for you.<br /><br />Join us for a summit of Presentations, Breakout Sessions, Open Spaces and more about organizing community events.<br /><br />Organized by: Mason Egger, Heather White (eevelweezel)</div><div><h2><a href="https://us.pycon.org/2025/events/humble-data/">Humble Data Workshop</a> - Sunday, May 18, 2024</h2>Are you eager to embark on a tech career but unsure where to start? Are you curious about data science? Taking the first steps in this area is hard, but you don’t have to do it alone. Join our workshop for complete beginners and get started in Python data science - even if you’ve never written a single line of code!<br /><br />We invite those from underrepresented groups to join us for a fun, supportive workshop that will give you the confidence to get started in this exciting area. You can expect plenty of exercises, as well as inspiring talks from those who were once in your shoes. You’ll cover the basics of programming in Python, as well as useful libraries and tools such as Jupyter notebooks, pandas, and Matplotlib.<br /><br />In this hands-on workshop, you’ll work through a series of beginner-friendly materials at your own pace. You’ll work within small groups, each with an assigned mentor, who will be there to help you with any questions or whenever you get stuck. All you’ll need to bring is a laptop that can connect to the internet and a willingness to learn!<br /><br />Humble Data aims to increase inclusivity and provide a safe community for Python and Data Science. We organise free workshops for people who are outside of the mainstream in the data science and tech industry. We encourage past participants to become mentors and even organise their own workshop one day. We are building a community where people can help and learn from each other. Remember to stay humble! See more on our website: <a href="https://humbledata.org/">https://humbledata.org/</a><br /><br />Organized by: Cheuk Ting Ho, Jodie Burchell, Laís Carvalho</div><div><h1>Register Now</h1><p><a href="https://us.pycon.org/2025/attend/information/">Registration for PyCon US</a> is now open, and all of the Hatchery programs are included as part of your PyCon US registration at no additional cost. Space is limited, so be sure to plan your Hatchery Program attendance in advance.&nbsp;</p><p>Congratulations to all the accepted program organizers! Thank you for bringing forward your fresh ideas to PyCon US. We look forward to seeing you in Pittsburgh.</p></div></div></p> <p> <em><a href="https://pycon.blogspot.com/2025/04/pycon-us-2025-call-for-volunteers.html">April 11, 2025 12:26 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://realpython.com/" title="Real Python">Real Python</a></h3> <h4><a href="https://realpython.com/podcasts/rpp/246/">The Real Python Podcast – Episode #246: Learning Intermediate Python With a Deep Dive Course</a></h4> <p> <p>Do you want to learn deeper concepts in Python? Would the accountability of scheduled group classes help you get past the basics? This week, five Real Python Intermediate Deep Dive workshop members discuss their experiences.</p> <hr /> <p><em>[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp; sweet Python Trick delivered to your inbox every couple of days. <a href="https://realpython.com/python-tricks/?utm_source=realpython&utm_medium=rss&utm_campaign=footer">&gt;&gt; Click here to learn more and see examples</a> ]</em></p></p> <p> <em><a href="https://realpython.com/podcasts/rpp/246/">April 11, 2025 12:00 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="http://edcrewe.blogspot.com/search/label/python" title="Ed Crewe">Ed Crewe</a></h3> <h4><a href="http://edcrewe.blogspot.com/2025/04/talk-about-cloud-prices-at-pyconlt-2025.html">Talk about Cloud Prices at PyConLT 2025</a></h4> <p> <p><span><img height="366" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeMbQWsFsx6cdC3J0tNHjTiGC_9e0OAxdzmKmuTJXB6SAqd_wWcOZXPuWrjwKiMh0SHac_7FinmyJn4uEb3Z7mlGZ99rxEjDDO7vzFSeQDuRgPMtsX6nlb2x6W26uC5rIzxeRJ2=w697-h366?key=P5CtPHSq8gZ_68XgDHhE5o1v" width="697" /></span></p><h3><span><br /></span></h3><h3><span>Introduction to Cloud Pricing</span></h3><p><span>I am looking forward to <a href="https://pycon.lt/talks/PZD7QE">speaking at PyConLT 2025</a> in two weeks.&nbsp;</span></p><p>Its been a while (12 years!) since my last Python conference <a href="https://www.youtube.com/watch?v=OeToCdcv8zo">EuroPython Florence 2012</a>, when I spoke as a Django web developer, although I did give <a href="https://colocatedeventsna2024.sched.com/event/1izso/fake-it-to-make-it-tdd-of-grpc-microservices-ed-crewe-enterprisedb">a Golang talk at Kubecon</a> USA last year.</p><p>I work at EDB, the Postgres company, on our Postgres AI product. The cloud version of which runs across the main cloud providers, AWS, Azure and GCP.</p><p>The team I am in handles the identity management and billing components of the product. So whilst I am mainly a Golang micro-service developer, I have dipped my toe into Data Science, having rewritten our Cloud prices ETL using Python &amp; Airflow. The subject of my talk in Lithuania.</p><p>Cloud pricing can be surprisingly complex ... and the price lists are not small.</p><p>The full price lists for the 3 CSPs together are almost 5 million prices - known as SKUs (Stock Keeping Unit prices)</p><h4><span>csp x service x type x tier x region<br /></span><span>3&nbsp; &nbsp; x&nbsp; 200&nbsp; &nbsp; &nbsp; x 50&nbsp; &nbsp; &nbsp;x 3&nbsp; &nbsp; &nbsp;x 50&nbsp; &nbsp; &nbsp; &nbsp; = 4.5 million</span></h4><p><span>csp = AWS, Azure and GCP</span></p><p><span>service = vms, k8s, network, load balancer, storage etc.</span></p><p><span>type = e.g. storage - general purpose E2, N1 ... accelerated A1, A2&nbsp; multiplied by various property sizes</span></p><p><span>tier&nbsp; = T-shirt size tiers of usage, ie more use = cheaper rate - small, medium, large</span></p><p><span>region = us-east-1, us-west-2, af-south-1, etc.</span></p><p>We need to gather all the latest service SKU that our Postgres AI may use and total them up as a cost estimate for when customers are selecting the various options for creating or adding to their installation. <br />Applying the additional pricing for our product and any private offer discounts for it, as part of this process.</p><p>Therefore we needed to build a data pipeline to gather the SKUs and keep them current.</p><p>Previously we used a 3rd party <a href="https://github.com/kubecost">kubecost</a> based provider's data, however our usage was not sufficient to justify for paying for this particular cloud service when its free usage expired.</p><p>Hence we needed to rewrite our cloud pricing data pipeline. This pipeline is in <a href="https://airflow.apache.org/">Apache Airflow</a> but it could equally be in <a href="https://dagster.io/">Dagster</a> or any other data pipeline framework.</p><p>My talk deals with the wider points around cloud pricing, refactoring a data pipeline and pipeline framework options. But here I want to provide more detail on the data pipeline's Python code, its use of <a href="https://github.com/fergusstrange/embedded-postgres">Embedded Postgres</a> and <a href="https://click.palletsprojects.com/en/stable/">Click</a>, and the benefits for development and testing.&nbsp; Some things I didn't have room for in the talk.</p><p><br /></p><h3><span><b>Outline of our use of Data Pipelines</b></span></h3><div><span>Airflow, Dagster, etc. provide many tools for pipeline development.<br /></span><span>Notably local development mode for running up the pipeline framework locally and doing test runs.<br /></span><span>Including some reloading on edit, it can still be a long process, running up a pipeline and then executing the full set of steps, known as a directed acyclic graph, DAG.</span></div><p><span><span id="docs-internal-guid-afd6e42e-7fff-3f8b-c524-67d2dca9d528"><img height="366" src="https://lh7-rt.googleusercontent.com/slidesz/AGV_vUeHuAvZb5hTvyn72fcoFP9vXKPdfYBZvJL3zuNaeXcqa6DZOm9FsDlw_2GWBzM3ft3ccY4jKUwJETw3VXG6kq7FFe1SB6R42Nh2UoTp_1kmoGLMqtSYWcteRZtrAVhZjB_zNoP9ZQ=w640-h366?key=4VZ9TwJPK2URz1sWyOhNT2lB" width="640" /></span></span></p><p><span>One way to improve the DEVX is if the DAG step's code is encapsulated as much as possible per step.<br />Removing use of shared state where that is viable and allowing individual steps to be separately tested, rapidly, with fixture data. With fast stand up and tear down, of temporary embedded storage.<span><br /></span></span></p><p><span><span>To avoid shared state persistence across the whole pipeline we use extract transform load (ETL) within each step, rather than across the whole pipeline. This enables functional running and testing of individual steps outside the pipeline.</span></span></p><p><span><span><br /></span></span></p><h3><span><span>The Scraper Class</span></span></h3><h3><span><span><p dir="ltr"><span><span>We need a standard scraper class to fetch the cloud prices from each CSP so use an abstract base class.</span></span></p><p dir="ltr"><span><span><br /></span></span></p><p dir="ltr"><span><span>from</span><span> abc </span><span>import</span><span> ABC</span><span><br /></span><span><br /></span><span>class</span><span> </span><span>BaseScraper</span><span>(ABC):</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;</span><span>"""Abstract base class for Scrapers"""</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;batch </span><span>=</span><span> </span><span>500</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;conn </span><span>=</span><span> </span><span>None</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;unit_map </span><span>=</span><span> {</span><span>"FAIL"</span><span>: </span><span>""</span><span>}</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;root_url </span><span>=</span><span> </span><span>""</span></span></p><p dir="ltr"><b id="docs-internal-guid-589ca1d7-7fff-f046-0a45-ea3cd1d1539e"><span><br /></span></b></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;</span><span>def</span><span> </span><span>map_units</span><span>(</span><span>self</span><span>, </span><span>entry</span><span>, </span><span>key</span><span>):</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>"""To standardize naming of units between CSPs"""</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>return</span><span> </span><span>self</span><span>.unit_map.get(entry.get(key, </span><span>"FAIL"</span><span>), entry[key])</span></span></p><p dir="ltr"><b><span><br /></span></b></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;</span><span>def</span><span> </span><span>scrape_sku</span><span>(</span><span>self</span><span>):</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>"""Scrapes prices from CSP bulk JSON API - uses CSP specific methods"""</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>Pass</span></span></p><p dir="ltr"><b><span><br /></span></b></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;</span><span>def</span><span> </span><span>bulk_insert_rows</span><span>(</span><span>self</span><span>, </span><span>rows</span><span>):</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>"""Bulk insert batches of rows - Note that Psycopg &gt;= 3.1 uses pipeline mode"""</span><span><br /><br /></span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;query </span><span>=</span><span> </span><span>"""INSERT INTO api_price.infra_price VALUES</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(</span><span>%(sku_id)s</span><span>, </span><span>%(cloud_provider)s</span><span>, </span><span>%(region)s</span><span>, </span><span>…</span><span> </span><span>%(sku_name)s</span><span>, </span><span>%(end_usage_amount)s</span><span>)"""</span><span><br /><br /></span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>with</span><span> </span><span>self</span><span>.conn.cursor() </span><span>as</span><span> cur:</span></span></p><p dir="ltr"><span><span></span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cur.executemany(query, rows)</span></span></p><p dir="ltr"><span><br /></span></p><p dir="ltr"><span>This has 3 common methods:<br /></span></p><p dir="ltr"></p><ol><li><span><span>mapping units to common ones across all CSP</span></span></li><li><span><span>Top level scrape sku methods some CSP differences within sub methods called from it</span></span></li><li><span><span>Bulk insert rows - the main concrete method used by all scrapers</span></span></li></ol><p></p><p dir="ltr"><span><span><span>To bulk insert 500 rows per query we use&nbsp;</span></span><span><a href="https://www.psycopg.org/psycopg3/">Psycopg 3</a>&nbsp;pipeline mode - so it can send batch updates again and again without waiting for response.</span></span></p><p dir="ltr"><span><span>The database update against local embedded Postgres is faster than the time to scrape the remote web site SKUs.</span></span></p><p dir="ltr"><span><span><br /></span></span></p><p dir="ltr"><span><span>The largest part of the Extract is done at this point. Rather than loading all 5 million SKU as we did with the kubecost data dump, to query out the 120 thousand for our product. Scraping the sources directly we only need to ingest those 120k SKU. Which saves handling 97.6% of the data!</span></span></p><p dir="ltr"><span><span><br /></span></span></p><p dir="ltr"><span><span>So the resultant speed is sufficient although not as performant as pg_dump loading which uses COPY.</span></span></p><p dir="ltr"><span><span><br /></span></span></p><p dir="ltr"><span><span>Unfortunately Python Psycopg is significantly slower when using cursor.copy and it mitigated against using zipped up Postgres dumps. Hence all the data artefact creation and loading simply uses the pg_dump utility wrapped as a Python shell command.&nbsp;</span></span></p><p dir="ltr"><span><span>There is no need to use Python here when there is the tried and tested C based pg_dump utility for it that ensures compatibility outside our pipeline. Later version pg_dump can always handle earlier Postgres dumps.</span></span></p><p dir="ltr"><span><span><br /></span></span></p><p dir="ltr"><span><span>We don't need to retain a long history of artefacts, since it is public data and never needs to be reverted.</span></span></p><p dir="ltr"><span><span>This allows us a low retention level, cleaning out most of the old dumps on creation of a new one. So any storage saving on compression is negligible.</span></span></p><p dir="ltr"><span><span>Therefore we avoid pg_dump compression, since it can be significantly slower, especially if the data already contains compressed blobs. Plain SQL COPY also allows for data inspection if required - eg grep for a SKU, when debugging why a price may be missing.</span></span></p><p dir="ltr"><span><span><br /></span></span></p></span></span></h3><h3><span><span>Postgres Embedded wrapped with Go</span></span></h3><div><span>Unlike MySQL, Postgres doesn't do in memory databases. The equivalent for temporary or test run database lifetime, is the embedded version of Postgres. Run from an auto-created temp folder of files.&nbsp;<br /></span><span>Python doesn’t have maintained wrapper for Embedded Postgres, sadly p</span><span>roject https://github.com/Simulmedia/pyembedpg is abandoned 😢</span></div><h3><span><span><div><span><span>Hence use the most up to date <a href="https://github.com/fergusstrange/embedded-postgres">wrapper from Go</a>. Running the Go binary via a Python shell command.</span></span></div><div><span><span>It still lags behind by a version of Postgres, so its on Postgres 16 rather than latest 17.</span></span></div><div><span><span>But for the purposes of embedded use that is irrelevant.<br /></span></span></div><div><span><span><br /></span></span></div><div><span><span>By using separate temporary Postgres per step we can save a dumped SQL artefact at the end of a step and need no data dependency between steps, meaning individual step retry in parallel, just works. <br />The performance of localhost dump to socket is also superior.</span></span></div><div><span><span>By processing everything in the same (if embedded) version of our final target database as the Cloud Price, Go micro-service, we remove any SQL compatibility issues and ensure full Postgresql functionality is available.</span></span></div><div><span><span><br />The final data artefacts will be loaded to a Postgres cluster price schema micro-service running on&nbsp;<a href="https://cloudnative-pg.io/">CloudNativePG</a></span></span></div><div><span><span><br /></span></span></div></span></span></h3><h3><span>Use a Click wrapper with Tests</span></h3><h3><span><span>The click package provides all the functionality for our pipeline..</span></span></h3><span id="docs-internal-guid-9cdd575b-7fff-debf-7bfb-29b4feca0607"><p dir="ltr"><span><span>&gt; pscraper -h </span></span></p><p dir="ltr"><span><span>Usage: pscraper [OPTIONS] COMMAND [ARGS]...</span><span><br /><br /></span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;&nbsp;price-scraper: python web scraping of CSP prices for api-price</span><span><br /><br /></span></span></p><p dir="ltr"><span><span>Options:</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;-h, --help&nbsp; Show this message and exit.</span></span></p><span><br /></span><p dir="ltr"><span><span>Commands:</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;awsscrape &nbsp; &nbsp; Scrape prices from AWS</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;azurescrape&nbsp; Scrape prices from Azure</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;delold&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Delete old blob storage files, default all over 12 weeks old are deleted</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;gcpscrape &nbsp; &nbsp; Scrape prices from GCP - set env GCP_BILLING_KEY</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;pgdump&nbsp; &nbsp; &nbsp; &nbsp; Dump postgres file and upload to cloud storage - set env STORAGE_KEY</span><span><br /></span><span> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &gt; pscraper pgdump --port 5377 --file price.sql&nbsp;</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;pgembed&nbsp; &nbsp; &nbsp; Run up local embeddedPG on a random port for tests </span></span></p><p dir="ltr"><span><span> &gt; pscraper pgembed</span></span></p><p dir="ltr"><span><span>&nbsp;&nbsp;pgload &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Load schema to local embedded postgres for testing </span></span></p><p dir="ltr"><span><span> &gt; pscraper pgload --port 5377 --file price.sql</span></span></p><p dir="ltr"><br /></p><p dir="ltr">This caters for developing the step code entirely outside the pipeline for development and debug.<br />We can run pgembed to create a local db, pgload to add the price schema. Then run individual scrapes from a pipenv <span>pip install -e version </span>of the the price scraper package.</p></span><div><br />For unit testing we can create a mock response object for the data scrapers that returns different fixture payloads based on the query and monkeypatch it in. This allows us to functionally test the whole scrape and data artefact creation ETL cycle as unit functional tests.<br /><br />Any issues with source data changes can be replicated via a fixture for regression tests.</div><div><span><h3><span><span><p dir="ltr"><span>class</span><span> </span><span>MockResponse</span><span>:</span></p><div><div> <span>"""Fake to return fixture value of requests.get() for testing scrape parsing"""</span></div><br /><div> name <span>=</span> <span>"Mock User"</span></div><div> payload <span>=</span> {}</div><div> content <span>=</span> <span>""</span></div><div> status_code <span>=</span> <span>200</span></div><div> url <span>=</span> <span>"http://mock_url"</span></div><br /><div> <span>def</span> <span>__init__</span>(<span>self</span>, <span>payload</span><span>=</span>{}, <span>url</span><span>=</span><span>"http://mock_url"</span>):</div><div> <span>self</span>.url <span>=</span> url</div><div> <span>self</span>.payload <span>=</span> payload</div><div> <span>self</span>.content <span>=</span> <span>str</span>(payload)</div><br /><div> <span>def</span> <span>json</span>(<span>self</span>):</div><div> <span>return</span> <span>self</span>.payload</div></div><p dir="ltr"><span><span><br /></span></span></p><p dir="ltr"><span><span></span></span></p><div><div><span>def</span> <span>mock_aws_get</span>(<span>url</span>, <span>**</span><span>kwargs</span>):</div><div><span>&nbsp;&nbsp; &nbsp;"""Return the fixture JSON that matches the URL used"""</span><br /></div><div> <span>for</span> key, fix <span>in</span> fixtures.items():</div><div> <span>if</span> key <span>in</span> url:</div><div> <span>return</span> MockResponse(<span>payload</span><span>=</span>fix, <span>url</span><span>=</span>url)</div><div> <span>return</span> MockResponse()</div></div><div><span><span><br /></span></span></div><div><span><span><div><div><span>class</span> <span>TestAWSScrape</span>(<span>TestCase</span>):</div><div> <span>"""Tests for the 'pscraper awsscrape' command"""</span></div><br /><div> <span>def</span> <span>setUpClass</span>():</div><div> <span>"""Simple monkeypatch in mock handlers for all tests in the class"""</span></div><div> psycopg.connect <span>=</span> MockConn</div><div> requests.get <span>=</span> mock_aws_get</div><div> <span># confirm that requests is patched hence returns short fixture of JSON from the AWS URLs</span></div><div> result <span>=</span> requests.get(<span>"</span><span>{}</span><span>/AmazonS3/current/index.json"</span>.format(ROOT))</div><div> <span>assert</span> <span>len</span>(result.json().keys()) <span>&gt;</span> <span>5</span> <span>and</span> <span>len</span>(result.content) <span>&lt;</span> <span>2000</span></div><div><span><br /></span></div></div></span></span></div></span></span></h3></span></div><div><span><h3><span><span>A simple DAG with Soda Data validation</span></span></h3></span></div><h3><span><span><p><span><span>The click commands for each DAG are imported at the top, one for the scrape and one for postgres embedded, the DAG just becomes a wrapper to run them, adding Soda data validation of the scraped data ...</span><span> </span><span> </span></span></p><div><span><span>def</span><span> </span><span>scrape_azure</span><span>():<br /></span><span>&nbsp;&nbsp;&nbsp;</span><span>"""Scrape Azure via API public json web pages"""<br /></span><span>&nbsp;&nbsp;&nbsp;</span><span>from</span><span> price_scraper.commands </span><span>import</span><span> azurescrape, pgembed<br /></span><span>&nbsp;&nbsp;&nbsp;folder, port </span><span>=</span><span> setup_pg_db(PORT</span><span>)<br /></span><span>&nbsp;&nbsp;&nbsp;error </span><span>=</span><span> azurescrape.run_azure_scrape(port, HOST)<br /></span><span>&nbsp;&nbsp;&nbsp;</span><span>if</span><span> </span><span>not</span><span> error:<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error </span><span>=</span><span> csp_dump(port, </span><span>"azure"</span><span>)<br /></span><span>&nbsp;&nbsp;&nbsp;</span><span>if</span><span> error:<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pgembed.teardown_pg_embed(folder)&nbsp;<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;notify_slack(</span><span>"azure"</span><span>, error)<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>raise</span><span> AirflowFailException(error)<br /></span><span>&nbsp;&nbsp;<br /></span><span>&nbsp;&nbsp;&nbsp;data_test </span><span>=</span><span> SodaScanOperator(<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>dag</span><span>=</span><span>dag,<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>task_id</span><span>=</span><span>"data_test"</span><span>,<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>data_sources</span><span>=</span><span>[<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>"data_source_name"</span><span>: </span><span>"embedpg"</span><span>,<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>"soda_config_path"</span><span>: </span><span>"price-scraper/soda/configuration_azure.yml"</span><span>,<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;],<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>soda_cl_path</span><span>=</span><span>"price-scraper/soda/price_azure_checks.yml"</span><span>,<br /></span><span>&nbsp;&nbsp;&nbsp;)<br /></span><span>&nbsp;&nbsp;&nbsp;data_test.execute(</span><span>dict</span><span>())<br /></span><span>&nbsp;&nbsp;&nbsp;pgembed.teardown_pg_embed(folder)</span></span><span>&nbsp;</span></div><p dir="ltr"><span id="docs-internal-guid-ff8941e2-7fff-b3dd-a8dc-df374c429826"></span><span></span></p></span></span></h3><p dir="ltr"><span><b><span><br /></span></b></span></p><p dir="ltr"><span><span>We setup a new Embedded Postgres (takes a few seconds) and then scrape directly to it.</span></span></p><p dir="ltr"><span><b><span><br /></span></b></span></p><p dir="ltr"><span><span>We then use the SodaScanOperator to check the data we have scraped, i</span></span><span>f there is no error we dump to blob storage </span><span>otherwise notify Slack with the error and raise it ending the DAG</span></p><p dir="ltr"><span> </span></p><p dir="ltr"><span><span><span>Our Soda tests check that the number of and prices are in the ranges that they should be for each service. We also check we have the amount of tiered rates that we expect. We expect over 10 starting usage rates and over 3000 specific tiered prices. </span> </span></span></p><p dir="ltr"><span><span><span>If the Soda tests pass, we dump to cloud storage and teardown temporary Postgres. A final step aggregates together each steps data. We save the money and maintenance of running a persistent database cluster in the cloud for our pipeline.</span></span></span></p><p dir="ltr"><br /></p></p> <p> <em><a href="http://edcrewe.blogspot.com/2025/04/talk-about-cloud-prices-at-pyconlt-2025.html">April 11, 2025 09:43 AM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://www.pythonguis.com/" title="Python GUIs">Python GUIs</a></h3> <h4><a href="https://www.pythonguis.com/tutorials/pyside6-actions-toolbars-menus/">PySide6 Toolbars &amp; Menus — QAction — Defining toolbars, menus, and keyboard shortcuts with QAction</a></h4> <p> <p>Next, we'll look at some of the common user interface elements you've probably seen in many other applications &mdash; toolbars and menus. We'll also explore the neat system Qt provides for minimizing the duplication between different UI areas &mdash; <code>QAction</code>.</p> <div class="toc"><span class="toctitle">Table of Contents</span><ul> <li><a href="https://www.pythonguis.com/feeds/all.atom.xml#basic-app">Basic App</a></li> <li><a href="https://www.pythonguis.com/feeds/all.atom.xml#toolbars">Toolbars</a></li> <li><a href="https://www.pythonguis.com/feeds/all.atom.xml#adding-a-toolbar">Adding a Toolbar</a></li> <li><a href="https://www.pythonguis.com/feeds/all.atom.xml#menus">Menus</a></li> <li><a href="https://www.pythonguis.com/feeds/all.atom.xml#adding-a-menu">Adding a Menu</a></li> </ul> </div> <h2 id="basic-app">Basic App</h2> <p>We'll start this tutorial with a simple skeleton application, which we can customize. Save the following code in a file named <code>app.py</code> -- this code all the imports you'll need for the later steps:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">from PySide6.QtCore import QSize, Qt from PySide6.QtGui import QAction, QIcon, QKeySequence from PySide6.QtWidgets import ( QApplication, QCheckBox, QLabel, QMainWindow, QStatusBar, QToolBar, ) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") app = QApplication([]) window = MainWindow() window.show() app.exec() </code></pre> </div> <p>This file contains the imports and the basic code that you'll use to complete the examples in this tutorial.</p> <p class="admonition admonition-tip"><span class="admonition-kind"><i class="fas fa-lightbulb"></i></span> If you're migrating to PySide6 from PySide2, notice that <code>QAction</code> is now available via the <code>QtGui</code> module.</p> <h2 id="toolbars">Toolbars</h2> <p>One of the most commonly seen user interface elements is the toolbar. Toolbars are bars of icons and/or text used to perform common tasks within an application, for which access via a menu would be cumbersome. They are one of the most common UI features seen in many applications. While some complex applications, particularly in the Microsoft Office suite, have migrated to contextual 'ribbon' interfaces, the standard toolbar is usually sufficient for the majority of applications you will create.</p> <p><img alt="Standard GUI elements" height="28" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/toolbar.png" width="859" /> <em>Standard GUI elements</em></p> <h2 id="adding-a-toolbar">Adding a Toolbar</h2> <p>Let's start by adding a toolbar to our application.</p> <p>In Qt, toolbars are created from the <code>QToolBar</code> class. To start, you create an instance of the class and then call <code>addToolbar</code> on the <code>QMainWindow</code>. Passing a string in as the first argument to <code>QToolBar</code> sets the toolbar's name, which will be used to identify the toolbar in the UI:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") self.addToolBar(toolbar) </code></pre> </div> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> You'll see a thin grey bar at the top of the window. This is your toolbar. Right-click the name to trigger a context menu and toggle the bar off.</p> <p><img alt="A window with a toolbar." height="139" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/window-with-toolbar.png" width="202" /> <em>A window with a toolbar.</em></p> <p class="admonition admonition-tip"><span class="admonition-kind"><i class="fas fa-lightbulb"></i></span> <strong>How can I get my toolbar back?</strong> Unfortunately, once you remove a toolbar, there is now no place to right-click to re-add it. So, as a general rule, you want to either keep one toolbar un-removeable, or provide an alternative interface in the menus to turn toolbars on and off.</p> <p>We should make the toolbar a bit more interesting. We could just add a <code>QButton</code> widget, but there is a better approach in Qt that gets you some additional features &mdash; and that is via <code>QAction</code>. <code>QAction</code> is a class that provides a way to describe abstract user interfaces. What this means in English is that you can define multiple interface elements within a single object, unified by the effect that interacting with that element has.</p> <p>For example, it is common to have functions that are represented in the toolbar but also the menu &mdash; think of something like <em>Edit-&gt;Cut</em>, which is present both in the <em>Edit</em> menu but also on the toolbar as a pair of scissors, and also through the keyboard shortcut <code>Ctrl-X</code> (<code>Cmd-X</code> on Mac).</p> <p>Without <code>QAction</code>, you would have to define this in multiple places. But with <code>QAction</code> you can define a single <code>QAction</code>, defining the triggered action, and then add this action to both the menu and the toolbar. Each <code>QAction</code> has names, status messages, icons, and signals that you can connect to (and much more).</p> <p>In the code below, you can see this first <code>QAction</code> added:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") self.addToolBar(toolbar) button_action = QAction("Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) toolbar.addAction(button_action) def toolbar_button_clicked(self, s): print("click", s) </code></pre> </div> <p>To start with, we create the function that will accept the signal from the <code>QAction</code> so we can see if it is working. Next, we define the <code>QAction</code> itself. When creating the instance, we can pass a label for the action and/or an icon. You must also pass in any <code>QObject</code> to act as the parent for the action &mdash; here we're passing <code>self</code> as a reference to our main window. Strangely, for <code>QAction</code>, the parent element is passed in as the final argument.</p> <p>Next, we can opt to set a status tip &mdash; this text will be displayed on the status bar once we have one. Finally, we connect the <code>triggered</code> signal to the custom function. This signal will fire whenever the <code>QAction</code> is <em>triggered</em> (or activated).</p> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> You should see your button with the label that you have defined. Click on it, and then our custom method will print "click" and the status of the button.</p> <p><img alt="Toolbar showing our QAction button." height="139" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/toolbar-with-qaction-button.png" width="202" /> <em>Toolbar showing our <code>QAction</code> button.</em></p> <p class="admonition admonition-tip"><span class="admonition-kind"><i class="fas fa-lightbulb"></i></span> <strong>Why is the signal always false?</strong> The signal passed indicates whether the button is <em>checked</em>, and since our button is not checkable &mdash; just clickable &mdash; it is always false. We'll show how to make it checkable shortly.</p> <p>Next, we can add a status bar.</p> <p>We create a status bar object by calling <code>QStatusBar</code> to get a new status bar object and then passing this into <code>setStatusBar</code>. Since we don't need to change the status bar settings, we can also just pass it in as we create it, in a single line:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") self.addToolBar(toolbar) button_action = QAction("Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) toolbar.addAction(button_action) self.setStatusBar(QStatusBar(self)) def toolbar_button_clicked(self, s): print("click", s) </code></pre> </div> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> Hover your mouse over the toolbar button, and you will see the status text in the status bar.</p> <p><img alt="Status bar text is updated as we hover our actions." height="218" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/statusbar-hover-action.png" width="294" /> <em>Status bar text updated as we hover over the action.</em></p> <p>Next, we're going to turn our <code>QAction</code> toggleable &mdash; so clicking will turn it on, and clicking again will turn it off. To do this, we simply call <code>setCheckable(True)</code> on the <code>QAction</code> object:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") self.addToolBar(toolbar) button_action = QAction("Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) button_action.setCheckable(True) toolbar.addAction(button_action) self.setStatusBar(QStatusBar(self)) def toolbar_button_clicked(self, s): print("click", s) </code></pre> </div> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> Click on the button to see it toggle from checked to unchecked state. Note that the custom slot method we create now alternates outputting <code>True</code> and <code>False</code>.</p> <p><img alt="The toolbar button toggled on." height="139" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/toolbar-button-toggled.png" width="202" /> <em>The toolbar button toggled on.</em></p> <p class="admonition admonition-info"><span class="admonition-kind"><i class="fas fa-info"></i></span> There is also a <code>toggled</code> signal, which only emits a signal when the button is toggled. But the effect is identical, so it is mostly pointless.</p> <p>Things look pretty shabby right now &mdash; so let's add an icon to our button. For this, I recommend you download the <a href="http://p.yusukekamiyamane.com/">fugue icon set</a> by designer Yusuke Kamiyamane. It's a great set of beautiful 16x16 icons that can give your apps a nice professional look. It is freely available with only attribution required when you distribute your application &mdash; although I am sure the designer would appreciate some cash too if you have some spare.</p> <p><img alt="Fugue Icon Set &mdash; Yusuke Kamiyamane" height="1006" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/fugue-icons.png" width="2004" /> <em>Fugue Icon Set &mdash;&nbsp;Yusuke Kamiyamane</em></p> <p>Select an image from the set (in the examples here, I've selected the file <code>bug.png</code>) and copy it into the same folder as your source code.</p> <p>We can create a <code>QIcon</code> object by passing the file name to the class, e.g. <code>QIcon("bug.png")</code> -- if you place the file in another folder, you will need a full relative or absolute path to it.</p> <p>Finally, to add the icon to the <code>QAction</code> (and therefore the button), we simply pass it in as the first argument when creating the <code>QAction</code>.</p> <p>You also need to let the toolbar know how large your icons are. Otherwise, your icon will be surrounded by a lot of padding. You can do this by calling <code>setIconSize()</code> with a <code>QSize</code> object:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) button_action = QAction(QIcon("bug.png"), "Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) button_action.setCheckable(True) toolbar.addAction(button_action) self.setStatusBar(QStatusBar(self)) def toolbar_button_clicked(self, s): print("click", s) </code></pre> </div> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> The <code>QAction</code> is now represented by an icon. Everything should work exactly as it did before.</p> <p><img alt="Our action button with an icon." height="139" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/action-button-with-icon.png" width="202" /> <em>Our action button with an icon.</em></p> <p class="admonition admonition-note"><span class="admonition-kind"><i class="fas fa-sticky-note"></i></span> Note that Qt uses your operating system's default settings to determine whether to show an icon, text, or an icon and text in the toolbar. But you can override this by using <code>setToolButtonStyle()</code>. This slot accepts any of the following flags from the <code>Qt</code> namespace:</p> <table> <thead> <tr> <th>Flag</th> <th>Behavior</th> </tr> </thead> <tbody> <tr> <td><code>Qt.ToolButtonStyle.ToolButtonIconOnly</code></td> <td>Icon only, no text</td> </tr> <tr> <td><code>Qt.ToolButtonStyle.ToolButtonTextOnly</code></td> <td>Text only, no icon</td> </tr> <tr> <td><code>Qt.ToolButtonStyle.ToolButtonTextBesideIcon</code></td> <td>Icon and text, with text beside the icon</td> </tr> <tr> <td><code>Qt.ToolButtonStyle.ToolButtonTextUnderIcon</code></td> <td>Icon and text, with text under the icon</td> </tr> <tr> <td><code>Qt.ToolButtonStyle.ToolButtonFollowStyle</code></td> <td>Follow the host desktop style</td> </tr> </tbody> </table> <p>The default value is <code>Qt.ToolButtonStyle.ToolButtonFollowStyle</code>, meaning that your application will default to following the standard/global setting for the desktop on which the application runs. This is generally recommended to make your application feel as <em>native</em> as possible.</p> <p>Finally, we can add a few more bits and bobs to the toolbar. We'll add a second button and a checkbox widget. As mentioned, you can literally put any widget in here, so feel free to go crazy:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">from PySide6.QtCore import QSize, Qt from PySide6.QtGui import QAction, QIcon from PySide6.QtWidgets import ( QApplication, QCheckBox, QLabel, QMainWindow, QStatusBar, QToolBar, ) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) button_action = QAction(QIcon("bug.png"), "&amp;Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) button_action.setCheckable(True) toolbar.addAction(button_action) toolbar.addSeparator() button_action2 = QAction(QIcon("bug.png"), "Your &amp;button2", self) button_action2.setStatusTip("This is your button2") button_action2.triggered.connect(self.toolbar_button_clicked) button_action2.setCheckable(True) toolbar.addAction(button_action2) toolbar.addWidget(QLabel("Hello")) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self)) def toolbar_button_clicked(self, s): print("click", s) app = QApplication([]) window = MainWindow() window.show() app.exec() </code></pre> </div> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> Now you see multiple buttons and a checkbox.</p> <p><img alt="Toolbar with an action and two widgets." height="139" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/toolbar-action-widgets.png" width="202" /> <em>Toolbar with an action and two widgets.</em></p> <h2 id="menus">Menus</h2> <p>Menus are another standard component of UIs. Typically, they are at the top of the window or the top of a screen on macOS. They allow you to access all standard application functions. A few standard menus exist &mdash; for example <em>File</em>, <em>Edit</em>, <em>Help</em>. Menus can be nested to create hierarchical trees of functions, and they often support and display keyboard shortcuts for fast access to their functions.</p> <p><img alt="Standard GUI elements - Menus" height="838" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/menus.png" width="452" /> <em>Standard GUI elements - Menus</em></p> <h2 id="adding-a-menu">Adding a Menu</h2> <p>To create a menu, we create a menubar we call <code>menuBar()</code> on the <code>QMainWindow</code>. We add a menu to our menu bar by calling <code>addMenu()</code>, passing in the name of the menu. I've called it <code>'&amp;File'</code>. The ampersand defines a quick key to jump to this menu when pressing Alt.</p> <p class="admonition admonition-warning"><span class="admonition-kind"><i class="fas fa-exclamation-circle"></i></span> This won't be visible on macOS. Note that this is different from a keyboard shortcut &mdash; we'll cover that shortly.</p> <p>This is where the power of actions comes into play. We can reuse the already existing QAction to add the same function to the menu. To add an action, you call <code>addAction()</code> passing in one of our defined actions:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) button_action = QAction(QIcon("bug.png"), "&amp;Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) button_action.setCheckable(True) toolbar.addAction(button_action) toolbar.addSeparator() button_action2 = QAction(QIcon("bug.png"), "Your &amp;button2", self) button_action2.setStatusTip("This is your button2") button_action2.triggered.connect(self.toolbar_button_clicked) button_action2.setCheckable(True) toolbar.addAction(button_action2) toolbar.addWidget(QLabel("Hello")) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self)) menu = self.menuBar() file_menu = menu.addMenu("&amp;File") file_menu.addAction(button_action) def toolbar_button_clicked(self, s): print("click", s) </code></pre> </div> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> Click the item in the menu, and you will notice that it is toggleable &mdash; it inherits the features of the <code>QAction</code>.</p> <p><img alt="Menu shown on the window -- on macOS this will be at the top of the screen." height="378" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/menu-shown-on-window.png" width="568" /> <em>Menu shown on the window -- on macOS this will be at the top of the screen.</em></p> <p>Let's add some more things to the menu. Here, we'll add a separator to the menu, which will appear as a horizontal line in the menu, and then add the second <code>QAction</code> we created:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) button_action = QAction(QIcon("bug.png"), "&amp;Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) button_action.setCheckable(True) toolbar.addAction(button_action) toolbar.addSeparator() button_action2 = QAction(QIcon("bug.png"), "Your &amp;button2", self) button_action2.setStatusTip("This is your button2") button_action2.triggered.connect(self.toolbar_button_clicked) button_action2.setCheckable(True) toolbar.addAction(button_action2) toolbar.addWidget(QLabel("Hello")) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self)) menu = self.menuBar() file_menu = menu.addMenu("&amp;File") file_menu.addAction(button_action) file_menu.addSeparator() file_menu.addAction(button_action2) def toolbar_button_clicked(self, s): print("click", s) </code></pre> </div> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> You should see two menu items with a line between them.</p> <p><img alt="Our actions showing in the menu." height="216" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/actions-shown-in-menu.png" width="277" /> <em>Our actions showing in the menu.</em></p> <p>You can also use ampersand to add <em>accelerator keys</em> to the menu to allow a single key to be used to jump to a menu item when it is open. Again this doesn't work on macOS.</p> <p>To add a submenu, you simply create a new menu by calling <code>addMenu()</code> on the parent menu. You can then add actions to it as usual. For example:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) button_action = QAction(QIcon("bug.png"), "&amp;Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) button_action.setCheckable(True) toolbar.addAction(button_action) toolbar.addSeparator() button_action2 = QAction(QIcon("bug.png"), "Your &amp;button2", self) button_action2.setStatusTip("This is your button2") button_action2.triggered.connect(self.toolbar_button_clicked) button_action2.setCheckable(True) toolbar.addAction(button_action2) toolbar.addWidget(QLabel("Hello")) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self)) menu = self.menuBar() file_menu = menu.addMenu("&amp;File") file_menu.addAction(button_action) file_menu.addSeparator() file_submenu = file_menu.addMenu("Submenu") file_submenu.addAction(button_action2) def toolbar_button_clicked(self, s): print("click", s) </code></pre> </div> <p class="admonition admonition-run"><span class="admonition-kind"><i class="fas fa-rocket"></i></span> <strong>Run it!</strong> You will see a nested menu in the <em>File</em> menu.</p> <p><img alt="Submenu nested in the File menu." height="220" src="https://www.pythonguis.com/static/tutorials/qt/actions-toolbars-menus/submenu-nested-in-file-menu.png" width="409" /> <em>Submenu nested in the File menu.</em></p> <p>Finally, we'll add a keyboard shortcut to the <code>QAction</code>. You define a keyboard shortcut by passing <code>setKeySequence()</code> and passing in the key sequence. Any defined key sequences will appear in the menu.</p> <p class="admonition admonition-tip"><span class="admonition-kind"><i class="fas fa-lightbulb"></i></span> Note that the keyboard shortcut is associated with the <code>QAction</code> and will still work whether or not the <code>QAction</code> is added to a menu or a toolbar.</p> <p>Key sequences can be defined in multiple ways - either by passing as text, using key names from the <code>Qt</code> namespace, or using the defined key sequences from the <code>Qt</code> namespace. Use the latter wherever you can to ensure compliance with the operating system standards.</p> <p>The completed code, showing the toolbar buttons and menus, is shown below:</p> <div class="code-block"> <span class="code-block-language code-block-python">python</span> <pre><code class="python">class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("My App") label = QLabel("Hello!") # The `Qt` namespace has a lot of attributes to customize # widgets. See: http://doc.qt.io/qt-6/qt.html label.setAlignment(Qt.AlignmentFlag.AlignCenter) # Set the central widget of the Window. Widget will expand # to take up all the space in the window by default. self.setCentralWidget(label) toolbar = QToolBar("My main toolbar") toolbar.setIconSize(QSize(16, 16)) self.addToolBar(toolbar) button_action = QAction(QIcon("bug.png"), "&amp;Your button", self) button_action.setStatusTip("This is your button") button_action.triggered.connect(self.toolbar_button_clicked) button_action.setCheckable(True) # You can enter keyboard shortcuts using key names (e.g. Ctrl+p) # Qt.namespace identifiers (e.g. Qt.CTRL + Qt.Key_P) # or system agnostic identifiers (e.g. QKeySequence.Print) button_action.setShortcut(QKeySequence("Ctrl+p")) toolbar.addAction(button_action) toolbar.addSeparator() button_action2 = QAction(QIcon("bug.png"), "Your &amp;button2", self) button_action2.setStatusTip("This is your button2") button_action2.triggered.connect(self.toolbar_button_clicked) button_action2.setCheckable(True) toolbar.addAction(button_action2) toolbar.addWidget(QLabel("Hello")) toolbar.addWidget(QCheckBox()) self.setStatusBar(QStatusBar(self)) menu = self.menuBar() file_menu = menu.addMenu("&amp;File") file_menu.addAction(button_action) file_menu.addSeparator() file_submenu = file_menu.addMenu("Submenu") file_submenu.addAction(button_action2) def toolbar_button_clicked(self, s): print("click", s) </code></pre> </div> <p>Experiment with building your own menus using <code>QAction</code> and <code>QMenu</code>.</p></p> <p> <em><a href="https://www.pythonguis.com/tutorials/pyside6-actions-toolbars-menus/">April 11, 2025 06:00 AM UTC</a></em> </p> <h2>April 10, 2025</h2> <hr /><h3 class="post"><a href="https://www.paulox.net/" title="Paolo Melchiorre - Python 🐍">Paolo Melchiorre</a></h3> <h4><a href="https://www.paulox.net/2025/04/11/maps-with-django-part-3-geodjango-pillow-and-gps/">Maps with Django⁽³⁾: GeoDjango, Pillow &amp; GPS</a></h4> <p> <p>A quick-start guide to create a web map with images, using the Python-based Django web framework, leveraging its GeoDjango module, and Pillow, the Python imaging library, to extract <span class="caps">GPS</span> information from&nbsp;images.</p></p> <p> <em><a href="https://www.paulox.net/2025/04/11/maps-with-django-part-3-geodjango-pillow-and-gps/">April 10, 2025 10:00 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://testandcode.com" title="Test &amp;amp; Code">Test and Code</a></h3> <h4><a href="https://testandcode.com/episodes/pytest-repeat-works-fine-on-python-3-14">pytest-repeat - works fine on Python 3.14</a></h4> <p> <p>pytest-repeat </p><ul><li>is a pytest plugin that makes it easy to repeat a single test, or multiple tests, a specific number of times.  </li><li>works fine on Python 3.14</li><li>is tested on Python 3.9-3.14</li><li>probably works fine still on 3.7 &amp; 3.8</li></ul><p>This episode also discusses the attempted April Fools episode.</p><p>Links:</p><ul><li><a href="https://github.com/pytest-dev/pytest-repeat">pytest-repeat</a></li><li>The April Fools episode: <a href="https://testandcode.com/episodes/python-3-14-wont-repeat-with-pytest-repeat">Python 3.14 won't repeat with pytest-repeat<strong><br /></strong></a><br /></li></ul> <br /><p><strong>Sponsored by: </strong></p><ul><li><a href="https://file+.vscode-resource.vscode-cdn.net/Users/brianokken/projects/test_and_code_notes/new_ad.md">The Complete pytest course</a> is now a bundle, with each part available separately.<ul><li><a href="https://courses.pythontest.com/pytest-primary-power">pytest Primary Power</a> teaches the super powers of pytest that you need to learn to use pytest effectively.</li><li><a href="https://courses.pythontest.com/using-pytest-with-projects">Using pytest with Projects</a> has lots of "when you need it" sections like debugging failed tests, mocking, testing strategy, and CI</li><li>Then <a href="https://courses.pythontest.com/pytest-booster-rockets">pytest Booster Rockets</a> can help with advanced parametrization and building plugins.</li></ul></li><li>Whether you need to get started with pytest today, or want to power up your pytest skills, <a href="https://courses.pythontest.com">PythonTest</a> has a course for you.<p></p></li></ul> <strong> <a href="https://www.patreon.com/c/testpodcast" rel="payment" title="★ Support this podcast on Patreon ★">★ Support this podcast on Patreon ★</a> </strong>&lt;p&gt;pytest-repeat &lt;/p&gt;&lt;ul&gt;&lt;li&gt;is a pytest plugin that makes it easy to repeat a single test, or multiple tests, a specific number of times.  &lt;/li&gt;&lt;li&gt;works fine on Python 3.14&lt;/li&gt;&lt;li&gt;is tested on Python 3.9-3.14&lt;/li&gt;&lt;li&gt;probably works fine still on 3.7 &amp;amp; 3.8&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This episode also discusses the attempted April Fools episode.&lt;/p&gt;&lt;p&gt;Links:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="https://github.com/pytest-dev/pytest-repeat"&gt;pytest-repeat&lt;/a&gt;&lt;/li&gt;&lt;li&gt;The April Fools episode: &lt;a href="https://testandcode.com/episodes/python-3-14-wont-repeat-with-pytest-repeat"&gt;Python 3.14 won't repeat with pytest-repeat&lt;strong&gt;&lt;br&gt;&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;&lt;/li&gt;&lt;/ul&gt; &lt;br&gt;&lt;p&gt;&lt;strong&gt;Sponsored by: &lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="https://file+.vscode-resource.vscode-cdn.net/Users/brianokken/projects/test_and_code_notes/new_ad.md"&gt;The Complete pytest course&lt;/a&gt; is now a bundle, with each part available separately.&lt;ul&gt;&lt;li&gt;&lt;a href="https://courses.pythontest.com/pytest-primary-power"&gt;pytest Primary Power&lt;/a&gt; teaches the super powers of pytest that you need to learn to use pytest effectively.&lt;/li&gt;&lt;li&gt;&lt;a href="https://courses.pythontest.com/using-pytest-with-projects"&gt;Using pytest with Projects&lt;/a&gt; has lots of "when you need it" sections like debugging failed tests, mocking, testing strategy, and CI&lt;/li&gt;&lt;li&gt;Then &lt;a href="https://courses.pythontest.com/pytest-booster-rockets"&gt;pytest Booster Rockets&lt;/a&gt; can help with advanced parametrization and building plugins.&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;Whether you need to get started with pytest today, or want to power up your pytest skills, &lt;a href="https://courses.pythontest.com"&gt;PythonTest&lt;/a&gt; has a course for you.&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;strong&gt; &lt;a href="https://www.patreon.com/c/testpodcast" rel="payment" title="★ Support this podcast on Patreon ★"&gt;★ Support this podcast on Patreon ★&lt;/a&gt; &lt;/strong&gt;</p> <p> <em><a href="https://testandcode.com/episodes/pytest-repeat-works-fine-on-python-3-14">April 10, 2025 09:30 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://devblogs.microsoft.com/python/" title="Microsoft for Python Developers Blog">Python Engineering at Microsoft</a></h3> <h4><a href="https://devblogs.microsoft.com/python/pytexas-2025/">Microsoft at PyTexas 2025: Join Us for a Celebration of Python and Innovation</a></h4> <p> <p>Microsoft is thrilled to announce our participation in <a href="https://www.pytexas.org/2025/">PyTexas 2025</a>, taking place this year in the vibrant city of Austin, Texas! As one of the premier regional Python conferences, PyTexas provides a unique opportunity for developers, innovators, and enthusiasts to come together and explore the limitless potential of Python programming. At this year’s event, Microsoft is proud to contribute to the community’s growth and excitement by hosting a booth and delivering an engaging talk.</p> <h1>A Strong Partnership with the Python Community</h1> <p>Python has become one of the most popular programming languages in the world, and its versatility spans industries ranging from web development to artificial intelligence, data science, and beyond. At Microsoft, we recognize the transformative power of Python and are deeply committed to fostering collaboration and innovation within the Python ecosystem. Our involvement in PyTexas 2025 is part of that commitment, as we seek to connect with developers, showcase our tools and platforms, and learn from the brilliant minds in the community.</p> <h1>Visit Us at the Microsoft Booth</h1> <p>We’ll have demos featuring many of the products and services Microsoft offers for Python development. You’ll see VS Code and Notebooks, PostgreSQL, Azure Cosmos DB, AI Foundry/Agents, Azure &amp; Fabric Functions for Python, and more! We’ll have experts to answer questions you might have, including the Austin-based Azure Functions Python team.</p> <h1>Talk: Generators: The Unsung Hero of Async Programming</h1> <p>Saturday at 11:55 AM (Central Time), Chris Anderson will be giving a talk on Generators and how they can be used for durable, asynchronous workflows. Find out more details <a href="https://www.pytexas.org/2025/schedule/talks/#generators-the-unsung-hero-of-async-programming">here</a>.</p> <h1>Python @ Microsoft</h1> <p>Whether you are attending PyTexas this year or not, we still hope to get a chance to connect with you through our <a href="https://aka.ms/python-discord">Microsoft Python Discord</a>. Make sure to check out just some of what we have to offer at Microsoft for all your Python needs:</p> <ul> <li><a href="https://code.visualstudio.com/docs/python/python-tutorial">Get started with Python in Visual Studio Code</a></li> <li><a href="https://code.visualstudio.com/docs/python/jupyter-support">Working with Jupyter Notebooks in Visual Studio Code</a></li> <li><a href="https://learn.microsoft.com/en-us/azure/ai-foundry/how-to/develop/sdk-overview"><span class="x x-first x-last">Build AI Apps with </span>Python <span class="x x-first x-last">with AI Foundry</span></a></li> <li><a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-python?utm_campaign_id=python-pytexas-psfblog">Create your first Python function in Azure</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/app-service/containers/quickstart-python?utm_campaign_id=python-pytexas-psfblog">Deploy a Python Web App to Azure App Service</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/notebooks/use-machine-learning-services-jupyter-notebooks">Use Azure Machine Learning service with Azure Notebooks</a></li> <li><a href="https://docs.microsoft.com/en-us/azure/developer/python/azure-sdk-overview">Use the Azure SDK for Python</a></li> </ul> <p>The post <a href="https://devblogs.microsoft.com/python/pytexas-2025/">Microsoft at PyTexas 2025: Join Us for a Celebration of Python and Innovation</a> appeared first on <a href="https://devblogs.microsoft.com/python">Microsoft for Python Developers Blog</a>.</p></p> <p> <em><a href="https://devblogs.microsoft.com/python/pytexas-2025/">April 10, 2025 05:14 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://talkpython.fm/" title="Talk Python To Me">Talk Python to Me</a></h3> <h4><a href="https://talkpython.fm/episodes/show/500/django-simple-deploy-and-other-devops-things">#500: Django Simple Deploy and other DevOps Things</a></h4> <p> We're sitting down with Eric Matthes, the educator, author, and developer behind Django Simple Deploy. If you've ever struggled with taking that final step of getting your Django app onto a live server (without spending days wrestling with DevOps complexities), then give Django Simple Deploy a look. Eric shares how Django Simple Deploy automates away the boilerplate parts of deployment, so you can focus on building features instead of deciphering endless configs. We'll talk about this new project's journey to 1.0, the range of hosting platforms it supports, and why it's not just for beginners.&lt;br/&gt; &lt;br/&gt; &lt;strong&gt;Episode sponsors&lt;/strong&gt;&lt;br/&gt; &lt;br/&gt; &lt;a href='https://talkpython.fm/worth'&gt;Worth Search&lt;/a&gt;&lt;br&gt; &lt;a href='https://talkpython.fm/training'&gt;Talk Python Courses&lt;/a&gt;&lt;br/&gt; &lt;br/&gt; &lt;h2 class="links-heading"&gt;Links from the show&lt;/h2&gt; &lt;div&gt;&lt;strong&gt;django-simple-deploy documentation&lt;/strong&gt;: &lt;a href="https://django-simple-deploy.readthedocs.io/en/latest/?featured_on=talkpython" target="_blank" &gt;readthedocs.io&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;django-simple-deploy repository&lt;/strong&gt;: &lt;a href="https://github.com/django-simple-deploy/django-simple-deploy?featured_on=talkpython" target="_blank" &gt;github.com&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Python Crash Course book&lt;/strong&gt;: &lt;a href="https://ehmatthes.github.io/pcc/?featured_on=talkpython" target="_blank" &gt;ehmatthes.github.io&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Code Red&lt;/strong&gt;: &lt;a href="https://www.codered.cloud/?featured_on=talkpython" target="_blank" &gt;codered.cloud&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Docker&lt;/strong&gt;: &lt;a href="https://www.docker.com/?featured_on=talkpython" target="_blank" &gt;docker.com&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Caddy&lt;/strong&gt;: &lt;a href="https://caddyserver.com/?featured_on=talkpython" target="_blank" &gt;caddyserver.com&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Bunny.net CDN&lt;/strong&gt;: &lt;a href="https://bunny.net/cdn/?featured_on=talkpython" target="_blank" &gt;bunny.net&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Platform.sh&lt;/strong&gt;: &lt;a href="https://platform.sh/?featured_on=talkpython" target="_blank" &gt;platform.sh&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;fly.io&lt;/strong&gt;: &lt;a href="https://fly.io/?featured_on=talkpython" target="_blank" &gt;fly.io&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Heroku&lt;/strong&gt;: &lt;a href="https://www.heroku.com/?featured_on=talkpython" target="_blank" &gt;heroku.com&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Watch this episode on YouTube&lt;/strong&gt;: &lt;a href="https://www.youtube.com/watch?v=ljzIDybvv6Q" target="_blank" &gt;youtube.com&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Episode transcripts&lt;/strong&gt;: &lt;a href="https://talkpython.fm/episodes/transcript/500/django-simple-deploy-and-other-devops-things" target="_blank" &gt;talkpython.fm&lt;/a&gt;&lt;br/&gt; &lt;br/&gt; &lt;strong&gt;--- Stay in touch with us ---&lt;/strong&gt;&lt;br/&gt; &lt;strong&gt;Subscribe to Talk Python on YouTube&lt;/strong&gt;: &lt;a href="https://talkpython.fm/youtube" target="_blank" &gt;youtube.com&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Talk Python on Bluesky&lt;/strong&gt;: &lt;a href="https://bsky.app/profile/talkpython.fm" target="_blank" &gt;@talkpython.fm at bsky.app&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Talk Python on Mastodon&lt;/strong&gt;: &lt;a href="https://fosstodon.org/web/@talkpython" target="_blank" &gt;&lt;i class="fa-brands fa-mastodon"&gt;&lt;/i&gt;talkpython&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Michael on Bluesky&lt;/strong&gt;: &lt;a href="https://bsky.app/profile/mkennedy.codes?featured_on=talkpython" target="_blank" &gt;@mkennedy.codes at bsky.app&lt;/a&gt;&lt;br/&gt; &lt;strong&gt;Michael on Mastodon&lt;/strong&gt;: &lt;a href="https://fosstodon.org/web/@mkennedy" target="_blank" &gt;&lt;i class="fa-brands fa-mastodon"&gt;&lt;/i&gt;mkennedy&lt;/a&gt;&lt;br/&gt;&lt;/div&gt;</p> <p> <em><a href="https://talkpython.fm/episodes/show/500/django-simple-deploy-and-other-devops-things">April 10, 2025 08:00 AM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://zato.io/en/blog" title="Python API Integration Insights with Zato">Zato Blog</a></h3> <h4><a href="https://zato.io/en/blog/airport-integrations-in-python.html">Airport Integrations in Python</a></h4> <p> <h1 class="insights-header"> <a class="rounded nav-title" href="https://zato.io/en/blog/airport-integrations-in-python.html">Airport Integrations in Python</a> </h1> <div class="article-meta"> 2025-04-10, by Dariusz Suchojad <img src="https://upcdn.io/kW15bqq/raw/root/static/blog/authors/dsuch.webp" alt="" width="35" class="insights-author" /> </div> <p>Did you know you can use Python as an <a href="https://zato.io/articles/integration-platform.html">integration platform</a> for your airport systems? It's Open Source too.</p> <p>From AODB, transportation, business operations and partner networks, to IoT, cloud and hybrid deployments, you can now use Python to build flexible, scalable and future-proof architectures that integrate your airport systems and support your master plan.</p> <p><a href="https://zato.io/en/industry/airports/index.html"><img src="https://upcdn.io/kW15bqq/raw/root/static/img/ads/airport-integrations-in-python.webp" class="img-responsive docs-article" alt="Airport integrations in Python" /> </a></p> <p><strong>➤ <a href="https://zato.io/en/industry/airports/index.html">Read here</a> about what is possible and learn more why Python and Open Source are the right choice.</strong> <br /> ➤ <a href="https://zato.io/articles/open-source-ipaas.html">Open-source iPaaS</a> in Python</p> <div class="nav-title footer"> <a class="rounded nav-title" href="https://zato.io/en/blog/">More blog posts</a><span class="arrow-footer">➤</span> </div></p> <p> <em><a href="https://zato.io/en/blog/airport-integrations-in-python.html">April 10, 2025 08:00 AM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://www.europython-society.org/" title="EuroPython Society">EuroPython Society</a></h3> <h4><a href="https://www.europython-society.org/board-report-for-march/">Board Report for March 2025</a></h4> <p> <p>In March, we achieved two significant milestones alongside several smaller improvements and operational work.</p><p>We launched our ticket sales, dedicating substantial effort to setting up the ticket shop, coordinating with multiple teams, and promoting the event.</p><p>We also open our call for sponsors, investing considerable time in budgeting, setting up and improving the process, and onboarding our sponsors.<br /></p><h1 id="individual-reports">Individual reports:</h1><h2 id="artur">Artur</h2><ul><li>Budget projection updates</li><li>Ticket launch and related activities.</li><li>Sponsor setup update and managing some of the sponsor interactions</li><li>Configuration upgrade of our static sever.</li><li>Catering negotiations.</li><li>Internal discord bot updates.</li><li>Financial aid meetings.</li><li>Billing flow updates.</li></ul><h2 id="mia">Mia</h2><ul><li>Website: Ticket requirements, PR review, and content updates.</li><li>Design: T-shirt review, creation of social media assets for ticket sales and sponsors, and a briefing with a designer.</li><li>Budget: Budget proposal.</li><li>Sponsors: Cold emailing, sponsor packages, and coordination of the sponsor launch.</li><li>Comms: Creation, review, and scheduling of content for the ticket sale launch and call for sponsors; speaker cards; automation proof of concept; International Women&#x2019;s Day communications; newsletter writing and review; board report; and YouTube videos communications.</li><li>PyCon US Booth: Coordination and paperwork.</li><li>Grants Program: Communication with recipients.</li><li>Venue: Re-signed contract.</li><li>Calls with the event manager. <br /></li></ul><h2 id="aris">Aris</h2><ul><li>OPS work, meetings, planning.</li><li>Accounting updates.</li><li>Billing workflow.</li><li>Payments<br /></li></ul><h2 id="ege">Ege</h2><ul><li>Read the Docs previews</li><li>Programme API setup.</li><li>Implementing a redirection system in the website.</li><li>Dependency updates and tailwind migration.</li><li>Website: issues and PR reviews. <br /></li></ul><h2 id="shekhar">Shekhar</h2><ul><li>Financial Aid: Planned how to handle responses and evaluated the process.</li><li>Ops: GitHub for task tracking and monitored integrations with team members.<br /></li></ul><h2 id="cyril">Cyril</h2><ul><li>&#x2026;</li></ul><h2 id="anders">Anders</h2><ul><li>&#x2026;<br /></li></ul></p> <p> <em><a href="https://www.europython-society.org/board-report-for-march/">April 10, 2025 07:51 AM UTC</a></em> </p> <h4><a href="https://www.europython-society.org/brno-python-pizza-great-things-come-in-threes/">Brno Python Pizza, great things come in threes</a></h4> <p> <p>We, the EuroPython Society, were proud partners of Brno Python Pizza. Here&#x2019;s what they shared with us about the event.</p><hr /><p>By now, the concept of combining Pizza and Python is well established and documented, it just works! But adding Brno into the mix makes it feel a little bit special for our local community. This was the second Pizza Python in Czechia, following the highly successful event in Prague.</p><p>While Prague set a high bar with its buzzing gathering of Python enthusiasts and pizza lovers, Brno brought its own unique flavor to the table, that was definitely no pineapple.<br /></p><h2 id="attendees">Attendees</h2><p>We capped the event at 120 attendees &#x2014; the comfortable maximum for our venue. While we didn&#x2019;t require attendees to disclose gender or dietary info, we did include optional fields in the ticket form. Based on the responses, we had 99 men and 34 women registered, including both in-person and online tickets. Unfortunately, nobody ticked the box for non-binary or transgender options, which will serve as valuable information for future inclusivity improvements..</p><p>We also asked about dietary preferences so we could make sure everyone would be fed and happy. The majority (98) had no restrictions, but we were glad to accommodate 6 vegetarians, 6 vegans, 2 gluten-free eaters, 1 halal, and one &#x201C;no bananas &#x1F34C;&#x201D;. The last one was the hardest to accommodate because when we called up pizzerias and told them how many pizzas we would like, they thought we were certainly bananas&#x2026;</p><p>The event ran smoothly, with no breaches of the Code of Conduct reported&#x2014;a testament to the respectful and friendly atmosphere fostered by the community.</p><h2 id="the-menu">The menu</h2><p>At Brno Python Pizza, we served up a feast sliced into 21 talks on the schedule, several lightning talks and plenty of opportunities to network. Each talk was kept short and snappy, capped at 10 minutes, ensuring a fast-paced and engaging experience for attendees. This is absolutely perfect for us that are having slightly underdeveloped focus glands. Not everyone likes mushrooms on their pizza, neither does everyone enjoy listening purely about AI advances. That&#x2019;s why we curated a diverse menu of topics to cater to our diverse audience.<br /></p><h2 id="feedback-things-to-improve-and-the-future">Feedback, Things to improve and the Future<br /></h2><p>From what we&#x2019;ve gathered, people enjoyed the event and are eager to attend again. They enjoyed the food, talks and that topics were varied and the overall format of the event.</p><p>The feedback gathering is also the main thing to improve as we have only anecdotal data. For the next time we have to provide people with a feedback form right after the event ends. &#xA0;<br /></p><p>If you ask us today if we would like to organise another edition of Python Pizza Brno, we will say &quot;definitely yes&quot;, but we will keep the possible date a secret. <br /></p><h2 id="stream-and-more-photos">Stream and more photos</h2><p>Stream is available <a href="https://www.youtube.com/live/ffiQSqOUY1g">here</a> and rest of photos <a href="https://photos.app.goo.gl/z138MLx6yX1DHpoq5">here</a>.</p><img src="https://www.europython-society.org/content/images/2025/04/DSCF7503.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7566.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7593.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7608.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7669.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7696.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7702.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7706.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7771.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF7971.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /><img src="https://www.europython-society.org/content/images/2025/04/DSCF8061.jpeg" class="kg-image" alt="alt" width="2000" height="1333" /></p> <p> <em><a href="https://www.europython-society.org/brno-python-pizza-great-things-come-in-threes/">April 10, 2025 07:51 AM UTC</a></em> </p> <h2>April 09, 2025</h2> <hr /><h3 class="post"><a href="https://testdriven.io/" title="TestDriven.io Blog Feed - Django">TestDriven.io</a></h3> <h4><a href="https://testdriven.io/blog/django-admin-celery/">Running Background Tasks from Django Admin with Celery</a></h4> <p> This tutorial looks at how to run background tasks directly from Django admin using Celery.</p> <p> <em><a href="https://testdriven.io/blog/django-admin-celery/">April 09, 2025 10:28 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://mirekdlugosz.com/" title="Mirek Długosz personal website - planet Python">Mirek Długosz</a></h3> <h4><a href="https://mirekdlugosz.com/blog/2025/pytest-running-multiple-tests-with-names-that-might-contain-spaces/">pytest: running multiple tests with names that might contain spaces</a></h4> <p> <p>You most certainly know that you can run a single test in entire suite by passing the full&nbsp;path:</p> <div class="highlight"><pre><span></span><code><span class="nv">PRODUCT_ENV</span><span class="o">=</span><span class="s1">'stage'</span><span class="w"> </span>pytest<span class="w"> </span>-v<span class="w"> </span>--critical<span class="w"> </span>tests/test_mod.py::test_func<span class="o">[</span>x1<span class="o">]</span> </code></pre></div> <p>This gets old when you want to run around 3 or more tests. In that case, you might end up putting paths into a file and passing this file content as command arguments. You probably know that,&nbsp;too:</p> <div class="highlight"><pre><span></span><code><span class="nv">PRODUCT_ENV</span><span class="o">=</span><span class="s1">'stage'</span><span class="w"> </span>pytest<span class="w"> </span>-v<span class="w"> </span>--critical<span class="w"> </span><span class="k">$(</span>&lt;<span class="w"> </span>/tmp/ci-failures.txt<span class="k">)</span> </code></pre></div> <p>However, this will fail if your test has space in the name (probably as pytest parameter value). Shell still performs command arguments splitting on&nbsp;space.</p> <p>To avoid this problem, use <code>cat</code> and <code>xargs</code>:</p> <div class="highlight"><pre><span></span><code>cat<span class="w"> </span>/tmp/ci-failures.txt<span class="w"> </span><span class="p">|</span><span class="nv">PRODUCT_ENV</span><span class="o">=</span><span class="s1">'stage'</span><span class="w"> </span>xargs<span class="w"> </span>-n<span class="w"> </span><span class="m">200</span><span class="w"> </span>-d<span class="w"> </span><span class="s1">'\n'</span><span class="w"> </span>pytest<span class="w"> </span>-v<span class="w"> </span>--critical </code></pre></div> <p>I always thought that xargs runs the command for each line from stdin, so I would avoid it when command takes a long time to start. But turns out, xargs is a little more sophisticated - it can group input lines into subclasses and run a command once for each&nbsp;subclass.</p> <p><code>-n 200</code> tells xargs to use no more than 200 items in subclass, effectively forcing it to run pytest command once. <code>-d '\n'</code> tells it to only delimit arguments on newline, removing any special meaning from&nbsp;space.</p> <p><code>PRODUCT_ENV</code> and any other environment variables must be set after the pipe character, or exported beforehand, because each part of shell pipeline is run in a separate&nbsp;subshell.</p> <p>After writing this article, I learned that since pytest 8.2 (released in April 2024), you can achieve the same by asking pytest to parse a file for&nbsp;you:</p> <div class="highlight"><pre><span></span><code><span class="nv">PRODUCT_ENV</span><span class="o">=</span><span class="s1">'stage'</span><span class="w"> </span>pytest<span class="w"> </span>-v<span class="w"> </span>--critical<span class="w"> </span>@/tmp/ci-failures.txt </code></pre></div> <p>However, everything written above still stands for scenarios where any other shell command is used in place of <code>pytest</code>.</p></p> <p> <em><a href="https://mirekdlugosz.com/blog/2025/pytest-running-multiple-tests-with-names-that-might-contain-spaces/">April 09, 2025 06:28 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://www.pypy.org/" title="PyPy">PyPy</a></h3> <h4><a href="https://www.pypy.org/posts/2025/04/prospero-in-rpython.html">Doing the Prospero-Challenge in RPython</a></h4> <p> <p>Recently I had a lot of fun playing with the <a href="https://www.mattkeeter.com/projects/prospero/">Prospero Challenge</a> by <a href="https://www.mattkeeter.com/">Matt Keeter</a>. The challenge is to render a 1024x1024 image of a quote from The Tempest by Shakespeare. The input is a mathematical formula with 7866 operations, which is evaluated once per pixel.</p> <p>What made the challenge particularly enticing for me personally was the fact that the formula is basically a trace in <a href="https://en.wikipedia.org/wiki/Static_single-assignment_form">SSA-form</a> – a linear sequence of operations, where every variable is assigned exactly once. The challenge is to evaluate the formula as fast as possible. I tried a number of ideas how to speed up execution and will talk about them in this somewhat meandering post. Most of it follows Matt's implementation <a href="https://github.com/mkeeter/fidget">Fidget</a> very closely. There are two points of difference:</p> <ul> <li>I tried to add more peephole optimizations, but they didn't end up helping much.</li> <li>I implemented a "demanded information" optimization that removes a lot of operations by only keeping the sign of the result. This optimization ended up being useful.</li> </ul> <p>Most of the prototyping in this post was done in RPython (a statically typable subset of Python2, that can be compiled to C), but I later rewrote the program in C to get better performance. All the code <a href="https://github.com/cfbolz/pyfidget/">can be found on Github</a>.</p> <h3 id="input-program">Input program</h3> <p>The input program is a sequence of operations, like this:</p> <div class="code"><pre class="code literal-block"><span class="n">_0</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="mf">2.95</span> <span class="n">_1</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">x</span> <span class="n">_2</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="mf">8.13008</span> <span class="n">_3</span><span class="w"> </span><span class="n">mul</span><span class="w"> </span><span class="n">_1</span><span class="w"> </span><span class="n">_2</span> <span class="n">_4</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">_0</span><span class="w"> </span><span class="n">_3</span> <span class="n">_5</span><span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="mf">3.675</span> <span class="n">_6</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">_5</span><span class="w"> </span><span class="n">_3</span> <span class="n">_7</span><span class="w"> </span><span class="n">neg</span><span class="w"> </span><span class="n">_6</span> <span class="n">_8</span><span class="w"> </span><span class="nb">max</span><span class="w"> </span><span class="n">_4</span><span class="w"> </span><span class="n">_7</span> <span class="o">...</span> </pre></div> <p>The first column is the name of the result variable, the second column is the operation, and the rest are the arguments to the operation. <code>var-x</code> is a special operation that returns the x-coordinate of the pixel being rendered, and equivalently for <code>var-y</code> the y-coordinate. The sign of the result gives the color of the pixel, the absolute value is not important.</p> <h3 id="a-baseline-interpreter">A baseline interpreter</h3> <p>To run the program, I first parse them and replace the register names with indexes, to avoid any dictionary lookups at runtime. Then I implemented a simple interpreter for the SSA-form input program. The interpreter is a simple register machine, where every operation is executed in order. The result of the operation is stored into a list of results, and the next operation is executed. This was the slow baseline implementation of the interpreter but it's very useful to compare against the optimized versions.</p> <p>This is roughly what the code looks like</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">DirectFrame</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">program</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="o">=</span> <span class="n">program</span> <span class="bp">self</span><span class="o">.</span><span class="n">next</span> <span class="o">=</span> <span class="kc">None</span> <span class="k">def</span><span class="w"> </span><span class="nf">run_floats</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">setxyz</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">run</span><span class="p">()</span> <span class="k">def</span><span class="w"> </span><span class="nf">setxyz</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">z</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="n">program</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="n">num_ops</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">num_operations</span><span class="p">()</span> <span class="n">floatvalues</span> <span class="o">=</span> <span class="p">[</span><span class="mf">0.0</span><span class="p">]</span> <span class="o">*</span> <span class="n">num_ops</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_ops</span><span class="p">):</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">const</span><span class="p">:</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">op</span><span class="p">]</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">consts</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="k">continue</span> <span class="n">farg0</span> <span class="o">=</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="n">farg1</span> <span class="o">=</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">arg1</span><span class="p">]</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_x</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_y</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_z</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">add</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">sub</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">mul</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">mul</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">max</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">max</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">min</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">min</span><span class="p">(</span><span class="n">farg0</span><span class="p">,</span> <span class="n">farg1</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">square</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">square</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">sqrt</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">exp</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">exp</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">neg</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">elif</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">abs</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">abs</span><span class="p">(</span><span class="n">farg0</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">assert</span> <span class="mi">0</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">op</span><span class="p">]</span> <span class="o">=</span> <span class="n">res</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">floatvalues</span><span class="p">[</span><span class="n">num_ops</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="k">def</span><span class="w"> </span><span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="n">arg0</span> <span class="o">+</span> <span class="n">arg1</span> <span class="k">def</span><span class="w"> </span><span class="nf">sub</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="n">arg0</span> <span class="o">-</span> <span class="n">arg1</span> <span class="k">def</span><span class="w"> </span><span class="nf">mul</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="n">arg0</span> <span class="o">*</span> <span class="n">arg1</span> <span class="k">def</span><span class="w"> </span><span class="nf">max</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="nb">max</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">min</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">):</span> <span class="k">return</span> <span class="nb">min</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">square</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="n">val</span> <span class="o">=</span> <span class="n">arg0</span> <span class="k">return</span> <span class="n">val</span><span class="o">*</span><span class="n">val</span> <span class="k">def</span><span class="w"> </span><span class="nf">sqrt</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="k">return</span> <span class="n">math</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">exp</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="k">return</span> <span class="n">math</span><span class="o">.</span><span class="n">exp</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">neg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="k">return</span> <span class="o">-</span><span class="n">arg0</span> <span class="k">def</span><span class="w"> </span><span class="nf">abs</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">):</span> <span class="k">return</span> <span class="nb">abs</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> </pre></div> <p>Running the naive interpreter on the prospero image file is super slow, since it performs 7866 * 1024 * 1024 float operations, plus the interpretation overhead.</p> <h3 id="using-quadtrees-to-render-the-picture">Using Quadtrees to render the picture</h3> <p>The approach that Matt describes in his really excellent <a href="https://www.youtube.com/watch?v=UxGxsGnbyJ4">talk</a> is to use <a href="https://en.wikipedia.org/wiki/Quadtree">quadtrees</a>: recursively subdivide the image into quadrants, and evaluate the formula in each quadrant. For every quadrant you can simplify the formula by doing a range analysis. After a few recursion steps, the formula becomes significantly smaller, often only a few hundred or a few dozen operations.</p> <p>At the bottom of the recursion you either reach a square where the range analysis reveals that the sign for all pixels is determined, then you can fill in all the pixels of the quadrant. Or you can evaluate the (now much simpler) formula in the quadrant by executing it for every pixel.</p> <p>This is an interesting use case of JIT compiler/optimization techniques, requiring the optimizer itself to execute really quickly since it is an essential part of the performance of the algorithm. The optimizer runs literally hundreds of times to render a single image. If the algorithm is used for 3D models it becomes even more crucial.</p> <h3 id="writing-a-simple-optimizer">Writing a simple optimizer</h3> <p>Implementing the quadtree recursion is straightforward. Since the program has no control flow the optimizer is very simple to write. I've written a couple of blog posts on how to easily write optimizers for linear sequences of operations, and I'm using the approach described in these <a href="https://pypy.org/categories/toy-optimizer.html">Toy Optimizer</a> posts. The interval analysis is basically an <a href="https://pypy.org/posts/2024/08/toy-knownbits.html">abstract interpretation</a> of the operations. The optimizer does a sequential forward pass over the input program. For every operation, the output interval is computed. The optimizer also performs optimizations based on the computed intervals, which helps in reducing the number of operations executed (I'll talk about this further down).</p> <p>Here's a sketch of the Python code that does the optimization:</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">Optimizer</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">program</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="o">=</span> <span class="n">program</span> <span class="n">num_operations</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">num_operations</span><span class="p">()</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span> <span class="o">=</span> <span class="n">ProgramBuilder</span><span class="p">(</span><span class="n">num_operations</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span> <span class="o">=</span> <span class="n">IntervalFrame</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">program</span><span class="p">)</span> <span class="c1"># old index -&gt; new index</span> <span class="bp">self</span><span class="o">.</span><span class="n">opreplacements</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">num_operations</span> <span class="bp">self</span><span class="o">.</span><span class="n">index</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">def</span><span class="w"> </span><span class="nf">get_replacement</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">op</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opreplacements</span><span class="p">[</span><span class="n">op</span><span class="p">]</span> <span class="k">def</span><span class="w"> </span><span class="nf">newop</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">arg1</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span><span class="o">.</span><span class="n">add_op</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">newconst</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span> <span class="n">const</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span><span class="o">.</span><span class="n">add_const</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">minvalues</span><span class="p">[</span><span class="n">const</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">maxvalues</span><span class="p">[</span><span class="n">const</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span> <span class="c1">#self.seen_consts[value] = const</span> <span class="k">return</span> <span class="n">const</span> <span class="k">def</span><span class="w"> </span><span class="nf">optimize</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">f</span><span class="p">):</span> <span class="n">program</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">setxyz</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span> <span class="n">numops</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">num_operations</span><span class="p">()</span> <span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">numops</span><span class="p">):</span> <span class="n">newop</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_optimize_op</span><span class="p">(</span><span class="n">index</span><span class="p">)</span> <span class="bp">self</span><span class="o">.</span><span class="n">opreplacements</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">newop</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opreplacements</span><span class="p">[</span><span class="n">numops</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="k">def</span><span class="w"> </span><span class="nf">_optimize_op</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">op</span><span class="p">):</span> <span class="n">program</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="n">intervalframe</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">assert</span> <span class="n">arg0</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="k">assert</span> <span class="n">arg1</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_x</span><span class="p">:</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">minx</span> <span class="n">maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxx</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">var_x</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_y</span><span class="p">:</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">miny</span> <span class="n">maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxy</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">var_y</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_z</span><span class="p">:</span> <span class="n">minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">minz</span> <span class="n">maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxz</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">var_z</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">const</span><span class="p">:</span> <span class="n">const</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">consts</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">newconst</span><span class="p">(</span><span class="n">const</span><span class="p">)</span> <span class="n">arg0</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_replacement</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="n">arg1</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_replacement</span><span class="p">(</span><span class="n">arg1</span><span class="p">)</span> <span class="k">assert</span> <span class="n">arg0</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="k">assert</span> <span class="n">arg1</span> <span class="o">&gt;=</span> <span class="mi">0</span> <span class="n">arg0minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">minvalues</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="n">arg0maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxvalues</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="n">arg1minimum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">minvalues</span><span class="p">[</span><span class="n">arg1</span><span class="p">]</span> <span class="n">arg1maximum</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">maxvalues</span><span class="p">[</span><span class="n">arg1</span><span class="p">]</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_neg</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">min</span><span class="p">:</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_min</span><span class="p">(</span><span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">)</span> <span class="o">...</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_default</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">func</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">arg1</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_set</span><span class="p">(</span><span class="n">newop</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">)</span> <span class="k">return</span> <span class="n">newop</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_neg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">):</span> <span class="c1"># peephole rules go here, see below</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_neg</span><span class="p">(</span><span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="p">)</span> <span class="nd">@symmetric</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_min</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">):</span> <span class="c1"># peephole rules go here, see below</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_max</span><span class="p">(</span><span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">max</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="o">...</span> </pre></div> <p>The resulting optimized traces are then simply interpreted at the bottom of the quadtree recursion. Matt talks about also generating machine code from them, but when I tried to use PyPy's JIT for that it was way too slow at producing machine code.</p> <h3 id="testing-soundness-of-the-interval-abstract-domain">Testing soundness of the interval abstract domain</h3> <p>To make sure that my interval computation in the optimizer is correct, I implemented a hypothesis-based property based test. It checks the abstract transfer functions of the interval domain for soundness. It does so by generating random concrete input values for an operation and random intervals that surround the random concrete values, then performs the concrete operation to get the concrete output, and finally checks that the abstract transfer function applied to the input intervals gives an interval that contains the concrete output.</p> <p>For example, the random test for the <code>square</code> operation would look like this:</p> <div class="code"><pre class="code literal-block"><span class="kn">from</span><span class="w"> </span><span class="nn">hypothesis</span><span class="w"> </span><span class="kn">import</span> <span class="n">given</span><span class="p">,</span> <span class="n">strategies</span><span class="p">,</span> <span class="n">assume</span> <span class="kn">from</span><span class="w"> </span><span class="nn">pyfidget.vm</span><span class="w"> </span><span class="kn">import</span> <span class="n">IntervalFrame</span><span class="p">,</span> <span class="n">DirectFrame</span> <span class="kn">import</span><span class="w"> </span><span class="nn">math</span> <span class="n">regular_floats</span> <span class="o">=</span> <span class="n">strategies</span><span class="o">.</span><span class="n">floats</span><span class="p">(</span><span class="n">allow_nan</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">allow_infinity</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">make_range_and_contained_float</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">):</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">([</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">])</span> <span class="k">return</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span> <span class="n">frame</span> <span class="o">=</span> <span class="n">DirectFrame</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span> <span class="n">intervalframe</span> <span class="o">=</span> <span class="n">IntervalFrame</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span> <span class="n">range_and_contained_float</span> <span class="o">=</span> <span class="n">strategies</span><span class="o">.</span><span class="n">builds</span><span class="p">(</span><span class="n">make_range_and_contained_float</span><span class="p">,</span> <span class="n">regular_floats</span><span class="p">,</span> <span class="n">regular_floats</span><span class="p">,</span> <span class="n">regular_floats</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">contains</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="n">rmin</span><span class="p">,</span> <span class="n">rmax</span><span class="p">):</span> <span class="k">if</span> <span class="n">math</span><span class="o">.</span><span class="n">isnan</span><span class="p">(</span><span class="n">rmin</span><span class="p">)</span> <span class="ow">or</span> <span class="n">math</span><span class="o">.</span><span class="n">isnan</span><span class="p">(</span><span class="n">rmax</span><span class="p">):</span> <span class="k">return</span> <span class="kc">True</span> <span class="k">return</span> <span class="n">rmin</span> <span class="o">&lt;=</span> <span class="n">res</span> <span class="o">&lt;=</span> <span class="n">rmax</span> <span class="nd">@given</span><span class="p">(</span><span class="n">range_and_contained_float</span><span class="p">)</span> <span class="k">def</span><span class="w"> </span><span class="nf">test_square</span><span class="p">(</span><span class="n">val</span><span class="p">):</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span> <span class="o">=</span> <span class="n">val</span> <span class="n">rmin</span><span class="p">,</span> <span class="n">rmax</span> <span class="o">=</span> <span class="n">intervalframe</span><span class="o">.</span><span class="n">_square</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="n">res</span> <span class="o">=</span> <span class="n">frame</span><span class="o">.</span><span class="n">square</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="k">assert</span> <span class="n">contains</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="n">rmin</span><span class="p">,</span> <span class="n">rmax</span><span class="p">)</span> </pre></div> <p>This test generates a random float <code>b</code>, and two other floats <code>a</code> and <code>c</code> such that the interval <code>[a, c]</code> contains <code>b</code>. The test then checks that the result of the <code>square</code> operation on <code>b</code> is contained in the interval <code>[rmin, rmax]</code> returned by the abstract transfer function for the <code>square</code> operation.</p> <h3 id="peephole-rewrites">Peephole rewrites</h3> <p>The only optimization that Matt does in his implementation is a peephole optimization rule that removes <code>min</code> and <code>max</code> operations where the intervals of the arguments don't overlap. In that case, the optimizer statically can know which of the arguments will be the result of the operation. I implemented this peephole optimization in my implementation as well, but I also added a few more peephole optimizations that I thought would be useful.</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">Optimizer</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_neg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">):</span> <span class="c1"># new: add peephole rule --x =&gt; x</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0arg0</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">:</span> <span class="k">return</span> <span class="n">arg0arg0</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_neg</span><span class="p">(</span><span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">neg</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="p">)</span> <span class="nd">@symmetric</span> <span class="k">def</span><span class="w"> </span><span class="nf">opt_min</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">):</span> <span class="c1"># Matt's peephole rule</span> <span class="k">if</span> <span class="n">arg0maximum</span> <span class="o">&lt;</span> <span class="n">arg1minimum</span><span class="p">:</span> <span class="k">return</span> <span class="n">arg0</span> <span class="c1"># we can use the intervals to decide which argument will be returned</span> <span class="c1"># new one by me: min(x, x) =&gt; x </span> <span class="k">if</span> <span class="n">arg0</span> <span class="o">==</span> <span class="n">arg1</span><span class="p">:</span> <span class="k">return</span> <span class="n">arg0</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0arg0</span><span class="p">,</span> <span class="n">arg0arg1</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">resultops</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">intervalframe</span><span class="o">.</span><span class="n">_max</span><span class="p">(</span><span class="n">arg0minimum</span><span class="p">,</span> <span class="n">arg0maximum</span><span class="p">,</span> <span class="n">arg1minimum</span><span class="p">,</span> <span class="n">arg1maximum</span><span class="p">)</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">opt_default</span><span class="p">(</span><span class="n">OPS</span><span class="o">.</span><span class="n">max</span><span class="p">,</span> <span class="n">minimum</span><span class="p">,</span> <span class="n">maximum</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span><span class="p">)</span> <span class="o">...</span> </pre></div> <p>However, it turns out that all my attempts at adding other peephole optimization rules were not very useful. Most rules never fired, and the ones that did only had a small effect on the performance of the program. The only peephole optimization that I found to be useful was the one that Matt describes in his talk. Matt's <code>min</code>/<code>max</code> optimization were 96% of all rewrites that my peephole optimizer applied for the <code>prospero.vm</code> input. The remaining 4% of rewrites were (the percentages are of that 4%):</p> <div class="code"><pre class="code literal-block">--x =&gt; x 4.65% (-x)**2 =&gt; x ** 2 0.99% min(x, x) =&gt; x 20.86% min(x, min(x, y)) =&gt; min(x, y) 52.87% max(x, x) =&gt; x 16.40% max(x, max(x, y)) =&gt; max(x, y) 4.23% </pre></div> <p>In the end it turned out that having these extra optimization rules made the total runtime of the system go up. Checking for the rewrites isn't free, and since they apply so rarely they don't pay for their own cost in terms of improved performance.</p> <p>There are some further rules that I tried that never fired at all:</p> <div class="code"><pre class="code literal-block">a <span class="gs">* 0 =&gt; 0</span> <span class="gs">a *</span> 1 =&gt; a a <span class="gs">* a =&gt; a *</span>* 2 a <span class="gs">* -1 =&gt; -a</span> <span class="gs">a + 0 =&gt; a</span> <span class="gs">a - 0 =&gt; a</span> <span class="gs">x - x =&gt; 0</span> <span class="gs">abs(known positive number x) =&gt; x</span> <span class="gs">abs(known negative number x) =&gt; -x</span> <span class="gs">abs(-x) =&gt; abs(x)</span> <span class="gs">(-x) *</span>* 2 =&gt; x ** 2 </pre></div> <p>This investigation is clearly way too focused on a single program and should be re-done with a larger set of example inputs, if this were an actually serious implementation.</p> <h3 id="demanded-information-optimization">Demanded Information Optimization</h3> <p>LLVM has an static analysis pass called 'demanded bits'. It is a backwards analysis that allows you to determine which bits of a value are actually used in the final result. This information can then be used in peephole optimizations. For example, if you have an expression that computes a value, but only the last byte of that value is used in the final result, you can optimize the expression to only compute the last byte.</p> <p>Here's an example. Let's say we first byte-swap a 64-bit int, and then mask off the last byte:</p> <div class="code"><pre class="code literal-block"><span class="kt">uint64_t</span><span class="w"> </span><span class="nf">byteswap_then_mask</span><span class="p">(</span><span class="kt">uint64_t</span><span class="w"> </span><span class="n">a</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">byteswap</span><span class="p">(</span><span class="n">a</span><span class="p">)</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="mh">0xff</span><span class="p">;</span> <span class="p">}</span> </pre></div> <p>In this case, the "demanded bits" of the <code>byteswap(a)</code> expression are <code>0b0...011111111</code>, which inversely means that we don't care about the upper 56 bits. Therefore the whole expression can be optimized to <code>a &gt;&gt; 56</code>.</p> <p>For the Prospero challenge, we can observe that for the resulting pixel values, the value of the result is not used at all, only its sign. Essentially, every program ends implicitly with a <code>sign</code> operation that returns <code>0.0</code> for negative values and <code>1.0</code> for positive values. For clarity, I will show this <code>sign</code> operation in the rest of the section, even if it's not actually in the real code.</p> <p>This makes it possible to simplify certain min/max operations further. Here is an example of a program, together with the intervals of the variables:</p> <div class="code"><pre class="code literal-block"><span class="n">x</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="c1"># [0.1, 1]</span> <span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">m</span><span class="w"> </span><span class="nb">min</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m</span> </pre></div> <p>This program can be optimized to:</p> <div class="code"><pre class="code literal-block"><span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m</span> </pre></div> <p>Because that expression has the same result as the original expression: if <code>x &gt; 0.1</code>, for the result of <code>min(x, y)</code> to be negative then <code>y</code> needs to be negative.</p> <p>Another, more complex, example is this:</p> <div class="code"><pre class="code literal-block"><span class="n">x</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="c1"># [1, 100]</span> <span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span><span class="w"> </span><span class="c1"># [-10, 10]</span> <span class="n">z</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">z</span><span class="w"> </span><span class="c1"># [-100, 100]</span> <span class="n">m1</span><span class="w"> </span><span class="nb">min</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="c1"># [-10, 10]</span> <span class="n">m2</span><span class="w"> </span><span class="nb">max</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="n">out</span><span class="w"> </span><span class="c1"># [-10, 100]</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m2</span> </pre></div> <p>Which can be optimized to this:</p> <div class="code"><pre class="code literal-block"><span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span> <span class="n">z</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">z</span> <span class="n">m2</span><span class="w"> </span><span class="nb">max</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="n">y</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m2</span> </pre></div> <p>This is because the sign of <code>min(x, y)</code> is the same as the sign of <code>y</code> if <code>x &gt; 0</code>, and the sign of <code>max(z, min(x, y))</code> is thus the same as the sign of <code>max(z, y)</code>.</p> <p>To implement this optimization, I do a backwards pass over the program after the peephole optimization forward pass. For every <code>min</code> call I encounter, where one of the arguments is positive, I can optimize the <code>min</code> call away and replace it with the other argument. For <code>max</code> calls I simplify their arguments recursively.</p> <p>The code looks roughly like this:</p> <div class="code"><pre class="code literal-block"><span class="k">def</span><span class="w"> </span><span class="nf">work_backwards</span><span class="p">(</span><span class="n">resultops</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">minvalues</span><span class="p">,</span> <span class="n">maxvalues</span><span class="p">):</span> <span class="k">def</span><span class="w"> </span><span class="nf">demand_sign_simplify</span><span class="p">(</span><span class="n">op</span><span class="p">):</span> <span class="n">func</span><span class="p">,</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">arg1</span> <span class="o">=</span> <span class="n">resultops</span><span class="o">.</span><span class="n">get_func_and_args</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">max</span><span class="p">:</span> <span class="n">narg0</span> <span class="o">=</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">if</span> <span class="n">narg0</span> <span class="o">!=</span> <span class="n">arg0</span><span class="p">:</span> <span class="n">resultops</span><span class="o">.</span><span class="n">setarg</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">narg0</span><span class="p">)</span> <span class="n">narg1</span> <span class="o">=</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg1</span><span class="p">)</span> <span class="k">if</span> <span class="n">narg1</span> <span class="o">!=</span> <span class="n">arg1</span><span class="p">:</span> <span class="n">resultops</span><span class="o">.</span><span class="n">setarg</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">narg1</span><span class="p">)</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">min</span><span class="p">:</span> <span class="k">if</span> <span class="n">minvalues</span><span class="p">[</span><span class="n">arg0</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mf">0.0</span><span class="p">:</span> <span class="k">return</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg1</span><span class="p">)</span> <span class="k">if</span> <span class="n">minvalues</span><span class="p">[</span><span class="n">arg1</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mf">0.0</span><span class="p">:</span> <span class="k">return</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="n">narg0</span> <span class="o">=</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg0</span><span class="p">)</span> <span class="k">if</span> <span class="n">narg0</span> <span class="o">!=</span> <span class="n">arg0</span><span class="p">:</span> <span class="n">resultops</span><span class="o">.</span><span class="n">setarg</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">narg0</span><span class="p">)</span> <span class="n">narg1</span> <span class="o">=</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">arg1</span><span class="p">)</span> <span class="k">if</span> <span class="n">narg1</span> <span class="o">!=</span> <span class="n">arg1</span><span class="p">:</span> <span class="n">resultops</span><span class="o">.</span><span class="n">setarg</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">narg1</span><span class="p">)</span> <span class="k">return</span> <span class="n">op</span> <span class="k">return</span> <span class="n">demand_sign_simplify</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> </pre></div> <p>In my experiment, this optimization lets me remove 25% of all operations in prospero, at the various levels of my octree. I'll briefly look at performance results further down.</p> <h3 id="further-ideas-about-the-demanded-sign-simplification">Further ideas about the demanded sign simplification</h3> <p>There is another idea how to short-circuit the evaluation of expressions that I tried briefly but didn't pursue to the end. Let's go back to the first example of the previous subsection, but with different intervals:</p> <div class="code"><pre class="code literal-block"><span class="n">x</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">x</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">y</span><span class="w"> </span><span class="k">var</span><span class="o">-</span><span class="n">y</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">m</span><span class="w"> </span><span class="nb">min</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="c1"># [-1, 1]</span> <span class="n">out</span><span class="w"> </span><span class="nb">sign</span><span class="w"> </span><span class="n">m</span> </pre></div> <p>Now we can't use the "demanded sign" trick in the optimizer, because neither <code>x</code> nor <code>y</code> are known positive. However, during <em>execution</em> of the program, if <code>x</code> turns out to be negative we can end the execution of this trace immediately, since we know that the result must be negative.</p> <p>So I experimented with adding <code>return_early_if_neg</code> flags to all operations with this property. The interpreter then checks whether the flag is set on an operation and if the result is negative, it stops the execution of the program early:</p> <div class="code"><pre class="code literal-block"><span class="n">x</span><span class="w"> </span><span class="nf">var</span><span class="o">-</span><span class="n">x</span><span class="o">[</span><span class="n">return_early_if_neg</span><span class="o">]</span> <span class="n">y</span><span class="w"> </span><span class="nf">var</span><span class="o">-</span><span class="n">y</span><span class="o">[</span><span class="n">return_early_if_neg</span><span class="o">]</span> <span class="n">m</span><span class="w"> </span><span class="nf">min</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span> <span class="k">out</span><span class="w"> </span><span class="nf">sign</span><span class="w"> </span><span class="n">m</span> </pre></div> <p>This looked pretty promising, but it's also a trade-off because the cost of checking the flag and the value isn't zero. Here's a sketch to the change in the interpreter:</p> <div class="code"><pre class="code literal-block"><span class="k">class</span><span class="w"> </span><span class="nc">DirectFrame</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span> <span class="o">...</span> <span class="k">def</span><span class="w"> </span><span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="n">program</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">program</span> <span class="n">num_ops</span> <span class="o">=</span> <span class="n">program</span><span class="o">.</span><span class="n">num_operations</span><span class="p">()</span> <span class="n">floatvalues</span> <span class="o">=</span> <span class="p">[</span><span class="mf">0.0</span><span class="p">]</span> <span class="o">*</span> <span class="n">num_ops</span> <span class="k">for</span> <span class="n">op</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_ops</span><span class="p">):</span> <span class="o">...</span> <span class="k">if</span> <span class="n">func</span> <span class="o">==</span> <span class="n">OPS</span><span class="o">.</span><span class="n">var_x</span><span class="p">:</span> <span class="n">res</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">...</span> <span class="k">else</span><span class="p">:</span> <span class="k">assert</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">program</span><span class="o">.</span><span class="n">get_flags</span><span class="p">(</span><span class="n">op</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">OPS</span><span class="o">.</span><span class="n">should_return_if_neg</span> <span class="ow">and</span> <span class="n">res</span> <span class="o">&lt;</span> <span class="mf">0.0</span><span class="p">:</span> <span class="k">return</span> <span class="n">res</span> <span class="n">floatvalues</span><span class="p">[</span><span class="n">op</span><span class="p">]</span> <span class="o">=</span> <span class="n">res</span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">floatvalues</span><span class="p">[</span><span class="n">num_ops</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> </pre></div> <p>I implemented this in the RPython version, but didn't end up porting it to C, because it interferes with SIMD.</p> <h3 id="dead-code-elimination">Dead code elimination</h3> <p>Matt performs dead code elimination in his implementation by doing a single backwards pass over the program. This is a very simple and effective optimization, and I implemented it in my implementation as well. The dead code elimination pass is very simple: It starts by marking the result operation as used. Then it goes backwards over the program. If the current operation is used, its arguments are marked as used as well. Afterwards, all the operations that are not marked as used are removed from the program. The PyPy JIT actually performs dead code elimination on traces in exactly the same way (and I don't think we ever explained how this works on the blog), so I thought it was worth mentioning.</p> <p>Matt also performs register allocation as part of the backwards pass, but I didn't implement it because I wasn't too interested in that aspect.</p> <h3 id="random-testing-of-the-optimizer">Random testing of the optimizer</h3> <p>To make sure I didn't break anything in the optimizer, I implemented a test that generates random input programs and checks that the output of the optimizer is equivalent to the input program. The test generates random operations, random intervals for the operations and a random input value within that interval. It then runs the optimizer on the input program and checks that the output program has the same result as the input program. This is again implemented with <code>hypothesis</code>. Hypothesis' test case minimization feature is super useful for finding optimizer bugs. It's just not fun to analyze a problem on a many-thousand-operation input file, but Hypothesis often generated reduced test cases that were only a few operations long.</p> <h3 id="visualizing-programs">Visualizing programs</h3> <p>It's actually surprisingly annoying to visualize <code>prospero.vm</code> well, because it's quite a bit too large to just feed it into Graphviz. I made the problem slightly easier by grouping several operations together, where only the first operation in a group is used as the argument for more than one operation further in the program. This made it slightly more manageable for Graphviz. But it still wasn't a big enough improvement to be able to visualize all of <code>prospero.vm</code> in its unoptimized form at the top of the octree.</p> <p>Here's a visualization of the optimized <code>prospero.vm</code> at one of the octree levels:</p> <p><img alt="graph visualization of a part of the input program" src="https://www.pypy.org/images/2025-image-prospero-dataflow.png" /></p> <p>The result is on top, every node points to its arguments. The <code>min</code> and <code>max</code> operations form a kind of "spine" of the expression tree, because they are unions and intersection in the constructive solid geometry sense.</p> <p>I also wrote a function to visualize the octree recursion itself, the output looks like this:</p> <p><img alt="graph visualization of the octree recursion, zoomed out" src="https://www.pypy.org/images/2025-image-octree-zoomed-out.png" /></p> <p><img alt="graph visualization of the octree recursion, zoomed in" src="https://www.pypy.org/images/2025-image-octree-zoomed-in.png" /></p> <p>Green nodes are where the interval analysis determined that the output must be entirely outside the shape. Yellow nodes are where the octree recursion bottomed out.</p> <h3 id="c-implementation">C implementation</h3> <p>To achieve even faster performance, I decided to rewrite the implementation in C. While RPython is great for prototyping, it can be challenging to control low-level aspects of the code. The rewrite in C allowed me to experiment with several techniques I had been curious about:</p> <ul> <li><a href="https://blog.reverberate.org/2021/04/21/musttail-efficient-interpreters.html"><code>musttail</code> optimization</a> for the interpreter.</li> <li>SIMD (Single Instruction, Multiple Data): Using Clang's <a href="https://clang.llvm.org/docs/LanguageExtensions.html#vectors-and-extended-vectors"><code>ext_vector_type</code></a>, I process eight pixels at once using AVX (or some other SIMD magic that I don't properly understand).</li> <li>Efficient struct packing: I packed the operations struct into just 8 bytes by limiting the maximum number of operations to 65,536, with the idea of making the optimizer faster.</li> </ul> <p>I didn't rigorously study the performance impact of each of these techniques individually, so it's possible that some of them might not have contributed significantly. However, the rewrite was a fun exercise for me to explore these techniques. The code can be found <a href="https://github.com/cfbolz/pyfidget/blob/main/pyfidget/experiments.c">here</a>.</p> <h3 id="testing-the-c-implementation">Testing the C implementation</h3> <p>At various points I had bugs in the C implementation, leading to a fun glitchy version of prospero:</p> <p><img alt="glitchy prospero" src="https://www.pypy.org/images/2025-glitchy-prospero.png" /></p> <p>To find these bugs, I used the same random testing approach as in the RPython version. I generated random input programs as strings in Python and checked that the output of the C implementation was equivalent to the output of the RPython implementation (simply by calling out to the shell and reading the generated image, then comparing pixels). This helped ensure that the C implementation was correct and didn't introduce any bugs. It was surprisingly tricky to get this right, for reasons that I didn't expect. At lot of them are related to the fact that in C I used <code>float</code> and Python uses <code>double</code> for its (Python) <code>float</code> type. This made the random tester find weird floating point corner cases where rounding behaviour between the widths was different.</p> <p>I solved those by using <code>double</code> in C when running the random tests by means of an <code>IFDEF</code>.</p> <p>It's super fun to watch the random program generator produce random images, here are a few:</p> <h3 id="performance">Performance</h3> <p>Some very rough performance results on my laptop (an AMD Ryzen 7 PRO 7840U with 32 GiB RAM running Ubuntu 24.04), comparing the RPython version, the C version (with and without demanded info), and Fidget (in <code>vm</code> mode, its JIT made things worse for me), both for 1024x1024 and 4096x4096 images:</p> <table> <thead> <tr> <th>Implementation</th> <th>1024x1024</th> <th>4096x4096</th> </tr> </thead> <tbody> <tr> <td>RPython</td> <td>26.8ms</td> <td>75.0ms</td> </tr> <tr> <td>C (no demanded info)</td> <td>24.5ms</td> <td>45.0ms</td> </tr> <tr> <td>C (demanded info)</td> <td>18.0ms</td> <td>37.0ms</td> </tr> <tr> <td>Fidget</td> <td>10.8ms</td> <td>57.8ms</td> </tr> </tbody> </table> <p>The demanded info seem to help quite a bit, which was nice to see.</p> <h3 id="conclusion">Conclusion</h3> <p>That's it! I had lots of fun with the challenge and have a whole bunch of other ideas I want to try out, thanks Matt for this interesting puzzle.</p></p> <p> <em><a href="https://www.pypy.org/posts/2025/04/prospero-in-rpython.html">April 09, 2025 03:07 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://realpython.com/" title="Real Python">Real Python</a></h3> <h4><a href="https://realpython.com/python-dict-attribute/">Using Python's .__dict__ to Work With Attributes</a></h4> <p> <div><p>Python’s <code>.__dict__</code> is a special attribute in classes and instances that acts as a namespace, mapping attribute names to their corresponding values. You can use <code>.__dict__</code> to inspect, modify, add, or delete attributes dynamically, which makes it a versatile tool for metaprogramming and debugging.</p> <p>In this tutorial, you’ll learn about using <code>.__dict__</code> in various contexts, including classes, instances, and functions. You’ll also explore its role in inheritance with practical examples and comparisons to other tools for manipulating attributes.</p> <p><strong>By the end of this tutorial, you’ll understand that:</strong></p> <ul> <li><strong><code>.__dict__</code></strong> holds an object’s <strong>writable attributes</strong>, allowing for dynamic manipulation and introspection.</li> <li>Both <strong><code>vars()</code></strong> and <strong><code>.__dict__</code></strong> let you <strong>inspect</strong> an object’s attributes. The <code>.__dict__</code> attribute gives you direct access to the object’s namespace, while the <code>vars()</code> function returns the object’s <code>.__dict__</code>.</li> <li>Common use cases of <code>.__dict__</code> include <strong>dynamic attribute management</strong>, <strong>introspection</strong>, <strong>serialization</strong>, and <strong>debugging</strong> in Python applications.</li> </ul> <p>While this tutorial provides detailed insights into using <code>.__dict__</code> effectively, having a solid understanding of Python <a href="https://realpython.com/python-dicts/">dictionaries</a> and how to use them in your code will help you get the most out of it.</p> <div class="alert alert-warning"> <p><strong>Get Your Code:</strong> <a href="https://realpython.com/bonus/python-dict-attribute-code/" class="alert-link">Click here to download the free sample code </a> you’ll use to learn about using Python’s .<strong>dict</strong> to work with attributes.</p> </div> <div class="container border rounded text-wrap-pretty my-3"> <p class="my-3"><strong><span class="icon baseline"></span> Take the Quiz:</strong> Test your knowledge with our interactive “Using Python's .__dict__ to Work With Attributes” quiz. You’ll receive a score upon completion to help you track your learning progress:</p> <hr /> <div class="row my-3"> <div class="col-xs-12 col-sm-4 col-md-3 align-self-center"> <a href="https://realpython.com/quizzes/python-dict-attribute/" tabindex="-1"> <div class="embed-responsive embed-responsive-16by9"> <img class="card-img-top m-0 p-0 embed-responsive-item rounded" alt="Using Python's .__dict__ to Work With Attributes" src="https://files.realpython.com/media/Using-Pythons-.__dict__-to-Inspect-Object-Attributes_Watermarked.e648f20c0563.jpg" width="1920" height="1080" /> <div class="card-img-overlay d-flex align-items-center"> <div class="mx-auto"> <span class="text-light"><span class="icon baseline scale2x"></span></span> </div> </div> </div> </a> </div> <div class="col"> <div class="mt-3 d-md-none"></div> <p class="small text-muted mb-0"><strong>Interactive Quiz</strong></p> <a href="https://realpython.com/quizzes/python-dict-attribute/" class="stretched-link"><span class="my-0 h4">Using Python's .__dict__ to Work With Attributes</span></a> <p class="text-muted mb-0 small">In this quiz, you'll test your understanding of Python's .__dict__ attribute and its usage in classes, instances, and functions. Acting as a namespace, this attribute maps attribute names to their corresponding values and serves as a versatile tool for metaprogramming and debugging.</p> </div> </div> </div> <h2 id="getting-to-know-the-__dict__-attribute-in-python">Getting to Know the <code>.__dict__</code> Attribute in Python<a class="headerlink" href="https://realpython.com/atom.xml#getting-to-know-the-__dict__-attribute-in-python" title="Permanent link"></a></h2> <p>Python supports the <a href="https://realpython.com/python3-object-oriented-programming/">object-oriented programming (OOP)</a> paradigm through <a href="https://realpython.com/python-classes/">classes</a> that <span>encapsulate</span> data (attributes) and behaviors (methods) in a single entity. Under the hood, Python takes advantage of dictionaries to handle these attributes and methods.</p> <p>Why dictionaries? Because they’re implemented as hash tables, which map keys to values, making lookup operations fast and efficient.</p> <div class="alert alert-primary"> <p><strong>Note:</strong> To learn more about using Python dictionaries, check out the following resources:</p> <ul> <li><a href="https://realpython.com/python-dicts/">Dictionaries in Python</a></li> <li><a href="https://realpython.com/iterate-through-dictionary-python/">How to Iterate Through a Dictionary in Python</a></li> <li><a href="https://realpython.com/sort-python-dictionary/">Sorting a Python Dictionary: Values, Keys, and More</a></li> <li><a href="https://realpython.com/python-dictionary-comprehension/">Python Dictionary Comprehensions: How and When to Use Them</a></li> </ul> </div> <p>Generally, Python uses a special dictionary called <a href="https://docs.python.org/3/reference/datamodel.html#object.__dict__"><code>.__dict__</code></a> to maintain references to writable attributes and methods in a Python class or instance. In practice, the <code>.__dict__</code> attribute is a <a href="https://realpython.com/python-namespaces-scope/">namespace</a> that maps attribute names to values and method names to method objects.</p> <p>The <code>.__dict__</code> attribute is fundamental to Python’s <a href="https://docs.python.org/3/reference/datamodel.html">data model</a>. The interpreter recognizes and uses it internally to process classes and objects. It enables dynamic attribute access, addition, removal, and manipulation. You’ll learn how to do these operations in a moment. But first, you’ll look at the differences between the class <code>.__dict__</code> and the instance <code>.__dict__</code>.</p> <h3 id="the-__dict__-class-attribute">The <code>.__dict__</code> Class Attribute<a class="headerlink" href="https://realpython.com/atom.xml#the-__dict__-class-attribute" title="Permanent link"></a></h3> <p>To start learning about <code>.__dict__</code> in a Python class, you’ll use the following demo class, which has attributes and methods:</p> <div class="codeblock mb-3 w-100"> <div class="codeblock__header d-flex justify-content-between codeblock--blue"> <span class="mr-2 noselect">Python</span> <span class="mr-2"><code>demo.py</code></span> <div class="noselect"> </div> </div> <div> <div class="highlight highlight--with-header"><pre><span></span><code><span class="k">class</span><span class="w"> </span><span class="nc">DemoClass</span><span class="p">:</span> <span class="n">class_attr</span> <span class="o">=</span> <span class="s2">"This is a class attribute"</span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="bp">self</span><span class="o">.</span><span class="n">instance_attr</span> <span class="o">=</span> <span class="s2">"This is an instance attribute"</span> <span class="k">def</span><span class="w"> </span><span class="nf">method</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span> <span class="k">return</span> <span class="s2">"This is a method"</span> </code></pre></div> <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline"></span></button> <span class="small"><span class="icon baseline mr-1 text-success"></span>Copied!</span> </div> </div> <p>In this class, you have a class attribute, two methods, and an <a href="https://realpython.com/python-classes/#instance-attributes">instance attribute</a>. Now, start a Python <a href="https://realpython.com/python-repl/">REPL</a> session and run the following code:</p> <div class="codeblock mb-3 w-100"> <div class="codeblock__header d-flex justify-content-between codeblock--blue"> <span class="mr-2 noselect">Python</span> <div class="noselect"> <span class="codeblock__output-toggle" title="Toggle prompts and output"><span class="icon baseline js-codeblock-output-on codeblock__header--icon-lower"></span></span> </div> </div> <div> <div class="highlight highlight--with-header"><pre><span></span><code><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span><span class="w"> </span><span class="nn">demo</span><span class="w"> </span><span class="kn">import</span> <span class="n">DemoClass</span> <span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">DemoClass</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span> <span class="go">{</span> <span class="go"> '__module__': 'demo',</span> <span class="go"> '__firstlineno__': 1,</span> <span class="go"> 'class_attr': 'This is a class attribute',</span> <span class="go"> '__init__': &lt;function DemoClass.__init__ at 0x102bcd120&gt;,</span> <span class="go"> 'method': &lt;function DemoClass.method at 0x102bcd260&gt;,</span> <span class="go"> '__static_attributes__': ('instance_attr',),</span> <span class="go"> '__dict__': &lt;attribute '__dict__' of 'DemoClass' objects&gt;,</span> <span class="go"> '__weakref__': &lt;attribute '__weakref__' of 'DemoClass' objects&gt;,</span> <span class="go"> '__doc__': None</span> <span class="go">}</span> </code></pre></div> <button class="codeblock__copy btn btn-outline-secondary border m-1 px-1 d-hover-only" title="Copy to clipboard"><span class="icon baseline"></span></button> <span class="small"><span class="icon baseline mr-1 text-success"></span>Copied!</span> </div> </div> <p>The call to <code>print()</code> displays a dictionary that maps names to objects. First, you have the <code>'__module__'</code> key, which maps to a special attribute that specifies where the class is defined. In this case, the class lives in the <code>demo</code> module. Then, you have the <code>'__firstlineno__'</code> key, which holds the line number of the first line of the class definition, including decorators. Next, you have the <code>'class_attr'</code> key and its corresponding value.</p> <div class="alert alert-primary"> <p><strong>Note:</strong> When you access the <code>.__dict__</code> attribute on a class, you get a <code>mappingproxy</code> object. This type of object creates a read-only <a href="https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects">view</a> of a dictionary.</p> </div> <p>The <code>'__init__'</code> and <code>'method'</code> keys map to the corresponding method objects <a href="https://realpython.com/python-class-constructor/#object-initialization-with-__init__"><code>.__init__()</code></a> and <code>.method()</code>. Next, you have a key called <code>'__dict__'</code> that maps to the attribute <code>.__dict__</code> of <code>DemoClass</code> objects. You’ll explore this attribute more in a moment.</p> <p>The <code>'__static_attributes__'</code> key is a tuple containing the names of the attributes that you assign through <code>self.attribute = value</code> from any method in the class body.</p> <p>The <code>'__weakref__'</code> key represents a special attribute that enables you to reference objects without preventing them from being <a href="https://realpython.com/ref/glossary/garbage-collection/" class="ref-link">garbage collected</a>.</p> <p>Finally, you have the <code>'__doc__'</code> key, which maps to the class’s docstring. If the class doesn’t have a docstring, it defaults to <a href="https://realpython.com/null-in-python/"><code>None</code></a>.</p> <p>Did you notice that the <code>.instance_attr</code> name doesn’t have a key in the class <code>.__dict__</code> attribute? You’ll find out where it’s hidden in the following section.</p> </div><h2><a href="https://realpython.com/python-dict-attribute/?utm_source=realpython&utm_medium=rss">Read the full article at https://realpython.com/python-dict-attribute/ »</a></h2> <hr /> <p><em>[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp; sweet Python Trick delivered to your inbox every couple of days. <a href="https://realpython.com/python-tricks/?utm_source=realpython&utm_medium=rss&utm_campaign=footer">&gt;&gt; Click here to learn more and see examples</a> ]</em></p></p> <p> <em><a href="https://realpython.com/python-dict-attribute/">April 09, 2025 02:00 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://www.blog.pythonlibrary.org/" title="Mouse Vs Python">Mike Driscoll</a></h3> <h4><a href="https://www.blog.pythonlibrary.org/2025/04/09/python-101-an-intro-to-working-with-ini-files-using-configparser/">Python 101 – An Intro to Working with INI files Using configparser</a></h4> <p> <p>Many programs require configuration. Most have a default configuration and many allow the user to adjust that configuration. There are many different types of configuration files. Some use text files while others use databases. Python has a standard library called <code>configparser</code> that you can use to work with Microsoft Windows INI files.</p> <p>In this tutorial, you will cover the following topics:</p> <ul> <li>An example INI file</li> <li>Creating a config file</li> <li>Editing a config file</li> <li>Reading a config file</li> </ul> <p>By the end of this tutorial, you will be able to use INI configuration files programmatically with Python.</p> <p>Let&#8217;s get started!</p> <h2>Example INI File</h2> <p>There are many examples of INI files on the Internet. You can find one over in the <a href="https://mypy.readthedocs.io/en/stable/config_file.html">Mypy documentation</a>. Mypy is a popular type checker for Python. Here is the <code>mypy.ini</code> file that they use as an example:</p> <pre class="EnlighterJSRAW"># Global options: [mypy] warn_return_any = True warn_unused_configs = True # per-module options: [mypy-mycode.foo.*] disallow_untyped_defs = True [ypy-mycode.bar] warn_return_any = False [mypy-somelibrary] ignore_missing_imports = True</pre> <p>Sections are denoted by being placed inside square braces. Then, each section can have zero or more settings. In the next section, you will learn how to create this configuration file programmatically with Python.</p> <h2>Creating a Config File</h2> <p>The <a href="https://docs.python.org/3/library/configparser.html">documentation</a> for Python&#8217;s <code>configparser</code>module is helpful. They tell you how to recreate an example INI file right in the documentation. Of course, their example is not the Mypy example above. Your job is a little bit harder as you need to be able to insert comments into your configuration, which isn&#8217;t covered in the documentation. Don&#8217;t worry. You&#8217;ll learn how to do that now!</p> <p>Open up your Python editor and create a new file called <code>create_config.py</code>. Then enter the following code:</p> <pre class="EnlighterJSRAW"># create_config.py import configparser config = configparser.ConfigParser(allow_no_value=True) config["mypy"] = {"warn_return_any": "True", "warn_unused_configs": "True",} config.set("mypy", "\n# Per-module options:") config["mypy-mycode.foo.*"] = {"disallow_untyped_defs": "True"} config["ypy-mycode.bar"] = {"warn_return_any": "False"} config["mypy-somelibrary"] = {"ignore_missing_imports": "True"} with open("custom_mypy.ini", "w") as config_file: config_file.write("# Global options:\n\n") config.write(config_file) </pre> <p>The documentation states that <span>the<strong> allow_no_value</strong></span> parameter allows for including sections that do not have values. You need to add this to be able to add comments in the middle of a section to be added as well. Otherwise, you will get a <strong>TypeError</strong>.</p> <p>To add entire sections, you use a dictionary-like interface. Each section is denoted by the key, and that section&#8217;s values are added by setting that key to another dictionary.</p> <p>Once you finish creating each section and its contents, you can write the configuration file to disk. You open a file for writing, then write the first comment. Next, you use the <code>config.write()</code> method to write the rest of the file.</p> <p>Try running the code above; you should get the same INI file as the one at the beginning of this article.</p> <h2>Editing a Config File</h2> <p>The <code>configparser</code>library makes editing your configuration files mostly painless. You will learn how to change a setting in the config file and add a new section to your pre-existing configuration.</p> <p>Create a new file named <code>edit_config.py</code> and add the following code to it:</p> <pre class="EnlighterJSRAW"># edit_config.py import configparser config = configparser.ConfigParser() config.read("custom_mypy.ini") # Change an item's value config.set("mypy-somelibrary", "ignore_missing_imports", "False") # Add a new section config["new-random-section"] = {"compressed": "True"} with open("modified_mypy.ini", "w") as config_file: config.write(config_file) </pre> <p>In this case, after create the <code>ConfigParser()</code>instance, you call <code>read()</code>to read the specified configuration file. Then you can set any value you want.</p> <p>Unfortunately, you cannot use dictionary-like syntax to set values. Instead, you must use <code>set()</code>which takes the following parameters:</p> <ul> <li><strong>section</strong> &#8211; The name of the section.</li> <li><strong>option</strong> &#8211; The option you wish to change.</li> <li><strong>value</strong> &#8211; The new value you want to set.</li> </ul> <p>Adding a new section works like it did when you created the initial sections in the last code example. You still use dictionary-like syntax where the new section is the key and the value is a dictionary of one or more settings to go in your section.</p> <p>When you run this code, it will create an INI file with the following contents:</p> <pre class="EnlighterJSRAW">[mypy] warn_return_any = True warn_unused_configs = True [mypy-mycode.foo.*] disallow_untyped_defs = True [ypy-mycode.bar] warn_return_any = False [mypy-somelibrary] ignore_missing_imports = False [new-random-section] compressed = True </pre> <p>Good job! You&#8217;ve just learned how to modify an INI file with Python!</p> <p>Now you are ready to learn about reading INI files.</p> <h2>Reading a Config File</h2> <p>You already caught a glimpse of how to read a configuration file in the previous section. The primary method is by calling the <code>ConfigParser</code>&#8216;s <code>read()</code>method.</p> <p>Here&#8217;s an example using the new INI file you just created:</p> <pre class="EnlighterJSRAW">&gt;&gt;&gt; import configparser &gt;&gt;&gt; config = configparser.ConfigParser() &gt;&gt;&gt; config.read(r"C:\code\modified_mypy.ini") ['C:\\code\\modified_mypy.ini'] &gt;&gt;&gt; config["mypy"] &lt;Section: mypy&gt; &gt;&gt;&gt; config["mypy"]["warn_return_any"] 'True' &gt;&gt;&gt; config["unknown"] Traceback (most recent call last): Python Shell, prompt 8, line 1 config["unknown"] File "c:\users\Mike\appdata\local\programs\python\python312\lib\configparser.py", line 941, in __getitem__ raise KeyError(key) builtins.KeyError: 'unknown'</pre> <p>You can access individual values using dictionary syntax. If you happen to try to access a section or an option that does not exist, you will receive a <code>KeyError</code>.</p> <p>The <code>configparser</code> has a second reading method called <code>read_string()</code> that you can use as well. Here is an example:</p> <pre class="EnlighterJSRAW">&gt;&gt;&gt; sample_config = """ ... [mypy] ... warn_return_any = True ... warn_unused_configs = True ... ... # Per-module options: ... ... [mypy-mycode.foo.*] ... disallow_untyped_defs = True ... """ &gt;&gt;&gt; config = configparser.ConfigParser(allow_no_value=True) &gt;&gt;&gt; config.read_string(sample_config) &gt;&gt;&gt; config["mypy"]["warn_return_any"] 'True'</pre> <p>You use <code>read_string()</code> to read in a multiline string and then access values inside of it. Pretty neat, eh?</p> <p>You can also grab the section and them use list comprehensions to extract the options from each section:</p> <pre class="EnlighterJSRAW">&gt;&gt;&gt; config.sections() ['mypy', 'mypy-mycode.foo.*'] &gt;&gt;&gt; [option for option in config["mypy"]] ['warn_return_any', 'warn_unused_configs']</pre> <p>The code above is a handy example for getting at the configuration options quickly and easily.</p> <h2>Wrapping Up</h2> <p>Having a way to configure your application makes it more useful and allows the user more control over how their copy of the application works. In this article, you learned how about the following topics:</p> <ul> <li>An example INI file</li> <li>Creating a config file</li> <li>Editing a config file</li> <li>Reading a config file</li> </ul> <p>The <a href="https://docs.python.org/3/library/configparser.html">configparser library</a> has more features than what is covered here. For example, you can use interpolation to preprocess values or customize the parser process. Check out the documentation for full details on those and other features.</p> <p>In the meantime, have fun and enjoy this neat feature of Python!</p> <h2>Related Articles</h2> <p>You might also be interested in these related articles:</p> <ul> <li><a href="https://www.blog.pythonlibrary.org/2013/10/25/python-101-an-intro-to-configparser/">The Python 2 Intro to ConfigParser</a></li> <li><a href="https://www.blog.pythonlibrary.org/2020/09/15/python-101-an-intro-to-working-with-json/" rel="bookmark">Python 101: An Intro to Working with JSON</a></li> <li> <p class="entry-title ast-blog-single-element"><a href="https://www.blog.pythonlibrary.org/2021/09/30/sqlite/" rel="bookmark">Python 101 – How to Work with a Database Using sqlite3</a></p> </li> </ul> <p>The post <a href="https://www.blog.pythonlibrary.org/2025/04/09/python-101-an-intro-to-working-with-ini-files-using-configparser/">Python 101 &#8211; An Intro to Working with INI files Using configparser</a> appeared first on <a href="https://www.blog.pythonlibrary.org">Mouse Vs Python</a>.</p></p> <p> <em><a href="https://www.blog.pythonlibrary.org/2025/04/09/python-101-an-intro-to-working-with-ini-files-using-configparser/">April 09, 2025 12:30 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://www.djangoproject.com/weblog/" title="The Django weblog">Django Weblog</a></h3> <h4><a href="https://www.djangoproject.com/weblog/2025/apr/09/annual-meeting-of-dsf-members-at-djangocon-europe/">Annual meeting of DSF Members at DjangoCon Europe</a></h4> <p> <p>We’re organizing an annual meeting for members of the Django Software Foundation! It will be held at <a href="https://2025.djangocon.eu/">DjangoCon Europe 2025</a> in two weeks in Dublin, bright and early <a href="https://pretalx.evolutio.pt/djangocon-europe-2025/talk/GJDTBU/">on the second day of the conference</a>. The meeting will be held in person at the venue, and participants can also join remotely.</p> <p><a href="https://docs.google.com/forms/d/e/1FAIpQLSfnq_xvdNvhwX3CXtjAcGkRJ-hEETY1NauAcsjt3KqrD0fTog/viewform?usp=dialog" class="cta">Register to join the annual meeting</a></p> <h4 id="s-what-to-expect">What to expect</h4> <p>This is an opportunity for current and aspiring members of the Foundation to directly contribute to discussions about our direction. We will cover our current and future projects, and look for feedback and possible contributions within our community.</p> <hr /> <p>If this sounds interesting to you but you’re not currently an Individual Member, do <a href="https://www.djangoproject.com/foundation/individual-members/">review our membership criteria and apply</a>!</p> <p> </p></p> <p> <em><a href="https://www.djangoproject.com/weblog/2025/apr/09/annual-meeting-of-dsf-members-at-djangocon-europe/">April 09, 2025 06:22 AM UTC</a></em> </p> <h2>April 08, 2025</h2> <hr /><h3 class="post"><a href="https://python.github.io/editorial-board/" title="Python Docs Editorial Board">Python Docs Editorial Board</a></h3> <h4><a href="https://python.github.io/editorial-board/updates/2025-04-08-editorial-board-update/">Meeting Minutes: Apr 8, 2025</a></h4> <p> Meeting Minutes from Python Docs Editorial Board: Apr 8, 2025</p> <p> <em><a href="https://python.github.io/editorial-board/updates/2025-04-08-editorial-board-update/">April 08, 2025 09:49 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://pycoders.com/" title="PyCoder’s Weekly">PyCoder’s Weekly</a></h3> <h4><a href="https://pycoders.com/issues/676">Issue #676: Bytearray, Underground Scripts, DjangoCon, and More (April 8, 2025)</a></h4> <p> <p> <span>#676 – APRIL 8, 2025</span><br /> <span><a href="https://pycoders.com/issues/676/feed">View in Browser »</a></span> </p> <p><a href="https://pycoders.com"><img alt="The PyCoder&rsquo;s Weekly Logo" src="https://cdn.pycoders.com/37bdf31dc645f968ffb90196e5d38ff5" /></a></p> <hr /> <div> <h3><a href="https://pycoders.com/link/14309/feed" target="_blank">Python&rsquo;s Bytearray: A Mutable Sequence of Bytes</a></h3> <p> In this tutorial, you&rsquo;ll learn about Python&rsquo;s <code>bytearray</code>, a mutable sequence of bytes for efficient binary data manipulation. You&rsquo;ll explore how it differs from bytes, how to create and modify <code>bytearray</code> objects, and when to use them in tasks like processing binary files and network protocols.<br /> <span><a href="https://pycoders.com/link/14309/feed" target="_blank">REAL PYTHON</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14321/feed" target="_blank">Quiz: Python&rsquo;s Bytearray</a></h3> <p> <span><a href="https://pycoders.com/link/14321/feed" target="_blank">REAL PYTHON</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14295/feed" target="_blank">10 Insane Underground Python Scripts</a></h3> <p> Imagine if your Python script could cover its tracks after execution or silently capture the screen. This post has 10 short scripts that do tricky things.<br /> <span><a href="https://pycoders.com/link/14295/feed" target="_blank">DEV.TONAPPY TUTS</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14294/feed" target="_blank">A Dev’s Guide to Surviving Python’s Error zoo 🐍</a></h3> <a href="https://pycoders.com/link/14294/feed" target="_blank"><img src="https://cdn.pycoders.com/5812db5e409fec4b447735b62ffd4b1c" alt="alt" /></a> <p> Exceptions happen—but they don’t have to wreck your app (or your day). This Sentry guide breaks down common Python errors, how to handle them cleanly, and how to monitor your app in production—without digging through logs or <a href="https://pycoders.com/link/14294/feed" target="_blank">duct-taping try/excepts everywhere →</a><br /> <span><a href="https://pycoders.com/link/14294/feed" target="_blank">SENTRY</a></span> <span>sponsor</span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14324/feed" target="_blank">Talks I Want to See at DjangoCon US 2025</a></h3> <p> Looking for a talk idea for DjangoCon US? Tim&rsquo;s post discusses things he&rsquo;d like to see at the conference.<br /> <span><a href="https://pycoders.com/link/14324/feed" target="_blank">TIM SCHILLING</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14297/feed" target="_blank">Django Security Releases: 5.1.8 and 5.0.14</a></h3> <p> <span><a href="https://pycoders.com/link/14297/feed" target="_blank">DJANGO SOFTWARE FOUNDATION</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14303/feed" target="_blank">Django 5.2 Released</a></h3> <p> <span><a href="https://pycoders.com/link/14303/feed" target="_blank">DJANGO SOFTWARE FOUNDATION</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14301/feed" target="_blank">Quiz: How to Strip Characters From a Python String</a></h3> <p> <span><a href="https://pycoders.com/link/14301/feed" target="_blank">REAL PYTHON</a></span> </p> </div> <h2>Articles &amp; Tutorials</h2> <div> <h3><a href="https://pycoders.com/link/14304/feed" target="_blank">REST in Peace? Django&rsquo;s Framework Problem</a></h3> <p> The Django Rest Framework (DRF) has recently locked down access to its issues and discussion boards due to being overwhelmed. What does this mean for larger open source projects that become the victims of their own success? The article&rsquo;s good points notwithstanding, the <a href="https://pycoders.com/link/14313/feed" target="_blank">DRF is still doing releases</a>.<br /> <span><a href="https://pycoders.com/link/14304/feed" target="_blank">DANLAMANNA.COM</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14320/feed" target="_blank">Developing and Testing Python Packages With uv</a></h3> <p> Structuring Python projects can be confusing. Where do tests go? Should you use a <code>src</code> folder? How do you import and test your code cleanly? In this post, Michael shares how he typically structures Python packages using uv, clarifying common setup and import pitfalls.<br /> <span><a href="https://pycoders.com/link/14320/feed" target="_blank">PYBITES</a> • Shared by <a href="https://pycoders.com/link/14323/feed" target="_blank">Bob Belderbos</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14325/feed" target="_blank">Building a Code Image Generator With Python</a></h3> <p> In this step-by-step video course, you&rsquo;ll build a code image generator that creates nice-looking images of your code snippets to share on social media. Your code image generator will be powered by the Flask web framework and include exciting packages like Pygments and Playwright.<br /> <span><a href="https://pycoders.com/link/14325/feed" target="_blank">REAL PYTHON</a></span> <span>course</span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14322/feed" target="_blank">Algorithms for High Performance Terminal Apps</a></h3> <p> This post by one of the creators of Textual talks about how to write high performing terminal applications. You may also be interested in the <a href="https://pycoders.com/link/14302/feed" target="_blank">Talk Python interview on the same topic</a>.<br /> <span><a href="https://pycoders.com/link/14322/feed" target="_blank">WILL MCGUGAN</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14317/feed" target="_blank">Migrate Django ID Field From <code>int</code> to <code>big int</code></a></h3> <p> If you&rsquo;re responsible for a project based on an older version of Django, you may be using <code>int</code> based primary keys. This post talks about how to transition to a 4-byte integer, used in more recent versions of Django, with minimal down time.<br /> <span><a href="https://pycoders.com/link/14317/feed" target="_blank">CHARLES OLIVEIRA</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14319/feed" target="_blank">Shadowing in Python Gave an <code>UnboundLocalError</code></a></h3> <p> Reusing a variable name to shadow earlier definitions normally isn&rsquo;t a problem, but due to how Python scopes, it occasionally gives you an exception. This post shows you just such a case and why it happened.<br /> <span><a href="https://pycoders.com/link/14319/feed" target="_blank">NICOLE TIETZ-SOKOLSKAYA</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14307/feed" target="_blank">If I Were Starting Out Now…</a></h3> <p> Carlton Gibson gives advice and what he&rsquo;d do if he was starting his development career now. It even starts with the caveat about why you maybe shouldn&rsquo;t listen to him?<br /> <span><a href="https://pycoders.com/link/14307/feed" target="_blank">CARLTON GIBSON</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14298/feed" target="_blank">Terrible Horrible No Good Very Bad Python</a></h3> <p> This quick post shows some questionable code and asks you to predict what it does. Don&rsquo;t forget to click the paragraphs at the bottom if you want to see the answers.<br /> <span><a href="https://pycoders.com/link/14298/feed" target="_blank">JYNN NELSON</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14318/feed" target="_blank">How to Report a Security Issue in an Open Source Project</a></h3> <p> So you’ve found a security issue in an open source project – or maybe just a weird problem that you think might be a security problem. What should you do next?<br /> <span><a href="https://pycoders.com/link/14318/feed" target="_blank">JACOB KAPLAN-MOSS</a></span> </p> </div> <h2>Projects &amp; Code</h2> <div> <h3><a href="https://pycoders.com/link/14310/feed" target="_blank">fastmcp: Build Model Context Protocol Servers</a></h3> <p> <span><a href="https://pycoders.com/link/14310/feed" target="_blank">GITHUB.COM/JLOWIN</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14299/feed" target="_blank">coredumpy: Saves Your Crash Site for Post-Mortem Debugging</a></h3> <p> <span><a href="https://pycoders.com/link/14299/feed" target="_blank">GITHUB.COM/GAOGAOTIANTIAN</a> • Shared by Tian Gao</span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14311/feed" target="_blank">System-Wide Package Discovery, Validation, and Allow-Listing</a></h3> <p> <span><a href="https://pycoders.com/link/14311/feed" target="_blank">GITHUB.COM/FETTER-IO</a> • Shared by <a href="https://pycoders.com/link/14296/feed" target="_blank">Christopher Ariza</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14314/feed" target="_blank">django-typer: Use Typer for Django Management Commands</a></h3> <p> <span><a href="https://pycoders.com/link/14314/feed" target="_blank">GITHUB.COM/DJANGO-COMMONS</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14306/feed" target="_blank">Wikipedia-API: Python Wrapper for Wikipedia</a></h3> <p> <span><a href="https://pycoders.com/link/14306/feed" target="_blank">GITHUB.COM/MARTIN-MAJLIS</a></span> </p> </div> <h2>Events</h2> <div> <h3><a href="https://pycoders.com/link/14305/feed" target="_blank">Weekly Real Python Office Hours Q&amp;A (Virtual)</a></h3> <p> April 9, 2025<br /> <span><a href="https://pycoders.com/link/14305/feed" target="_blank">REALPYTHON.COM</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14315/feed" target="_blank">Python Atlanta</a></h3> <p> April 10 to April 11, 2025<br /> <span><a href="https://pycoders.com/link/14315/feed" target="_blank">MEETUP.COM</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14312/feed" target="_blank">PyTexas 2025</a></h3> <p> April 11 to April 14, 2025<br /> <span><a href="https://pycoders.com/link/14312/feed" target="_blank">PYTEXAS.ORG</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14316/feed" target="_blank">SpaceCon 2025</a></h3> <p> April 11 to April 12, 2025<br /> <span><a href="https://pycoders.com/link/14316/feed" target="_blank">ANTARIKCHYA.ORG.NP</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14300/feed" target="_blank">DFW Pythoneers 2nd Saturday Teaching Meeting</a></h3> <p> April 12, 2025<br /> <span><a href="https://pycoders.com/link/14300/feed" target="_blank">MEETUP.COM</a></span> </p> </div> <div> <h3><a href="https://pycoders.com/link/14326/feed" target="_blank">Workshop: Creating Python Communities</a></h3> <p> April 15 to April 16, 2025<br /> <span><a href="https://pycoders.com/link/14326/feed" target="_blank">PYTHON-GM.ORG</a></span> </p> </div> <hr /> <p>Happy Pythoning!<br />This was PyCoder&rsquo;s Weekly Issue #676.<br /><a href="https://pycoders.com/issues/676/feed">View in Browser »</a></p> <img src="https://pycoders.com/issues/676/open/feed" width="1" height="1" alt="alt" /> <hr /> <p><em>[ Subscribe to 🐍 PyCoder&rsquo;s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week <a href="https://pycoders.com/?utm_source=pycoders&utm_medium=feed&utm_campaign=footer">&gt;&gt; Click here to learn more</a> ]</em></p></p> <p> <em><a href="https://pycoders.com/issues/676">April 08, 2025 07:30 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://https://everydaysuperpowers.dev/articles/?tag=" title="Recent articles from Everyday Superpowers">Everyday Superpowers</a></h3> <h4><a href="https://everydaysuperpowers.dev/articles/what-is-event-sourcing-and-why-you-should-care/">What is event sourcing and why you should care</a></h4> <p> <div class="block-intro"><p>This is the second entry in a five-part series about event sourcing:</p><ol><li><a href="https://everydaysuperpowers.dev/articles/why-i-finally-embraced-event-sourcingand-why-you-should-too/">Why I Finally Embraced Event Sourcing—And Why You Should Too</a></li><li>What is event sourcing and why you should care</li><li>Preventing painful coupling</li><li>Event-driven microservice in a monolith</li><li>Get started with event sourcing today</li></ol></div> <div class="block-paragraph"><p></p><p>In my last blog post, I introduced the concept of event sourcing and some of its benefits. In this post, I’ll discuss the pattern in more depth.</p><p></p><p></p></div> <div class="block-h2">What is event sourcing?</div> <div class="block-paragraph"><p></p><p>Event sourcing is an architectural pattern for software development that has two components:</p><ul><li>To change the state of the application, you save the data associated with that change in an append-only log.</li><li>The current state of an item is derived by querying the log for related events and building the state from those events.</li></ul><p></p><p>It emerged from the domain-driven design community over twenty years ago, and like many things in the development world, its definition can vary drastically from the original.</p><p></p><p>However, these two components are the core of event sourcing. I’ve seen people include eventual consistency, CQRS, and event streaming in their definitions of event sourcing, but these are optional additions to the pattern.</p><p></p><p>It’s best to see an example. Let’s compare a shopping cart application built in a traditional way and an event-sourced way, you&#x27;d see a stark difference in the following scenario:</p><p></p><p>A user:</p><ul><li>adds a teeny weenie beanie to their shopping cart</li><li>adds a warm sweater</li><li>adds a scarf</li><li>adds one of those hats that has ear flaps</li><li>removes the teeny weenie beanie</li><li>checks out</li></ul><p></p><p>A traditional application would store the current state:</p><p></p></div> <div class="block-aligned_html"><dl> <dt>html</dt> <dd><table> <thead> <tr> <th>cart_id</th> <th>product_ids</th> <th>purchased_at</th> </tr> </thead> <tbody> <tr> <td>1234</td> <td>1,2,5</td> <td>2025-03-04T15:06:24</td> </tr> </tbody> </table></dd> <dt>alignment</dt> <dd>normal</dd> </dl></div> <div class="block-paragraph"><p></p><p>Where the event-sourced application would have saved all the changes:</p><p></p><p></p></div> <div class="block-aligned_html"><dl> <dt>html</dt> <dd><table> <thead> <tr> <th>event_id</th> <th>cart_id</th> <th>event_type</th> <th>data</th> <th>timestamp</th> </tr> </thead> <tbody> <tr> <td>23</td> <td>1234</td> <td>CartCreated</td> <td>{}</td> <td>2025-01-12T11:01:31</td> </tr> <tr> <td>24</td> <td>1234</td> <td>ItemAdded</td> <td>{“product_id”: 3}</td> <td>2025-01-12T11:01:31</td> </tr> <tr> <td>25</td> <td>1234</td> <td>ItemAdded</td> <td>{“product_id”: 2}</td> <td>2025-01-12T11:02:48</td> </tr> <tr> <td>26</td> <td>1234</td> <td>ItemAdded</td> <td>{“product_id”: 1}</td> <td>2025-01-12T11:04:15</td> </tr> <tr> <td>27</td> <td>1234</td> <td>ItemAdded</td> <td>{“product_id”: 5}</td> <td>2025-01-12T11:05:42</td> </tr> <tr> <td>28</td> <td>1234</td> <td>ItemRemoved</td> <td>{“product_id”: 3}</td> <td>2025-01-12T11:09:59</td> </tr> <tr> <td>29</td> <td>1234</td> <td>CheckedOut</td> <td>{}</td> <td>2025-01-12T11:10:20</td> </tr> </tbody> </table></dd> <dt>alignment</dt> <dd>normal</dd> </dl></div> <div class="block-paragraph"><p></p><p>From this example, it’s clear that event sourcing uses more storage space than a similar traditional app. This extra storage isn&#x27;t just a tradeoff—it unlocks powerful capabilities. Some of my favorite include:</p><p></p><p></p></div> <div class="block-h2">Having fast web views</div> <div class="block-paragraph"><p></p><p></p><p>Initially the thing that made me interested in event sourcing was to have fast web pages. I’ve worked on several projects with expensive database queries that hampered performance and user experience.</p><p></p><p>In one project, we introduced a new feature that stored product-specific metadata for items. For example, a line of printers had specific dimensions, was available in three colors, and had scanning capabilities. However, for a line of shredders, we would save its shredding rate, what technique it uses to shred, and its capacity.</p><p></p><p>This feature had a design flaw. The system needed to query the database multiple times to build the query that retrieved the item&#x27;s information. This caused our service to slow whenever a client hit one of our more common endpoints.</p><p></p><p>Most applications use the same mechanism to save and read data from the database, often optimizing for data integrity rather than read performance. This can lead to slow queries, especially when retrieving complex data.</p><p></p><p>For example, the database tables supporting the feature I mentioned above looked something somewhat like this:</p><p></p><p>A look-up table to define the product based on the product type, manufacturer, and model:</p><p></p></div> <div class="block-aligned_html"><dl> <dt>html</dt> <dd><table> <thead> <tr> <th>id</th> <th>product_type_id</th> <th>manufacturer_id</th> <th>model_id</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>23</td> <td>12</td> <td>38</td> </tr> <tr> <td>2</td> <td>141</td> <td>7</td> <td>125</td> </tr> </tbody> </table></dd> <dt>alignment</dt> <dd>normal</dd> </dl></div> <div class="block-paragraph"><p>A table to define the feature names and what kind of data they are:</p><p></p><p></p></div> <div class="block-aligned_html"><dl> <dt>html</dt> <dd><table> <thead> <tr> <th>id</th> <th>name</th> <th>type</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>available colors</td> <td>list_string</td> </tr> <tr> <td>2</td> <td>has scanner</td> <td>boolean</td> </tr> <tr> <td>3</td> <td>dimensions</td> <td>string</td> </tr> <tr> <td>4</td> <td>capacity</td> <td>string</td> </tr> </tbody> </table></dd> <dt>alignment</dt> <dd>normal</dd> </dl></div> <div class="block-paragraph"><p></p><p></p><p>A table that held the values:</p><p></p></div> <div class="block-aligned_html"><dl> <dt>html</dt> <dd><table> <thead> <tr> <th>id</th> <th>product_id</th> <th>feature_id</th> <th>value</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>1</td> <td>1</td> <td>["dark grey", "lighter grey", "gray grey"]</td> </tr> <tr> <td>2</td> <td>1</td> <td>2</td> <td>false</td> </tr> <tr> <td>3</td> <td>2</td> <td>3</td> <td>"roughly 2 feet in diameter..."</td> </tr> <tr> <td>4</td> <td>2</td> <td>4</td> <td>"64 cubic feet"</td> </tr> </tbody> </table></dd> <dt>alignment</dt> <dd>normal</dd> </dl></div> <div class="block-paragraph"><p></p><p>The final query to retrieve the features for a line of printers would look something like this:</p><p></p><p></p></div> <div class="block-code"> <pre class="line-numbers"><code id="target-element-current">SELECT f.name, pf.value, f.type FROM product_features pf JOIN features f ON pf.feature_id = f.id WHERE pf.product_id = ( SELECT id FROM products WHERE product_type_id = 23 AND manufacturer_id = 12 AND model_id = 38</code></pre> </div> <div class="block-paragraph"><p></p><p>That would return:</p><p></p><p></p></div> <div class="block-aligned_html"><dl> <dt>html</dt> <dd><table> <thead> <tr> <th>name</th> <th>value</th> <th>type</th> </tr> </thead> <tbody> <tr> <td>available colors</td> <td>["dark grey", "lighter grey", "gray grey"]</td> <td>list_string</td> </tr> <tr> <td>has scanner</td> <td>false</td> <td>boolean</td> </tr> </tbody> </table></dd> <dt>alignment</dt> <dd>normal</dd> </dl></div> <div class="block-paragraph"><p></p><p></p><p>Instead, you can use the CQRS (Command Query Responsibility Segregation) pattern. Instead of using the same data model for both reads and writes, CQRS separates them, allowing the system to maintain highly efficient, read-optimized views.</p><p></p><p>A read-optimized view of features could look like this:</p><p></p></div> <div class="block-aligned_html"><dl> <dt>html</dt> <dd><table> <thead> <tr> <th>product_type_id</th> <th>manufacturer_id</th> <th>model_id</th> <th>features</th> </tr> </thead> <tbody> <tr> <td>23</td> <td>12</td> <td>38</td> <td> [{"name":"available colors", "value":["dark grey", "lighter grey", "office grey", "gray grey"], "type":"list_string"}, {"name":"has scanner", "value": false, "type": "boolean"}, ...] </td> </tr> <tr> <td>141</td> <td>7</td> <td>125</td> <td> [{"name":"dimensions", "value":"roughly 2 feet in diameter at the mouth and 4 feet deep", "type":"string"}, {"name":"capacity", "value": "64 cubic feet", "type": "string"}, ...] </td> </tr> </tbody> </table></dd> <dt>alignment</dt> <dd>normal</dd> </dl></div> <div class="block-paragraph"><p></p><p></p><p>And querying it would look like:</p><p></p></div> <div class="block-code"> <pre class="line-numbers"><code id="target-element-current">SELECT features FROM features_table WHERE product_type_id = 23 AND manufacturer_id = 12 AND model_id = 38;</code></pre> </div> <div class="block-paragraph"><p>What a difference!</p><p>I recommend looking into CQRS even without using event sourcing.</p></div> <div class="block-h3">Event sourcing pairs well with CQRS</div> <div class="block-paragraph"><p>Event sourcing aligns well with CQRS because once events have been written to the append-only log, the system can also publish the event to internal functions that can do something with that data, like updating read-optimized views. This allows applications to maintain high performance and prevent complex queries.</p><p>An event-sourced solution that used a command-query responsibility segregation (CQRS) pattern would have allowed us to maintain a read-optimized table instead of constructing expensive queries dynamically.</p><p>While this specific case was painful, your project doesn’t have to be that bad to see a benefit. In today’s world of spinners and waiting for data to appear in blank interfaces, it’s refreshing to have a web app that loads quickly.</p><p>As a developer, it’s also nice not to chase data down across multiple tables. As someone once said, “I always like to get the data for a view by running `SELECT * FROM ui_specific_table WHERE id = 123;`.</p><p></p></div> <div class="block-h3">Not just web views</div> <div class="block-paragraph"><p></p><p></p><p>The same principles that make web views fast can also help with large reports or exports.</p><p></p><p>Another project I know about suffered performance problems whenever an admin user would request to download a report. Querying the data was expensive, and generating the file took a lot of memory. The whole process slowed the application down for every user and timed out occasionally, causing the process to start over.</p><p></p><p>The team changed their approach to storing files on the server and incrementally updating them as events happened. This turned what was an expensive operation that slowed the system for 20 or more seconds per request into a simple static file transfer that took milliseconds without straining the server at all.</p><p></p><p></p></div> <div class="block-h2">Schema changes without fear</div> <div class="block-paragraph"><p></p><p></p><p>Another thing I love about the event sourcing pattern is changing database schemas and experimenting with new features.</p><p></p><p>In my last blog post, I mentioned adding a duration column to a table that shows the status of files being processed by an application I&#x27;m working on. Since I wrote that, we&#x27;ve determined that we would like even more information. I will add the duration for each step in the process to that view.</p><p></p><p>This change is relatively simple from a database perspective. I will add new columns for each step&#x27;s duration. But if I needed to change the table&#x27;s schema significantly, I would still confidently approach this task.</p><p></p><p>I would look at the UI, see how the data would be formatted, and consider how we could store the data in that format. That would become the schema for a new table for this feature.</p><p></p><p>Then, I would write code that would query the store for each kind of event that changes the data. For example, I would have a function that creates a row whenever a `FileAdded` event is saved and another that updates the row&#x27;s progress percent and duration information when a step finishes.</p><p></p><p>Then, I would create a script that reads every event in the event log and calls any function associated with that event.</p><p></p><p>In Python, that script could look like this:</p><p></p></div> <div class="block-code"> <pre class="line-numbers"><code id="target-element-current">def populate_table(events): for event in events: if event.kind == &#x27;FileAdded&#x27;: on_file_added(event) elif event.kind == &#x27;FileMetadataProcessed&#x27;: on_metadata_added(event) ...</code></pre> </div> <div class="block-paragraph"><p></p><p></p><p>This would populate the table in seconds (without causing other side effects).</p><p></p><p>Then, I would have the web page load the data from that table to check my work. If something isn&#x27;t right, I&#x27;d adjust and replay the events again.</p><p></p><p>I love the flexibility this pattern gives me. I can create and remove database tables as needed, confident that the system isn&#x27;t losing data.</p><p></p></div> <div class="block-h2">Up next</div> <div class="block-paragraph"><p></p><p></p><p>Once I started working on an event-sourced project, I found a new feature that became my favorite, to the point that it completely changed how I think about writing applications. In the next post, I&#x27;ll explore how coupling is one of the biggest challenges in software and how the same properties that make event sourcing flexible also make it a powerful tool for reducing coupling.</p><p></p><p></p></div><br /><a href="https://everydaysuperpowers.devhttps://everydaysuperpowers.dev/articles/what-is-event-sourcing-and-why-you-should-care/">Read more...</a><br /></p> <p> <em><a href="https://everydaysuperpowers.dev/articles/what-is-event-sourcing-and-why-you-should-care/">April 08, 2025 04:38 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://pythoninsider.blogspot.com/" title="Python Insider">Python Insider</a></h3> <h4><a href="https://pythoninsider.blogspot.com/2025/03/python-3140-alpha-6-is-out.html">Python 3.14.0 alpha 6 is out</a></h4> <p> <p>Here comes the penultimate alpha.</p> <p><a href="https://www.python.org/downloads/release/python-3140a6/" class="uri">https://www.python.org/downloads/release/python-3140a6/</a></p> <p><strong>This is an early developer preview of Python 3.14</strong></p> <h1 id="major-new-features-of-the-3.14-series-compared-to-3.13">Major new features of the 3.14 series, compared to 3.13</h1> <p>Python 3.14 is still in development. This release, 3.14.0a6, is the sixth of seven planned alpha releases.</p> <p>Alpha releases are intended to make it easier to test the current state of new features and bug fixes and to test the release process.</p> <p>During the alpha phase, features may be added up until the start of the beta phase (2025-05-06) and, if necessary, may be modified or deleted up until the release candidate phase (2025-07-22). Please keep in mind that this is a preview release and its use is <strong>not</strong> recommended for production environments.</p> <p>Many new features for Python 3.14 are still being planned and written. Among the new major new features and changes so far:</p> <ul> <li><a href="https://peps.python.org/pep-0649/">PEP 649</a>: <a href="https://docs.python.org/3.14/whatsnew/3.14.html#pep-649-deferred-evaluation-of-annotations">deferred evaluation of annotations</a></li> <li><a href="https://peps.python.org/pep-0741/">PEP 741</a>: Python configuration C API</li> <li><a href="https://peps.python.org/pep-0761/">PEP 761</a>: Python 3.14 and onwards no longer provides PGP signatures for release artifacts. Instead, Sigstore is recommended for verifiers.</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#improved-error-messages">Improved error messages</a></li> <li>A <a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-tail-call">new type of interpreter</a>. For certain newer compilers, this interpreter provides significantly better performance. Opt-in for now, requires building from source.</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#uuid">UUID versions 6-8</a> are now supported by the <code>uuid</code> module, and generation of versions 3-5 and 8 are up to 40% faster.</li> <li>Python <a href="https://docs.python.org/3.14/whatsnew/3.14.html#removed">removals</a> and <a href="https://docs.python.org/3.14/whatsnew/3.14.html#deprecated">deprecations</a></li> <li>C API <a href="https://docs.python.org/3.14/whatsnew/3.14.html#c-api-removed">removals</a> and <a href="https://docs.python.org/3.14/whatsnew/3.14.html#c-api-deprecated">deprecations</a></li> <li><small>(Hey, <strong>fellow core developer,</strong> if a feature you find important is missing from this list, let Hugo know.)</small></li> </ul> <p>The next pre-release of Python 3.14 will be the final alpha, 3.14.0a7, currently scheduled for 2025-04-08.</p> <h1 id="more-resources">More resources</h1> <ul> <li><a href="https://docs.python.org/3.14/">Online documentation</a></li> <li><a href="https://peps.python.org/pep-0745/">PEP 745</a>, 3.14 Release Schedule</li> <li>Report bugs at <a href="https://github.com/python/cpython/issues">github.com/python/cpython/issues</a></li> <li><a href="https://www.python.org/psf/donations/">Help fund Python and its community</a></li> </ul> <h1 id="and-now-for-something-completely-different">And now for something completely different</h1> <p>March 14 is celebrated as <a href="https://www.exploratorium.edu/pi">pi day</a>, because 3.14 is an approximation of π. The day is observed by eating pies (savoury and/or sweet) and celebrating π. The first pi day was organised by physicist and tinkerer Larry Shaw of the San Francisco <a href="https://annex.exploratorium.edu/learning_studio/pi/">Exploratorium</a> in 1988. It is also the <a href="https://www.idm314.org/">International Day of Mathematics</a> and Albert Einstein’s birthday. Let’s all eat some pie, recite some π, install and test some py, and wish a happy birthday to Albert, Loren and all the other pi day children!</p> <h1 id="enjoy-the-new-release">Enjoy the new release</h1> <p>Thanks to all of the many volunteers who help make Python Development and these releases possible! Please consider supporting our efforts by volunteering yourself or through organisation contributions to the <a href="https://www.python.org/psf-landing/">Python Software Foundation</a>.</p> <p>Regards from Helsinki as fresh snow falls,</p> <p>Your release team, <br />Hugo van Kemenade<br />Ned Deily<br />Steve Dower<br />Łukasz Langa</p></p> <p> <em><a href="https://pythoninsider.blogspot.com/2025/03/python-3140-alpha-6-is-out.html">April 08, 2025 03:28 PM UTC</a></em> </p> <h4><a href="https://pythoninsider.blogspot.com/2025/04/python-3140a7-3133-31210-31112-31017.html">Python 3.14.0a7, 3.13.3, 3.12.10, 3.11.12, 3.10.17 and 3.9.22 are now available</a></h4> <p> <p>Not one, not two, not three, not four, not five, but six releases! Is this the most in a single day?</p> <p>3.12-3.14 were regularly scheduled, and we had some security fixes to release in 3.9-3.11 so let’s make a big day of it. This also marks the last bugfix release of 3.12 as it enters the security-only phase. See <a href="https://devguide.python.org/versions/">devguide.python.org/versions/</a> for a chart.</p> <h1 id="python-3.14.0a7">Python 3.14.0a7</h1> <p>Here comes the final alpha! This means we have just four weeks until the first beta to get those last features into 3.14 before the feature freeze on 2025-05-06!</p> <p><a href="https://www.python.org/downloads/release/python-3140a7/">https://www.python.org/downloads/release/python-3140a7/</a></p> <p><strong>This is an early developer preview of Python 3.14</strong></p> <h2 id="major-new-features-of-the-3.14-series-compared-to-3.13">Major new features of the 3.14 series, compared to 3.13</h2> <p>Python 3.14 is still in development. This release, 3.14.0a7, is the last of seven planned alpha releases.</p> <p>Alpha releases are intended to make it easier to test the current state of new features and bug fixes and to test the release process.</p> <p>During the alpha phase, features may be added up until the start of the beta phase (2025-05-06) and, if necessary, may be modified or deleted up until the release candidate phase (2025-07-22). Please keep in mind that this is a preview release and its use is <strong>not</strong> recommended for production environments.</p> <p>Many new features for Python 3.14 are still being planned and written. Among the new major new features and changes so far:</p> <ul> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep649">PEP 649</a>: deferred evaluation of annotations</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep741">PEP 741</a>: Python configuration C API</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep758">PEP 758</a>: Allow except and except* expressions without parentheses</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep761">PEP 761</a>: Python 3.14 and onwards no longer provides PGP signatures for release artifacts. Instead, Sigstore is recommended for verifiers.</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep765">PEP 765</a>: disallow <code>return</code>/<code>break</code>/<code>continue</code> that exit a <code>finally</code> block</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-pep768">PEP 768</a>: Safe external debugger interface for CPython</li> <li>A <a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-tail-call">new type of interpreter</a>. For certain newer compilers, this interpreter provides significantly better performance. Opt-in for now, requires building from source.</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#uuid">UUID versions 6-8</a> are now supported by the <code>uuid</code> module, and generation of versions 3-5 and 8 are up to 40% faster.</li> <li><a href="https://docs.python.org/3.14/whatsnew/3.14.html#improved-error-messages">Improved error messages</a></li> <li>Python <a href="https://docs.python.org/3.14/whatsnew/3.14.html#removed">removals</a> and <a href="https://docs.python.org/3.14/whatsnew/3.14.html#deprecated">deprecations</a></li> <li>C API <a href="https://docs.python.org/3.14/whatsnew/3.14.html#c-api-removed">removals</a> and <a href="https://docs.python.org/3.14/whatsnew/3.14.html#c-api-deprecated">deprecations</a></li> <li><small>(Hey, <strong>fellow core developer,</strong> if a feature you find important is missing from this list, let Hugo know.)</small></li> </ul> <p>The next pre-release of Python 3.14 will be the first beta, 3.14.0b1, currently scheduled for 2025-05-06. After this, no new features can be added but bug fixes and docs improvements are allowed – and encouraged!</p> <h1 id="python-3.13.3">Python 3.13.3</h1> <p>This is the third maintenance release of Python 3.13.</p> <p>Python 3.13 is the newest major release of the Python programming language, and it contains many new features and optimizations compared to Python 3.12. 3.13.3 is the latest maintenance release, containing almost 320 bugfixes, build improvements and documentation changes since 3.13.2.</p> <p><a href="https://www.python.org/downloads/release/python-3133/">https://www.python.org/downloads/release/python-3133/</a></p> <h1 id="python-3.12.10">Python 3.12.10</h1> <p>This is the tenth maintenance release of Python 3.12.</p> <p>Python 3.12.10 is the latest maintenance release of Python 3.12, and the last full maintenance release. Subsequent releases of 3.12 will be security-fixes only. This last maintenance release contains about 230 bug fixes, build improvements and documentation changes since 3.12.9.</p> <p><a href="https://www.python.org/downloads/release/python-31210/">https://www.python.org/downloads/release/python-31210/</a></p> <h1 id="python-3.11.12">Python 3.11.12</h1> <p>This is a security release of Python 3.11:</p> <ul> <li><a href="https://github.com/python/cpython/issues/106883">gh-106883</a>: Fix deadlock in threaded application when using sys._current_frames</li> <li><a href="https://github.com/python/cpython/issues/131809">gh-131809</a>: Upgrade vendored expat to 2.7.1</li> <li><a href="https://github.com/python/cpython/issues/80222">gh-80222</a>: Folding of quoted string in display_name violates RFC</li> <li><a href="https://github.com/python/cpython/issues/121284">gh-121284</a>: Invalid RFC 2047 address header after refolding with email.policy.default</li> <li><a href="https://github.com/python/cpython/issues/131261">gh-131261</a>: Update libexpat to 2.7.0</li> <li><a href="https://github.com/python/cpython/issues/105704">gh-105704</a>: [CVE-2025-0938] urlparse does not flag hostname <em>containing</em> [ or ] as incorrect</li> <li><a href="https://github.com/python/cpython/issues/119511">gh-119511</a>: OOM vulnerability in the imaplib module</li> </ul> <p><a href="https://www.python.org/downloads/release/python-31112/">https://www.python.org/downloads/release/python-31112/</a></p> <h1 id="python-3.10.17">Python 3.10.17</h1> <p>This is a security release of Python 3.10:</p> <ul> <li><a href="https://github.com/python/cpython/issues/131809">gh-131809</a>: Upgrade vendored expat to 2.7.1</li> <li><a href="https://github.com/python/cpython/issues/80222">gh-80222</a>: Folding of quoted string in display_name violates RFC</li> <li><a href="https://github.com/python/cpython/issues/121284">gh-121284</a>: Invalid RFC 2047 address header after refolding with email.policy.default</li> <li><a href="https://github.com/python/cpython/issues/131261">gh-131261</a>: Update libexpat to 2.7.0</li> <li><a href="https://github.com/python/cpython/issues/105704">gh-105704</a>: <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-0938">CVE-2025-0938</a> urlparse does not flag hostname <em>containing</em> [ or ] as incorrect</li> <li><a href="https://github.com/python/cpython/issues/119511">gh-119511</a>: OOM vulnerability in the imaplib module</li> </ul> <p><a href="https://www.python.org/downloads/release/python-31017/">https://www.python.org/downloads/release/python-31017/</a></p> <h1 id="python-3.9.22">Python 3.9.22</h1> <p>This is a security release of Python 3.9:</p> <ul> <li><a href="https://github.com/python/cpython/issues/131809">gh-131809</a> and <a href="https://github.com/python/cpython/issues/131261">gh-131261</a>: Upgrade vendored expat to 2.7.1</li> <li><a href="https://github.com/python/cpython/issues/121284">gh-121284</a>: Invalid RFC 2047 address header after refolding with email.policy.default</li> <li><a href="https://github.com/python/cpython/issues/105704">gh-105704</a>: <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-0938">CVE-2025-0938</a> urlparse does not flag hostname <em>containing</em> [ or ] as incorrect</li> <li><a href="https://github.com/python/cpython/issues/119511">gh-119511</a>: OOM vulnerability in the imaplib module</li> </ul> <p><a href="https://www.python.org/downloads/release/python-3922/">https://www.python.org/downloads/release/python-3922/</a></p> <h1 id="please-upgrade-please-test">Please upgrade! Please test!</h1> <p>We highly recommend upgrading 3.9-3.13 and we encourage you to test 3.14.</p> <h1 id="and-now-for-something-completely-different">And now for something completely different</h1> <p>On Saturday, 5th April, 3.141592653589793 months of the year had elapsed.</p> <h1 id="enjoy-the-new-releases">Enjoy the new releases</h1> <p>Thanks to all of the many volunteers who help make Python Development and these releases possible! Please consider supporting our efforts by volunteering yourself or through organisation contributions to the <a href="https://www.python.org/psf-landing/">Python Software Foundation</a>.</p> <p>Regards from a sunny and cold Helsinki springtime,</p> <p>Your full release team,</p> <p>Hugo van Kemenade<br />Thomas Wouters<br />Pablo Galindo Salgado<br />Łukasz Langa <br />Ned Deily<br />Steve Dower</p></p> <p> <em><a href="https://pythoninsider.blogspot.com/2025/04/python-3140a7-3133-31210-31112-31017.html">April 08, 2025 03:27 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="https://realpython.com/" title="Real Python">Real Python</a></h3> <h4><a href="https://realpython.com/courses/checking-membership-in-not-in-operators/">Checking for Membership Using Python's "in" and "not in" Operators</a></h4> <p> <p>Python&rsquo;s <code>in</code> and <code>not in</code> operators allow you to quickly check if a given value is or isn&rsquo;t part of a collection of values. This type of check is generally known as a <strong>membership test</strong> in Python. Therefore, these operators are known as <strong>membership operators</strong>.</p> <p><strong>By the end of this video course, you&rsquo;ll understand that:</strong></p> <ul> <li>The <strong><code>in</code> operator</strong> in Python is a <strong>membership operator</strong> used to check if a value is part of a collection.</li> <li>You can write <strong><code>not in</code></strong> in Python to check if a value is <strong>absent from a collection</strong>.</li> <li>Python&rsquo;s membership operators work with <strong>several data types</strong> like lists, tuples, ranges, and dictionaries.</li> <li>You can use <strong><code>operator.contains()</code></strong> as a function equivalent to the <code>in</code> operator for membership testing.</li> <li>You can support <code>in</code> and <code>not in</code> in <strong>custom classes</strong> by implementing methods like <code>.__contains__()</code>, <code>.__iter__()</code>, or <code>.__getitem__()</code>.</li> </ul> <hr /> <p><em>[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short &amp; sweet Python Trick delivered to your inbox every couple of days. <a href="https://realpython.com/python-tricks/?utm_source=realpython&utm_medium=rss&utm_campaign=footer">&gt;&gt; Click here to learn more and see examples</a> ]</em></p></p> <p> <em><a href="https://realpython.com/courses/checking-membership-in-not-in-operators/">April 08, 2025 02:00 PM UTC</a></em> </p> <h2>April 07, 2025</h2> <hr /><h3 class="post"><a href="https://www.blog.pythonlibrary.org/" title="Mouse Vs Python">Mike Driscoll</a></h3> <h4><a href="https://www.blog.pythonlibrary.org/2025/04/07/how-to-download-the-latest-release-assets-from-github-with-python/">How to Download the Latest Release Assets from GitHub with Python</a></h4> <p> <p>I recently needed to figure out how to write an updater script for a project I was working on. The application is released on an internal GitHub page with compressed files and an executable. I needed a way to check the latest release artifacts in GitHub and download them.</p> <p>Let&#8217;s find out how all this works!</p> <h2>Getting Set Up</h2> <p>You will need to download and install a couple of packages to make this all work. Specifically, you will need the following:</p> <ul> <li><a href="https://requests.readthedocs.io/en/latest/">requests</a></li> <li><a href="https://github.com/PyGithub/PyGithub">PyGitHub</a></li> </ul> <p>You can install both of these using pip. Open up your terminal and run the following command:</p> <pre class="EnlighterJSRAW">python -m pip install PyGithub requests</pre> <p>Once this finishes, you should have everything you need to get the latest GitHub release assets.</p> <h2>Downloading the Latest Release Assets</h2> <p>The only other item you will need to make this work is a <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens">GitHub personal access token</a>. You will need to create one of those. Depending on your use case, you may want to create what amounts to a bot account to make your token last a little longer.</p> <p>The next step is to write some code. Open up your favorite Python IDE and create a new file. Then add the following code to it:</p> <pre class="EnlighterJSRAW">import requests from github import Auth from github import Github from pathlib import Path token = "YOUR_PERSONAL_ACCESS_TOKEN" headers = CaseInsensitiveDict() headers["Authorization"] = f"token {token}" headers["Accept"] = "application/octet-stream" session = requests.Session() auth = Auth.Token(token) # Token can be None if the repo is public g = Github(auth=auth) # Use this one if you have an internal GitHub instance: #g = Github(auth=auth, base_url="https://YOUR_COMPANY_URL/api/v3") repo = g.get_repo("user/repo") # Replace with the proper user and repo combo for release in repo.get_releases(): # Releases are returned with the latest first print(release) break for asset in release.get_assets(): print(asset.name) destination = Path(r"C:\Temp") / asset.name response = session.get(asset.url, stream=True, headers=headers) with open(destination, "wb") as f: for chunk in response.iter_content(1024*1024): f.write(chunk) print(f"Downloaded asset to {destination}") </pre> <p>The first half of this code is your imports and boilerplate for creating a GitHub authentication token and a requests Session object. If you work for a company and have an internal GitHub instance, see the commented-out code and use that instead for your GitHub authentication.</p> <p>The next step is to get the GitHub repository and loop over its releases. By default, the iterable will return the items with the latest first and the oldest last. So you break out of the loop on the first release found to get the latest.</p> <p>At this point, you loop over the assets in the release. In my case, I wanted to find an asset that was an executable and download it, but this code downloads all the assets.</p> <h2>Wrapping Up</h2> <p>This is a pretty short example, but it demonstrates one of the many things you can do with the handy <a href="https://github.com/PyGithub/PyGithub">PyGitHub</a> package. You should check it out if you need to script other tasks in GitHub.</p> <p>Happy coding!</p> <p>The post <a href="https://www.blog.pythonlibrary.org/2025/04/07/how-to-download-the-latest-release-assets-from-github-with-python/">How to Download the Latest Release Assets from GitHub with Python</a> appeared first on <a href="https://www.blog.pythonlibrary.org">Mouse Vs Python</a>.</p></p> <p> <em><a href="https://www.blog.pythonlibrary.org/2025/04/07/how-to-download-the-latest-release-assets-from-github-with-python/">April 07, 2025 08:25 PM UTC</a></em> </p> <hr /><h3 class="post"><a href="" title="Python Archives - Erik Marsja">Erik Marsja</a></h3> <h4><a href="https://www.marsja.se/how-to-extract-gps-coordinates-from-a-photo-the-usaid-mystery/">How to Extract GPS Coordinates from a Photo: The USAID Mystery</a></h4> <p> <p>The post <a href="https://www.marsja.se/how-to-extract-gps-coordinates-from-a-photo-the-usaid-mystery/">How to Extract GPS Coordinates from a Photo: The USAID Mystery</a> appeared first on <a href="https://www.marsja.se">Erik Marsja</a>.</p> <p>In today&#x2019;s digital world, people do not just snap photos for memories; they capture hidden data. One of the most incredible pieces of information stored in many images is the geolocation, which includes latitude and longitude. If the device capturing the photo enabled GPS, it can tell us exactly where a photo was taken.</p> <p>In this post, I will show you how to extract geolocation data from an image using Python. I will specifically work with a photo of a <a href="https://sv.wikipedia.org/wiki/United_States_Agency_for_International_Development">USAID </a>nutrition pack, and after extracting the location, I will plot it on a map. But here is the catch: I will leave it up to you to decide if the pack should be there.</p> <span id="more-12144"></span> <h2 class="simpletoc-title">Table of Contents</h2> <ul class="simpletoc-list"> <li><a href="https://www.marsja.se/category/python/feed/#how-to-extract-gps-coordinates-in-python-and-plot-them-on-a-map">How to Extract GPS Coordinates in Python and Plot Them on a Map</a> <ul><li> <a href="https://www.marsja.se/category/python/feed/#step-1-setting-up-your-python-environment">Step 1: Setting Up Your Python Environment</a> </li> <li><a href="https://www.marsja.se/category/python/feed/#step-2-extracting-exif-data-from-the-photo">Step 2: Extracting EXIF Data from the Photo</a> </li> <li><a href="https://www.marsja.se/category/python/feed/#step-3-extracting-the-gps-coordinates">Step 3: Extracting the GPS Coordinates</a> </li> <li><a href="https://www.marsja.se/category/python/feed/#step-4-plotting-the-location-on-a-map">Step 4: Plotting the Location on a Map</a> </li> </ul> <li><a href="https://www.marsja.se/category/python/feed/#where-was-this-photo-taken">Where Was This Photo Taken?</a> <ul><li> <a href="https://www.marsja.se/category/python/feed/#should-the-usaid-nutrition-pack-be-in-this-location">Should the USAID nutrition pack be in this location?</a> </li> </ul> <li><a href="https://www.marsja.se/category/python/feed/#conclusion-the-photos-true-location">Conclusion: The Photo&#x2019;s True Location</a> </li></li></li></ul> <h2 class="wp-block-heading" id="how-to-extract-gps-coordinates-in-python-and-plot-them-on-a-map">How to Extract GPS Coordinates in Python and Plot Them on a Map</h2> <p>In this section, we will go through the four main steps involved in extracting GPS coordinates from a photo and visualizing it on a map. First, will set up the Python environment with the necessary libraries. Then, we will extract the EXIF data from the image, focus on removing the GPS coordinates, and finally, plot the location on a map.</p> <h3 class="wp-block-heading" id="step-1-setting-up-your-python-environment">Step 1: Setting Up Your Python Environment</h3> <p>Before extracting the GPS coordinates, let us prepare your Python environment. We will need a few libraries:</p> <ul class="wp-block-list"> <li><strong>Pillow</strong>: To handle the image file.</li> <li><strong>ExifRead</strong>: To read the EXIF metadata, including geolocation.</li> <li><strong>Folium</strong>: To plot the GPS coordinates on an interactive map.</li> </ul> <p>To install these libraries, run the following command:</p> <pre class="wp-block-code"><span><code class="hljs language-bash">pip install Pillow ExifRead folium</code></span></pre> <p>Now, we are ready to extract information from our photos!</p> <h3 class="wp-block-heading" id="step-2-extracting-exif-data-from-the-photo">Step 2: Extracting EXIF Data from the Photo</h3> <p>EXIF data is metadata embedded in photos by many cameras and smartphones. It can contain details such as date, camera settings, and GPS coordinates. We can access the latitude and longitude if GPS data is available in the photo.</p> <p>Here is how you can extract the EXIF data using Python:</p> <pre class="wp-block-code"><span><code class="hljs language-php">import exifread <span class="hljs-comment"># Open the image file</span> with open(<span class="hljs-string">'nutrition_pack.jpg'</span>, <span class="hljs-string">'rb'</span>) <span class="hljs-keyword">as</span> f: tags = exifread.process_file(f) <span class="hljs-comment"># Check the tags available</span> <span class="hljs-keyword">for</span> tag in tags: <span class="hljs-keyword">print</span>(tag, tags&#91;tag])</code></span></pre> <p>In the code chunk above, we open the image file <code>'nutrition_pack.jpg'</code> in binary mode and use the <code>exifread</code> library to process its metadata. The <code>process_file()</code> function extracts the EXIF data, which we then iterate through and print each tag along with its corresponding value. This allows us to see the available metadata in the image, including potential GPS coordinates.</p> <h3 class="wp-block-heading" id="step-3-extracting-the-gps-coordinates">Step 3: Extracting the GPS Coordinates</h3> <p>Now that we have the EXIF data, let us pull out the GPS coordinates. If the photo has geolocation data, it will be in the <code>GPSLatitude </code>and <code>GPSLongitude </code>fields. Here is how to extract them:</p> <pre class="wp-block-code"><span><code class="hljs language-python"><span class="hljs-comment"># Helper function to convert a list of Ratio to float degrees</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dms_to_dd</span><span class="hljs-params">(dms)</span>:</span> degrees = float(dms&#91;<span class="hljs-number">0</span>]) minutes = float(dms&#91;<span class="hljs-number">1</span>]) seconds = float(dms&#91;<span class="hljs-number">2</span>]) <span class="hljs-keyword">return</span> degrees + (minutes / <span class="hljs-number">60.0</span>) + (seconds / <span class="hljs-number">3600.0</span>) <span class="hljs-comment"># Updated keys to match your EXIF tag names</span> lat_key = <span class="hljs-string">'GPS GPSLatitude'</span> lat_ref_key = <span class="hljs-string">'GPS GPSLatitudeRef'</span> lon_key = <span class="hljs-string">'GPS GPSLongitude'</span> lon_ref_key = <span class="hljs-string">'GPS GPSLongitudeRef'</span> <span class="hljs-comment"># Check if GPS data exists</span> <span class="hljs-keyword">if</span> lat_key <span class="hljs-keyword">in</span> tags <span class="hljs-keyword">and</span> lon_key <span class="hljs-keyword">in</span> tags <span class="hljs-keyword">and</span> lat_ref_key <span class="hljs-keyword">in</span> tags <span class="hljs-keyword">and</span> lon_ref_key <span class="hljs-keyword">in</span> tags: <span class="hljs-comment"># Extract raw DMS data</span> lat_values = tags&#91;lat_key].values lon_values = tags&#91;lon_key].values <span class="hljs-comment"># Convert to decimal degrees</span> latitude = dms_to_dd(lat_values) longitude = dms_to_dd(lon_values) <span class="hljs-comment"># Adjust for hemisphere</span> <span class="hljs-keyword">if</span> tags&#91;lat_ref_key].printable != <span class="hljs-string">'N'</span>: latitude = -latitude <span class="hljs-keyword">if</span> tags&#91;lon_ref_key].printable != <span class="hljs-string">'E'</span>: longitude = -longitude print(<span class="hljs-string">f"GPS Coordinates: Latitude = <span class="hljs-subst">{latitude}</span>, Longitude = <span class="hljs-subst">{longitude}</span>"</span>) <span class="hljs-keyword">else</span>: print(<span class="hljs-string">"No GPS data found!"</span>)</code></span></pre> <p>In the code above, we first check whether all four GPS-related tags (<code>GPSLatitude</code>, <code>GPSLongitude</code>, and their respective directional references) are present in the image&#8217;s EXIF data. If they are, we extract the coordinate values, convert them from degrees&#x2013;minutes&#x2013;seconds (DMS) format to decimal degrees, and adjust the signs based on the hemisphere indicators. Finally, the GPS coordinates are printed. If any necessary tags are missing, we print a message stating that no GPS data was found.</p> <h3 class="wp-block-heading" id="step-4-plotting-the-location-on-a-map">Step 4: Plotting the Location on a Map</h3> <p>Now for the fun part! Once we have the GPS coordinates, we plot them on a map. I will use the Folium library to create an interactive map with a marker at the exact location. Here is how to do it:</p> <pre class="wp-block-code"><span><code class="hljs language-python"><span class="hljs-keyword">import</span> folium <span class="hljs-comment"># Create a map centered around the coordinates</span> map_location = folium.Map(location=&#91;latitude, longitude], zoom_start=<span class="hljs-number">12</span>) <span class="hljs-comment"># Add a marker for the photo location</span> folium.Marker(&#91;latitude, longitude], popup=<span class="hljs-string">"Photo Location"</span>).add_to(map_location) <span class="hljs-comment"># Save map to HTML</span> map_location.save(<span class="hljs-string">'map_location.html'</span>)</code></span></pre> <p>In the code chunk above, we create a map using the <code>folium</code> library, centered around the extracted GPS coordinates. We then add a marker at the photo&#8217;s location and attach a popup labeled &#8220;Photo Location.&#8221; Finally, the map is saved as an interactive HTML file, allowing us to view it in a web browser and explore the location on the map.</p> <h2 class="wp-block-heading" id="where-was-this-photo-taken">Where Was This Photo Taken?</h2> <p>We have now extracted the geolocation and plotted the coordinates on a map. Here is the question you should ask yourself:</p> <a href="https://www.marsja.se/wp-content/uploads/2025/04/the_location_of_USAID_nutrition_package_extracted_with_Python-1.webp"><img width="900" height="330" src="https://www.marsja.se/wp-content/uploads/2025/04/the_location_of_USAID_nutrition_package_extracted_with_Python-1.webp" alt="" class="wp-image-12203" /></a> <h3 class="wp-block-heading" id="should-the-usaid-nutrition-pack-be-in-this-location">Should the USAID nutrition pack be in this location?</h3> <p>By examining the map and the coordinates, you can make your judgment. Does it make sense for this nutrition pack to be in this specific place? Should it have been placed somewhere else? The photo is of a USAID nutrition pack, and these packs are typically distributed in various places around the world where aid is needed. But is this particular location one that should be receiving this kind of aid?</p> <p>The coordinates are up to you to interpret, and the map is ready for your eyes to roam. Take a look and think critically: Does this look like a place where this aid should be, or could other places be in more need?</p> <h2 class="wp-block-heading" id="conclusion-the-photos-true-location">Conclusion: The Photo&#x2019;s True Location</h2> <p>With just a few lines of Python code, I have extracted hidden geolocation data from a photo, plotted it on an interactive map, and raised the question about aid distribution. Should the USAID nutrition pack be where it was found? After exploring the location on the map, you may have your thoughts about whether this is the right spot for such aid.</p> <p>Comment below and let me know whether you think the pack should be where it was found. If you believe it should not be there, share this post on social media and help spark the conversation. Also, if you found this post helpful, please share it with others!</p> <p>The post <a href="https://www.marsja.se/how-to-extract-gps-coordinates-from-a-photo-the-usaid-mystery/">How to Extract GPS Coordinates from a Photo: The USAID Mystery</a> appeared first on <a href="https://www.marsja.se">Erik Marsja</a>.</p></p> <p> <em><a href="https://www.marsja.se/how-to-extract-gps-coordinates-from-a-photo-the-usaid-mystery/">April 07, 2025 07:03 PM UTC</a></em> </p> </div> </div> <div id="left-hand-navigation"> <div id="menu"> <ul class="level-one"> <li> <ul class="level-two"> <li><a href="rss20.xml">RSS feed</a></li> <li><a href="titles_only.html">Titles Only</a></li> <li><a href="http://www.planetplanet.org/">Powered by Planet!</a></li> </ul></li> <li>Other Python Planets <ul class="level-two"> <li><a href="http://terri.toybox.ca/python-soc/">Python Summer of Code</a></li> <li><a href="http://www.afpy.org/planet/">Planet Python Francophone</a></li> <li><a href="http://planeta.python.org.ar/">Planet Python Argentina</a></li> <li><a href="http://wiki.python.org.br/planet/">Planet Python Brasil</a></li> <li><a href="http://pl.python.org/planeta/">Planet Python Poland</a></li> </ul></li> <li>Python Libraries <ul class="level-two"> <li><a href="http://planet.laptop.org/">OLPC</a></li> <li><a href="http://planet.pysoy.org/">PySoy</a></li> <li><a href="http://planet.scipy.org/">SciPy</a></li> <li><a href="http://planet.sympy.org/">SymPy</a></li> <li><a href="http://planet.twistedmatrix.com/">Twisted</a></li> </ul></li> <li>Python/Web Planets <ul class="level-two"> <li><a href="http://planet.cherrypy.org/">CherryPy</a></li> <li><a href="http://www.djangoproject.com/community/">Django Community</a></li> <li><a href="http://planet.plone.org/">Plone</a></li> <li><a href="http://planet.turbogears.org/">Turbogears</a></li> </ul></li> <li>Other Languages <ul class="level-two"> <li><a href="http://planet.haskell.org/">Haskell</a></li> <li><a href="http://planet.lisp.org/">Lisp</a></li> <li><a href="http://planet.parrotcode.org/">Parrot</a></li> <li><a href="http://planet.perl.org/">Perl</a></li> <li><a href="http://planetruby.0x42.net/">Ruby</a></li> </ul></li> <li>Databases <ul class="level-two"> <li><a href="http://www.planetmysql.org/">MySQL</a></li> <li><a href="http://planet.postgresql.org/">PostgreSQL</a></li> <li><a href=""></a></li> </ul></li> <li>Subscriptions <ul class="level-two"> <li><a href="opml.xml">[OPML feed]</a></li> <li><a href="https://mathspp.com/blog/pydonts" title="mathspp.com feed">"Mathspp Pydon'ts"</a> </li> <li><a href="" title="">"Menno's Musings"</a> </li> <li><a href="http://mjtokelly.blogspot.com/search/label/Python" title="mokelly's devlog">"Michael J.T. O'Kelly"</a> </li> <li><a href="https://blogologue.com/atom.xml?category=1470141490X3&amp;protocol=https" title="Morphex's blogologue (Life, technology, music, politics, business, mental health and more)">"Morphex's Blogologue"</a> </li> <li><a href="http://speno.blogspot.com/" title="Speno's Pythonic Avocado">"Speno's Pythonic Avocado"</a> </li> <li><a href="https://william-os4y.livejournal.com/" title="William's journal">"William's Journal"</a> </li> <li><a href="http://dev.2degreesnetwork.com/" title="2degrees Developers">2degrees</a> </li> <li><a href="https://emptysqua.re/blog/category/python/" title="Python on A. Jesse Jiryu Davis">A. Jesse Jiryu Davis</a> </li> <li><a href="https://ablog.readthedocs.org/" title="ABlog">ABlog for Sphinx</a> </li> <li><a href="http://www.artima.com/weblogs/index.jsp?blogger=aahz" title="Aahz's Weblog">Aahz</a> </li> <li><a href="http://djangocentral.com" title="Djangocentral">Abhijeet Pal</a> </li> <li><a href="http://masnun.rocks/tags/python/index.xml" title="masnun.rocks()">Abu Ashraf Masnun</a> </li> <li><a href="http://techarttiki.blogspot.com/search/label/python" title="Tech Art Tiki">Adam Pletcher</a> </li> <li><a href="https://agendaless.com/blog/?category=python" title="Agendaless Blog">Agendaless Consulting</a> </li> <li><a href="https://efficientcoder.net/" title="">Ahmed Bouchefra</a> </li> <li><a href="http://alstatr.blogspot.com/search/label/Python" title="Analysis with Programming">Al-Ahmadgaid Asaad</a> </li> <li><a href="http://alecmunro.blogspot.com/search/label/python" title="Experiments in public">Alec Munro</a> </li> <li><a href="http://alextechrants.blogspot.com/search/label/python" title="Alex's Tech Rants">Alex Grönholm</a> </li> <li><a href="https://morozov.ca/" title="CTO with a CMO flavor - python">Alex Morozov</a> </li> <li><a href="http://limi.net/" title="Alex Limi">Alexander Limi</a> </li> <li><a href="http://www.alexconrad.org/search/label/python" title="Alex Conrad">Alexandre Conrad</a> </li> <li><a href="https://peadrop.com/blog" title="Alexandre Vassalotti » Python">Alexandre Vassalotti</a> </li> <li><a href="https://st4lk.github.io/en/" title="Alexey Evseev">Alexey Evseev</a> </li> <li><a href="http://akaptur.github.com/" title="Category: python, | Allison Kaptur">Allison Kaptur</a> </li> <li><a href="https://blog.amjith.com" title="Brain Spill - python">Amjith Ramanujam</a> </li> <li><a href="https://www.amvtek.com/blog/" title="AmvTek blog">AmvTek</a> </li> <li><a href="https://anarc.at/tag/python-planet/" title="pages tagged python-planet">Anarcat</a> </li> <li><a href="http://techtonik.rainforce.org/search/label/python" title="another day another vice another roll of the dice">Anatoly Techtonik</a> </li> <li><a href="https://aroberge.blogspot.com/" title="Only Python">Andre Roberge</a> </li> <li><a href="https://www.andreagrandi.it/" title="Andrea Grandi - Python">Andrea Grandi</a> </li> <li><a href="http://www.dalkescientific.com/writings/diary/index.html" title="Andrew Dalke's writings">Andrew Dalke</a> </li> <li><a href="http://mindref.blogspot.com/search/label/python" title="Mind Reference">Andriy Kornatskyy</a> </li> <li><a href="http://mysql-python.blogspot.com/" title="MySQL-Python">Andy Dustman</a> </li> <li><a href="http://andy.terrel.us/" title="Codematician - Python">Andy R. Terrel</a> </li> <li><a href="http://annaraven.blogspot.com/search/label/python" title="Meandering streams of consciousness">Anna Martelli Ravenscroft</a> </li> <li><a href="http://codingweasel.blogspot.com/search/label/python" title="The Coding Weasel">Anthony Baxter</a> </li> <li><a href="http://kpoxit.blogspot.com/search/label/python" title="kpoxit">Anton Belyaev</a> </li> <li><a href="http://bobrochel.blogspot.com/search/label/python" title="Beaver notes">Anton Bobrov</a> </li> <li><a href="http://anweshadas.in/" title="Python - Law Explained India">Anwesha Das</a> </li> <li><a href="https://arilamstein.com" title="Python – Ari Lamstein">Ari Lamstein</a> </li> <li><a href="http://lucumr.pocoo.org/" title="Armin Ronacher's Thoughts and Writings">Armin Ronacher</a> </li> <li><a href="https://rushter.com/blog/feed/" title="Artem Golubin | Python">Artem Golubin</a> </li> <li><a href="https://medium.com/python4you?source=rss----5527f69f4771---4" title="python4you - Medium">Artem Rys</a> </li> <li><a href="http://pyfunc.blogspot.com/search/label/python" title="Programmer's notebook">Ashish Vidyarthi</a> </li> <li><a href="http://astrocodeschool.com/" title="Astro Code School">Astro Code School</a> </li> <li><a href="http://www.automatingosint.com/blog" title="Python – Automating OSINT Blog">Automating OSINT</a> </li> <li><a href="https://github.com/mahmoud/awesome-python-applications" title="Awesome Python Applications">Awesome Python Applications</a> </li> <li><a href="http://baijum.blogspot.com/search/label/python" title="Baiju Muthukadan's Blog">Baiju Muthukadan</a> </li> <li><a href="http://gbtami.github.io" title="gbtami - Python">Bajusz Tamás</a> </li> <li><a href="https://www.bedjango.com/planet/feed/" title="Bedjango posts">BeDjango</a> </li> <li><a href="http://blog.codedstructure.net/search/label/python" title="Coded Structure">Ben Bass</a> </li> <li><a href="https://sparrow.dev/" title="Sparrow Computing">Ben Cook</a> </li> <li><a href="http://clusterbleep.net/blog" title="python – Ben Rousch's Cluster of Bleep">Ben Rousch</a> </li> <li><a href="http://pybites.blogspot.com/" title="Python bytes">Benjamin Peterson</a> </li> <li><a href="http://benjiyork.com/blog/" title="Benji York">Benji York</a> </li> <li><a href="http://zebert.blogspot.com/search/label/python" title="Paste here">Bertrand Mathieu</a> </li> <li><a href="https://geeksocket.in/tags/python/" title="python on GeekSocket">Bhavin Gandhi</a> </li> <li><a href="https://thetaranights.com" title="BHISHAN BHANDARI – PROGRAMMING BLOG">Bhishan Bhandari</a> </li> <li><a href="https://www.open-bio.org/news/category/obf-projects/biopython/feed/atom/#respond" title="Comments on">BioPython News</a> </li> <li><a href="http://bitofcheese.blogspot.com/" title="Bit Of Cheese">Bit of Cheese</a> </li> <li><a href="http://code.informatikamihelac.com/en/tags/django/" title="code informatikamihelac blog for django">Bojan Mihelac</a> </li> <li><a href="https://rhodesmill.org/brandon/category/python/feed/" title="Let's Discuss the Matter Further">Brandon Rhodes</a> </li> <li><a href="https://breadcrumbscollector.tech" title="python – Breadcrumbs Collector">BreadcrumbsCollector</a> </li> <li><a href="https://python4dads.wordpress.com" title="Python for Dads">Brendan Scott</a> </li> <li><a href="https://snarky.ca/" title="Tall, Snarky Canadian">Brett Cannon</a> </li> <li><a href="https://pythontest.com/posts/" title="Archive on PythonTest">Brian Okken</a> </li> <li><a href="https://nicoddemus.github.io/" title="Midnight Coding - python">Bruno Oliveira</a> </li> <li><a href="https://www.codingthepast.com/" title="coding-the-past">Bruno Ponne / Coding The Past</a> </li> <li><a href="http://brunorocha.org/" title="BrunoRocha.org | Python web development | Tag pythonplanet | feed">Bruno Rocha</a> </li> <li><a href="" title="">Caktus Consulting Group</a> </li> <li><a href="https://calvinx.com" title="Python – Calvin's">Calvin Cheng</a> </li> <li><a href="http://techblog.ironfroggy.com/search/label/programming" title="Developing Upwards">Calvin Spealman</a> </li> <li><a href="https://carlchenet.com" title="Carl Chenet">Carl Chenet</a> </li> <li><a href="https://carl.duevel.online/tags/python/" title="python on Personal weblog">Carl Düvel</a> </li> <li><a href="http://pyright.blogspot.com/" title="pyright">Carl Trachte</a> </li> <li><a href="http://themindcaster.blogspot.com/search/label/python" title="The Mindcaster">Carlos Eduardo de Paula</a> </li> <li><a href="http://eatthedots.blogspot.com/" title=":: eat the dots ::">Casey Duncan</a> </li> <li><a href="http://catherinedevlin.blogspot.com/search/label/python" title="Catherine: pyOraGeek">Catherine Devlin</a> </li> <li><a href="https://chinghwayu.com" title="Python – Ching-Hwa Yu">Ching-Hwa Yu</a> </li> <li><a href="https://www.metachris.com/tags/python/" title="Python on Chris Hager">Chris Hager</a> </li> <li><a href="http://chris-miles-writes-python.blogspot.com/" title="Chris Miles Writes Python">Chris Miles</a> </li> <li><a href="http://pyinformatics.blogspot.com/" title="PyInformatics: Bioinformatics and Data Science in Python">Chris Mitchell</a> </li> <li><a href="https://pbpython.com/" title="Practical Business Python">Chris Moffitt</a> </li> <li><a href="https://offby1.website/" title="Ideas.Offby1 - python">Chris Rose</a> </li> <li><a href="https://chriswarrick.com/" title="Chris Warrick (Posts about Python)">Chris Warrick</a> </li> <li><a href="http://lipyrary.blogspot.com/" title="LiPyrary - Python for books">Christian Heimes</a> </li> <li><a href="https://dev.to/ldrscke" title="DEV Community: Christian Ledermann">Christian Ledermann</a> </li> <li><a href="" title="Kommentare zu:">Christian Scholz</a> </li> <li><a href="https://bugfactory.io/" title="Web Development for SaaS Businesses | BugFactory">Christoph Schiessl</a> </li> <li><a href="https://cito.github.io/tags/python/" title="Python on Seasoned &amp;amp; Agile">Christoph Zwerschke</a> </li> <li><a href="https://blog.codegrades.com/" title="The CodeGrades Blog">CodeGrades</a> </li> <li><a href="http://CodeSnipers.com/?q=taxonomy/term/16/0" title="CodeSnipers.com - Python">CodeSnipers</a> </li> <li><a href="https://coderslegacy.com/category/python/" title="Python Archives - CodersLegacy">CodersLegacy</a> </li> <li><a href="https://allanderek.github.io/" title="Coding Diet (python)">Coding Diet</a> </li> <li><a href="https://gallon.me" title="python – gallon.me">Corey Gallon</a> </li> <li><a href="http://coreygoldberg.blogspot.com/search/label/python" title="Corey Goldberg">Corey Goldberg</a> </li> <li><a href="http://cormoran-project.blogspot.com/" title="Cormoran Development Blog">Cormoran Project</a> </li> <li><a href="http://portablecommandline.blogspot.com/" title="Cross-Platform Command Line Tools">Cross-Platform Command Line Tools</a> </li> <li><a href="https://www.cubicweb.org/view?rql=Any%20X%2CT%2CCD%20ORDERBY%20CD%20DESC%20LIMIT%2020%20WHERE%20X%20is%20BlogEntry%2C%20X%20title%20T%2C%20X%20creation_date%20CD" title="Blog entries (CubicWeb's Forge) RSS Feed">CubicWeb</a> </li> <li><a href="https://ntguardian.wordpress.com" title="Python – Curtis Miller's Personal Website">Curtis Miller</a> </li> <li><a href="https://dspillustrations.com/" title="DSPIllustrations.com">DSPIllustrations.com</a> </li> <li><a href="https://www.dapythonista.com/" title="Yüzünü Karartma BlackJack Bahis Sitelerinde Kazanan Hep Sensin!">DaPythonista</a> </li> <li><a href="https://dailytechvideo.com" title="Python – Daily Tech Video">Daily Tech Video (Python)</a> </li> <li><a href="http://dfwpython.blogspot.com/" title="DFW Pythoneers">Dallas Fort Worth Pythoneers</a> </li> <li><a href="https://late.am/" title="late.am - python">Dan Crosta</a> </li> <li><a href="http://strombrg.blogspot.com/search/label/Python" title="Dan on Python">Dan Stromberg</a> </li> <li><a href="https://dan.yeaw.me/" title="Dan Yeaw's Blog (Posts about Python)">Dan Yeaw</a> </li> <li><a href="https://dbader.org/blog/tags/python" title="dbader.org - Python">Daniel Bader</a> </li> <li><a href="https://danielnouri.org/notes" title="Daniel Nouri's Blog">Daniel Nouri</a> </li> <li><a href="https://daniel.feldroy.com" title="Python posts by Daniel Roy Greenfeld">Daniel Roy Greenfeld</a> </li> <li><a href="" title="">Data Community DC</a> </li> <li><a href="https://www.dataschool.io/" title="Python - Data School">Data School</a> </li> <li><a href="https://www.datawars.io" title="DataWars Posts and Articles about Data Science">DataWars.io</a> </li> <li><a href="http://dabeaz.blogspot.com/" title="Dabeaz">Dave Beazley</a> </li> <li><a href="https://davidamos.dev/" title="Python - Curious About Code">David Amos</a> </li> <li><a href="https://davidcaron.dev/" title="David Caron - python">David Caron</a> </li> <li><a href="http://www.artima.com/weblogs/index.jsp?blogger=goodger" title="David Goodger's Weblog">David Goodger</a> </li> <li><a href="https://davidlindelof.com" title="Python - David's blog">David Lindelof</a> </li> <li><a href="https://www.drmaciver.com" title="Python – David R. MacIver">David MacIver</a> </li> <li><a href="https://dmalcolm.livejournal.com/" title="dmalcolm">David Malcolm</a> </li> <li><a href="http://www.szotten.com/david/" title="davidszotten - python">David Szotten</a> </li> <li><a href="http://davidemoro.blogspot.com/search/label/planetpython" title="Davide Moro">Davide Moro</a> </li> <li><a href="http://daftpython.blogspot.com/" title="daftpython">Davy Mitchell</a> </li> <li><a href="https://davywybiral.blogspot.com/" title="davy wybiral">Davy Wybiral</a> </li> <li><a href="https://declassed.art" title="Declassed use of Python">Declassed Art</a> </li> <li><a href="http://makkalot-opensource.blogspot.com/search/label/python" title="Open Sourced">Denis Kurov</a> </li> <li><a href="https://www.djangoproject.com/weblog/" title="The Django weblog">Django Weblog</a> </li> <li><a href="https://djangostars.com/blog" title="Django Stars Blog">Djangostars</a> </li> <li><a href="http://doingmathwithpython.github.io/" title="Doing Math with Python">Doing Math with Python</a> </li> <li><a href="https://doughellmann.com/tags/python/" title="python on Doug Hellmann">Doug Hellmann</a> </li> <li><a href="http://www.dougalmatthews.com/" title="Dougal Matthews - python">Dougal Matthews</a> </li> <li><a href="http://oubiwann.blogspot.com/search/label/python" title="Electric Duncan">Duncan McGreggor</a> </li> <li><a href="http://edcrewe.blogspot.com/search/label/python" title="Ed Crewe">Ed Crewe</a> </li> <li><a href="http://edreamleo.blogspot.com/search/label/python" title="Explorations">Edward K. Ream</a> </li> <li><a href="https://eli.thegreenplace.net/" title="Eli Bendersky's website - Python">Eli Bendersky</a> </li> <li><a href="http://eniramltd.github.io/devblog/" title="Eniram developers' blog - python">Eniram Ltd.</a> </li> <li><a href="https://examachine.net/blog/category/python/" title="Python Archives &amp;lt; log.examachine.net">Eray Özkural (examachine)</a> </li> <li><a href="" title="Python Archives - Erik Marsja">Erik Marsja</a> </li> <li><a href="http://etienned.github.io/" title="Etienne’s blog (Python)">Etienne Desautels</a> </li> <li><a href="https://blog.europython.eu/" title="EuroPython Blog">EuroPython</a> </li> <li><a href="https://www.europython-society.org/" title="EuroPython Society">EuroPython Society</a> </li> <li><a href="http://evennia.blogspot.com/" title="Griatch's Evennia musings">Evennia</a> </li> <li><a href="https://https://everydaysuperpowers.dev/articles/?tag=" title="Recent articles from Everyday Superpowers">Everyday Superpowers</a> </li> <li><a href="http://pydev.blogspot.com/" title="PyDev adventures">Fabio Zadrozny</a> </li> <li><a href="http://en.ig.ma/notebook/tag/python/" title="Filip Wasilewski (en.ig.ma): Newest Articles Tagged 'python'">Filip Wasilewski</a> </li> <li><a href="https://blog.filipesaraiva.info" title="planet-python – Filipe Saraiva's blog">Filipe Saraiva</a> </li> <li><a href="http://pyinsci.blogspot.com/" title="Python in Science">Flavio Coelho</a> </li> <li><a href="https://blog.flaper87.com/" title="Developer's black hole - python">Flavio Percoco</a> </li> <li><a href="http://blog.devork.be/search/label/python" title="devork">Floris Bruynooghe</a> </li> <li><a href="http://fwierzbicki.blogspot.com/" title="Frank Wierzbicki's Weblog">Frank Wierzbicki</a> </li> <li><a href="http://raspberry-python.blogspot.com/search/label/english" title="Raspberry Pi Python Adventures">François Dion</a> </li> <li><a href="https://feeding.cloud.geek.nz/tags/python/" title="pages tagged python">François Marier</a> </li> <li><a href="https://www.fridh.nl/" title="FRidh's blog - python">Frederik Rietdijk</a> </li> <li><a href="http://www.frompythonimportpodcast.com" title="From Python Import Podcast">From Python Import Podcast</a> </li> <li><a href="https://www.fullstackpython.com/" title="Full Stack Python">Full Stack Python</a> </li> <li><a href="https://gael-varoquaux.info/" title="Gaël Varoquaux - programming">Ga&#235;l Varoquaux</a> </li> <li><a href="https://compiletoi.net/" title="python - Compile-toi toi même">Georges Dubus</a> </li> <li><a href="https://ghaandeeonit.tumblr.com/" title="Untitled">Ghaandee on IT</a> </li> <li><a href="https://gmpy.dev/" title="Giampaolo Rodola - python">Giampaolo Rodola</a> </li> <li><a href="http://giuliofidente.com/" title="Giulio Fidente - python">Giulio Fidente</a> </li> <li><a href="http://hackermojo.com" title="Glenn Fanxman's python feed">Glenn Franxman</a> </li> <li><a href="https://blog.glyph.im/" title="Deciphering Glyph">Glyph Lefkowitz</a> </li> <li><a href="http://paddy3118.blogspot.com/" title="Go deh!">Go Deh</a> </li> <li><a href="https://blog.gocept.com" title="python – gocept blog – public archive">Gocept Weblog</a> </li> <li><a href="http://blog.godson.in/search/label/Python" title="Core Dump -- Godson Gera's Blog">Godson Gera</a> </li> <li><a href="https://www.curiousvenn.com" title="Python – Curious Venn">Graeme Cross</a> </li> <li><a href="http://blog.dscpl.com.au/" title="Graham Dumpleton">Graham Dumpleton</a> </li> <li><a href="https://www.grahamwheeler.com/" title="Graham Wheeler's Random Forest (Posts about Python)">Graham Wheeler</a> </li> <li><a href="http://defrobnication.blogspot.com/search/label/python" title="Defrobnication">Grant Baillie</a> </li> <li><a href="https://www.wisdomandwonder.com" title="Python – Wisdom And Wonder">Grant Rettke</a> </li> <li><a href="https://gc-taylor.com/" title="Greg Taylor">Greg Taylor</a> </li> <li><a href="http://agiletesting.blogspot.com/search/label/python" title="Agile Testing">Grig Gheorghiu</a> </li> <li><a href="https://fizyk.dev/" title="Cases (Posts about python)">Grzegorz Śliwiński</a> </li> <li><a href="http://neopythonic.blogspot.com/" title="Neopythonic">Guido van Rossum</a> </li> <li><a href="https://gustavonarea.net/" title="Python – Gustavo on Software Development">Gustavo Narea</a> </li> <li><a href="https://blog.labix.org" title="Labix Blog">Gustavo Niemeyer</a> </li> <li><a href="http://pycloud.blogspot.com/search/label/python" title="On Clouds, Poems, Python and more...">Gökhan Sever</a> </li> <li><a href="http://python-in-the-lab.blogspot.com/search/label/Python" title="Python in the Lab">Hernan Grecco</a> </li> <li><a href="https://hilarymason.com" title="Hi. I'm Hilary Mason.">Hilary Mason</a> </li> <li><a href="http://holgerkrekel.net" title="metaprogramming and politics">Holger Krekel</a> </li> <li><a href="http://www.holger-peters.de/" title="Data Voyage - Blog of Holger Peters - Python Feed">Holger Peters</a> </li> <li><a href="https://blog.holoviz.org/" title="HoloViz Blog">HoloViz</a> </li> <li><a href="https://hugovk.dev/tags/python/" title="python on Hugo van Kemenade">Hugo van Kemenade</a> </li> <li><a href="https://humberto.io/tags/python/" title="Python on Humberto Rocha">Humberto Rocha</a> </li> <li><a href="https://hynek.me/" title="Hynek Schlawack">Hynek Schlawack</a> </li> <li><a href="https://ianozsvald.com" title="Python – Entrepreneurial Geekiness">Ian Ozsvald</a> </li> <li><a href="http://www.ilian.io/feed/" title="Between engineering and real life">Ilian Iliev</a> </li> <li><a href="http://importpython.com/blog/feed/" title="ImportPython Blog">Import Python</a> </li> <li><a href="https://www.inspiredpython.com/" title="Inspired Python">Inspired Python</a> </li> <li><a href="https://blog.ionelmc.ro/" title="ionel's codelog - python">Ionel Cristian Maries</a> </li> <li><a href="http://ironpython-urls.blogspot.com/" title="IronPython URLs">IronPython-URLs</a> </li> <li><a href="https://islandtropicaman.com/wp" title="Python – IslandTropicaMan">IslandT</a> </li> <li><a href="http://fruch.github.io" title="- Miscellaneous">Israel Fruchter</a> </li> <li><a href="https://codewithoutrules.com/" title="Code Without Rules">Itamar Turner Trauring</a> </li> <li><a href="https://iximiuz.com/" title="Ivan Velichko on Containers, Kubernetes, and Backend Development">Ivan Velichko</a> </li> <li><a href="https://pablofernandez.tech" title="Python – Pablo Fernandez">J. Pablo Fernández</a> </li> <li><a href="http://jackdied.blogspot.com/" title="Jack Diederich's Python Blog">Jack Diederich</a> </li> <li><a href="https://streamhacker.com" title="python – StreamHacker">Jacob Perkins</a> </li> <li><a href="http://rahmonov.me/" title="Jahongir Rahmonov">Jahongir Rahmonov</a> </li> <li><a href="https://wrongsideofmemphis.com" title="english – Wrong Side of Memphis">Jaime Buelta</a> </li> <li><a href="https://datadependence.com" title="Data Dependence">Jamal Moir</a> </li> <li><a href="https://www.b-list.org/" title="James Bennett (b-list.org)">James Bennett</a> </li> <li><a href="https://janusworx.com/tags/planetpython/" title="Planetpython on Janusworx">Janusworx</a> </li> <li><a href="http://blog.jarrodmillman.com/search/label/python" title="Jarrod Millman">Jarrod Millman</a> </li> <li><a href="http://as.ynchrono.us/search/label/python" title="Interesting Things, Largely Python and Twisted Related">Jean-Paul Calderone</a> </li> <li><a href="http://jbisbee.blogspot.com/search/label/python" title="Change for the Better">Jeff Bisbee</a> </li> <li><a href="http://jeffbradberry.com/" title="Jeff Bradberry - python">Jeff Bradberry</a> </li> <li><a href="http://inre.dundeemt.com" title="Python – In Re:">Jeff Hinrichs</a> </li> <li><a href="http://griddlenoise.blogspot.com/search/label/python" title="Griddle Noise">Jeff Shell</a> </li> <li><a href="https://greenash.net.au/thoughts/topics/python/" title="python - GreenAsh">Jeremy Epstein</a> </li> <li><a href="http://jeremyhylton.blogspot.com/" title="Jeremy Hylton: Inconceivable">Jeremy Hylton</a> </li> <li><a href="http://j1mfulton.blogspot.com/" title="Jim Fulton">Jim Fulton</a> </li> <li><a href="https://pyrseas.wordpress.com" title="Taming Serpents and Pachyderms">Joe Abbate</a> </li> <li><a href="https://joepitz.wordpress.com" title="Python – Joe Pitz – Technology Blog">Joe Pitz</a> </li> <li><a href="https://blogs.gnome.org/johan/category/python/feed/atom/#respond" title="Comments on">Johan Dahlin</a> </li> <li><a href="http://clouddbs.blogspot.com/search/label/python" title="Python Cloud">John Burns</a> </li> <li><a href="https://www.johndcook.com/blog" title="Python | John D. Cook">John Cook</a> </li> <li><a href="http://eigenhombre.com" title="Forays into Simplicity - Python">John Jacobsen</a> </li> <li><a href="https://www.nbshare.io/feeds/all.atom.xml" title="Share Your R and Python Notebooks">John Ludhi/nbshare.io</a> </li> <li><a href="https://www.indelible.org/ink/" title="Indelible Ink">Jon Parise</a> </li> <li><a href="http://spyced.blogspot.com/search/label/python" title="Spyced">Jonathan Ellis</a> </li> <li><a href="http://blog.jonharrington.org/" title="python - Jonathan Harrington">Jonathan Harrington</a> </li> <li><a href="https://www.tartley.com/" title="Made out of meat (Posts about python)">Jonathan Hartley</a> </li> <li><a href="http://jonathanstreet.com/" title="Posts tagged python at jonathanstreet.com">Jonathan Street</a> </li> <li><a href="http://blog.jorgenschaefer.de/search/label/Python" title="Jorgen’s Weblog">Jorgen Schäfer</a> </li> <li><a href="https://pythonmonopoly.wordpress.com" title="Python Monopoly">Juan Manuel Contreras</a> </li> <li><a href="https://julien.danjou.info/" title="Python - Julien Danjou">Julien Danjou</a> </li> <li><a href="https://beauty-of-imagination.blogspot.com/search/label/python" title="Imagination">Julien Tayon</a> </li> <li><a href="https://juripakaste.fi" title="Juri Pakaste: Blog">Juri Pakaste</a> </li> <li><a href="http://blog.pythonisito.com/" title="Just a little Python">Just a little Python</a> </li> <li><a href="https://hackercodex.com/" title="Hacker Codex - python">Justin Mayer</a> </li> <li><a href="https://lautaportti.wordpress.com" title="Kai Lautaportti">Kai Lautaportti</a> </li> <li><a href="https://www.afternerd.com/blog" title="python – Afternerd">Karim Elghamrawy</a> </li> <li><a href="https://nuitka.net/" title="Nuitka Blog - Posts tagged Python">Kay Hayen</a> </li> <li><a href="http://fiber-space.de/wordpress" title="Python – Trails in a Langscape">Kay Schluehr</a> </li> <li><a href="http://kbyanc.blogspot.com/search/label/python" title="The Other Kelly Yancey">Kelly Yancey</a> </li> <li><a href="https://kodnito.com/categories/python/" title="Kodnito.com">Kodnito</a> </li> <li><a href="https://devblog.kogan.com/" title="Kogan.com Dev Blog">Kogan Dev</a> </li> <li><a href="http://konryd.blogspot.com/" title="Most recent call">Konrad Delong</a> </li> <li><a href="https://koodaamo.wordpress.com" title="python – Koodaamo">Koodaamo</a> </li> <li><a href="https://cosmicpercolator.com" title="Python – Kristján's Cosmic Percolator">Kristján Valur Jónsson</a> </li> <li><a href="http://justaworldaway.com" title="python – Just a World Away">Kriti Godey</a> </li> <li><a href="https://gofedora.com" title="Python – Tech Stuff">Kulbir Saini</a> </li> <li><a href="http://farmdev.com/thoughts/on/3/python/" title="Farmdev: Thoughts on Python">Kumar McMillan</a> </li> <li><a href="https://vipin711.wordpress.com" title="Python – Programming">Kumar Vipin Yadav</a> </li> <li><a href="https://kushaldas.in/" title="Kushal Das">Kushal Das</a> </li> <li><a href="http://charlesnagy.info" title="python | Charles Nagy">Károly Nagy</a> </li> <li><a href="https://www.laac.dev/tags/python/" title="python | LAAC Technology">LAAC Technology</a> </li> <li><a href="" title="Reacties op:">Laurent Szyster</a> </li> <li><a href="https://hypatia.ca" title="python – hypatia dot ca">Leigh Honeywell</a> </li> <li><a href="https://regebro.wordpress.com" title="python – Lennart Regebro: Python, Plone, Web">Lennart Regebro</a> </li> <li><a href="https://leovt.wordpress.com" title="Python – leovt">Leonhard Vogt</a> </li> <li><a href="https://howto.lintel.in" title="python – Lintel Technologies Blog">Lintel Technologies</a> </li> <li><a href="https://linuxstans.com" title="python – Linux Stans">Linux Stans</a> </li> <li><a href="https://www.listendata.com/search/label/Python" title="ListenData">ListenData</a> </li> <li><a href="https://www.logilab.org/blog/6056?amp=&amp;amp=&amp;rql=Any%20E%20ORDERBY%20D%20DESC%20LIMIT%2020%20WHERE%20E%20is%20BlogEntry%2C%20E%20entry_of%20X%2C%20X%20eid%206056%2C%20E%20creation_date%20D&amp;vtitle=Main%20Blog%20%28in%20English%29" title="Main Blog (in English) RSS Feed">Logilab</a> </li> <li><a href="https://blog.lowkster.com/" title="Lowkster - Pythonly yours">Low Kian Seong</a> </li> <li><a href="http://codingandlinux.blogspot.com/search/label/python" title="Coding and Linux">Luca Botti</a> </li> <li><a href="" title="Liens utiles et à partager">Lucas Cimon</a> </li> <li><a href="http://blog.gmludo.eu/search/label/python" title="Ludo">Ludovic Gasc</a> </li> <li><a href="http://blog.sendapatch.se" title="blog.sendapatch.se">Ludvig Ericson</a> </li> <li><a href="https://lukeplant.me.uk/blog/categories/python/" title="Luke Plant's home page (Posts about Python)">Luke Plant</a> </li> <li><a href="http://lostinjit.blogspot.com/" title="Lost in JIT">Maciej Fijalkowsk</a> </li> <li><a href="https://madewith.mu/" title="Made With Mu">Made With Mu</a> </li> <li><a href="http://sedimental.org/tagged/python/" title="Sedimental - python">Mahmoud Hashemi</a> </li> <li><a href="https://maltheborch.com/rss?tags=python" title="Malthe's Homepage: Software Engineering and Architecture, Data Processing and Analytics A Blog About Computer Systems">Malthe Borch</a> </li> <li><a href="https://www.marc-richter.info" title="Planet Python Articles – Marc Richter's personal site">Marc Richter</a> </li> <li><a href="https://www.malemburg.com/" title="All Things Python">Marc-André Lemburg</a> </li> <li><a href="https://www.grulic.org.ar/~mdione/glob/" title=".:: Marcos Dione/StyXman's glob ::. (Posts about python)">Marcos Dione</a> </li> <li><a href="https://mariatta.ca/tags/python/" title="Python on Mariatta">Mariatta</a> </li> <li><a href="https://mg.pov.lt/blog/" title="Random notes from mg">Marius Gedminas</a> </li> <li><a href="http://shed-skin.blogspot.com/" title="Shed Skin - A (restricted) Python-to-C++ Compiler">Mark Dufour</a> </li> <li><a href="https://blogs.gnome.org/markmc" title="Comments on:">Mark McLoughlin</a> </li> <li><a href="http://pywinauto.blogspot.com/" title="mark.py">Mark McMahon</a> </li> <li><a href="http://www.zopatista.com" title="Zopatista">Martijn Pieters</a> </li> <li><a href="https://blog.martinfitzpatrick.com/" title="Martin Fitzpatrick - python">Martin Fitzpatrick</a> </li> <li><a href="http://mysqlmusings.blogspot.com/search/label/python" title="MySQL Musings">Mats Kindahl</a> </li> <li><a href="https://www.mattlayman.com/categories/python/" title="Python on Matt Layman">Matt Layman</a> </li> <li><a href="https://matthewrocklin.com/blog/" title="Working notes by Matthew Rocklin - Python">Matthew Rocklin</a> </li> <li><a href="https://stealthcopter.com/blog" title="python – Stealthcopter">Matthew Rollings</a> </li> <li><a href="https://blog.tplus1.com" title="Comments on:">Matthew Wilson</a> </li> <li><a href="https://www.wrighters.io/category/python/" title="Python Archives - wrighters.io">Matthew Wright</a> </li> <li><a href="http://copypasteprogrammer.blogspot.com/search/label/python" title="The copy/paste programmer">Mattias Brändström</a> </li> <li><a href="http://mauveweb.co.uk/" title="Mauveweb">Mauveweb</a> </li> <li><a href="http://beckerfuffle.com/" title="Category: python | Beckerfuffle">Michael Becker</a> </li> <li><a href="http://droettboom.com/" title="Boom! Michael Droettboom's blog">Michael Droettboom</a> </li> <li><a href="https://agileabstractions.com/" title="Agile Abstractions: Michael Foord">Michael Foord</a> </li> <li><a href="https://micknelson.wordpress.com" title="python – Something-driven development">Michael Nelson</a> </li> <li><a href="https://mousebender.wordpress.com" title="Mousebender">Michal Kwiatkowski</a> </li> <li><a href="https://bultrowicz.com" title="Butlablog - Posts tagged Python">Michał Bultrowicz</a> </li> <li><a href="http://www.artima.com/weblogs/index.jsp?blogger=micheles" title="Michele Simionato's Weblog">Michele Simionato</a> </li> <li><a href="http://firsttimeprogrammer.blogspot.com/search/label/python" title="The Beginner Programmer">Michy Alice</a> </li> <li><a href="http://blog.vrplumber.com/b/categories/snaking/" title="blog.vrplumber.com - Entries for the category Snaking">Mike C. Fletcher</a> </li> <li><a href="https://www.blog.pythonlibrary.org/" title="Mouse Vs Python">Mike Driscoll</a> </li> <li><a href="http://python-academy.blogspot.com/" title="Python Academy">Mike Müller</a> </li> <li><a href="http://kmike.ru/" title="kmike.ru">Mikhail Korobov</a> </li> <li><a href="https://opensourcehacker.com" title="python – Open Source Hacker">Mikko Ohtamaa</a> </li> <li><a href="https://mirekdlugosz.com/" title="Mirek Długosz personal website - planet Python">Mirek Długosz</a> </li> <li><a href="http://www.elastician.com/" title="Elastician">Mitchell Garnaat</a> </li> <li><a href="http://pythonbyexample.blogspot.com/" title="PythonByExample">Mitya Sirenef</a> </li> <li><a href="https://montrealpython.org/feed/" title="Montréal-Python">Montreal Python User Group</a> </li> <li><a href="https://orbifold.xyz/" title="Orbifolds and Other Games">Moshe Zadka</a> </li> <li><a href="https://www.moyaproject.com/blog/" title="Moya Project Blog">Moya Project</a> </li> <li><a href="https://blog.mozilla.org/webdev/category/python-2/" title="Python Archives - Mozilla Web Development">Mozilla Web Development</a> </li> <li><a href="https://muharem.wordpress.com" title="Muharem Hrnjadovic">Muharem Hrnjadovic</a> </li> <li><a href="https://www.mycli.net/" title="mycli - python">Mycli</a> </li> <li><a href="http://www.thesamet.com" title="python – Nadav Samet's Blog">Nadav Samet</a> </li> <li><a href="https://learnpython.wordpress.com" title="Learn Python">Naomi Ceder</a> </li> <li><a href="http://www.natan.termitnjak.net//blog" title="NMB's Development Blog">Natan Zabkar</a> </li> <li><a href="https://nedbatchelder.com/blog" title="Ned Batchelder's blog">Ned Batchelder</a> </li> <li><a href="http://python.ca/nas/log/" title="Neil Schemenauer's Web Log">Neil Schemenauer</a> </li> <li><a href="https://www.curiousefficiency.org" title="Curious Efficiency (Posts about python)">Nick Coghlan</a> </li> <li><a href="http://www.craig-wood.com/nick/articles" title="Nick Craig-Wood's Articles">Nick Craig-Wood</a> </li> <li><a href="http://blog.efford.org/" title="Pythoneering">Nick Efford</a> </li> <li><a href="http://nickjanetakis.blogspot.com/search/label/python" title="Nick Janetakis">Nick Janetakis</a> </li> <li><a href="https://nicolaiarocci.com/tags/python/" title="Python on Nicola Iarocci">Nicola Iarocci</a> </li> <li><a href="https://nicdumz.fr/blog" title="Nicolas Dumazet - Blog">Nicolas Dumazet</a> </li> <li><a href="https://nicosphere.net" title="nicosphere.net">Nicolas Paris</a> </li> <li><a href="https://nigelb.me" title="python – Nigel Babu">Nigel Babu</a> </li> <li><a href="https://getnikola.com/" title="Nikola">Nikola</a> </li> <li><a href="http://www.nikos7am.com/" title="My Python Blog">Nikolaos Diamantis</a> </li> <li><a href="https://dev.nextthought.com/blog/categories/python.html" title="Not Invented Here (Posts about python)">Not Invented Here</a> </li> <li><a href="https://nskm.xyz/tags/python/" title="python on Nskm">Nsukami Patrick</a> </li> <li><a href="http://www.obeythetestinggoat.com/" title="Obey the Testing Goat!">Obey the Testing Goat</a> </li> <li><a href="https://ofosos.org/" title="python - ofosos.org">Ofosos</a> </li> <li><a href="http://www.omahapython.org/blog" title="Omaha Python Users Group">Omaha Python Users Group</a> </li> <li><a href="http://ondrejcertik.blogspot.com/search/label/python" title="Ondřej Čertík">Ond&#345;ej &#268;ert&iacute;k</a> </li> <li><a href="https://www.paulox.net/" title="Paolo Melchiorre - Python 🐍">Paolo Melchiorre</a> </li> <li><a href="http://blog.pathwright.com/" title="Pathwright Blog">Pathwright</a> </li> <li><a href="https://weblog.patrice.ch/404.html" title="Patrice's Weblog">Patrice Neff</a> </li> <li><a href="https://www.patricksoftwareblog.com" title="python – Patrick's Software Blog">Patrick Kennedy</a> </li> <li><a href="https://pauleveritt.wordpress.com" title="Chatterbox, Reloaded">Paul Everitt</a> </li> <li><a href="https://logarithmic.net/pfh/blog_code" title="pfh's blog: code section">Paul Harrison</a> </li> <li><a href="https://codebright.wordpress.com" title="Python – Codebright's Blog">Paul Redman</a> </li> <li><a href="https://pfertyk.me/" title="Paweł Fertyk - python">Paweł Fertyk</a> </li> <li><a href="http://pedro.valelima.com" title="Underwater log feed for python">Pedro Lima</a> </li> <li><a href="http://www.peterbe.com/rss.xml" title="Peterbe.com">Peter Bengtsson</a> </li> <li><a href="http://petereisentraut.blogspot.com/search/label/Python" title="Peter Eisentraut's Blog">Peter Eisentraut</a> </li> <li><a href="https://neovox.advancedmagic.de" title="Python – Advanced Magic">Peter Fankhänel</a> </li> <li><a href="https://push.cx" title="Python – push.cx">Peter Harkins</a> </li> <li><a href="http://peter-hoffmann.com" title="Peter Hoffmann - Python">Peter Hoffmann</a> </li> <li><a href="https://www.philhassey.com/blog" title="python – Phil Hassey">Phil Hassey</a> </li> <li><a href="http://dunderboss.blogspot.com/search/label/python" title="__boss__">Philip Jenvey</a> </li> <li><a href="https://philikon.wordpress.com" title="Python – philiKON – a journal">Philipp von Weitershausen</a> </li> <li><a href="https://base-art.net/" title="Base-Art - Philippe Normand">Philippe Normand</a> </li> <li><a href="https://dirtsimple.org" title="Software Development – dirtSimple.org">Phillip J. Eby</a> </li> <li><a href="https://www.pythonpodcast.com" title="The Python Podcast.__init__">Podcast.__init__</a> </li> <li><a href="https://masnun.com/category/python" title="Python Archives - Abu Ashraf Masnun">Polyglot.Ninja()</a> </li> <li><a href="http://blog.ironboundsoftware.com" title="Possibility and Probability">Possbility and Probability</a> </li> <li><a href="http://pradeepgowda.com" title="pradeepgowda.com">Pradeep Gowda</a> </li> <li><a href="http://flicker-technical.blogspot.com/search/label/Python" title="Hit n Trail">Pranav Pandey</a> </li> <li><a href="http://www.shutupandship.com/search/label/python" title="Shut Up and Ship">Praveen Gollakota</a> </li> <li><a href="https://www.programiz.com/python-programming/rss.xml" title="Python feed">Programiz</a> </li> <li><a href="https://programmingideaswithjake.wordpress.com" title="Python – Programming Ideas With Jake">Programming Ideas With Jake</a> </li> <li><a href="https://eshlox.net" title="Articles tagged as python">Przemysław Kołodziejczyk</a> </li> <li><a href="https://pyatl.dev/" title="PyATL news">PyATL Bytecode</a> </li> <li><a href="https://pybit.es" title="Pybites">PyBites</a> </li> <li><a href="https://blog.jetbrains.com" title="PyCharm : The Python IDE for data science and web development | The JetBrains Blog">PyCharm</a> </li> <li><a href="https://pycoders.com/" title="PyCoder’s Weekly">PyCoder’s Weekly</a> </li> <li><a href="https://pycon.blogspot.com/" title="The PyCon US Blog">PyCon</a> </li> <li><a href="https://pypodcats.live/episodes/" title="Hidden Figures of Python Podcast">PyPodcats</a> </li> <li><a href="https://www.pypy.org/" title="PyPy">PyPy</a> </li> <li><a href="https://pytennessee.tumblr.com/" title="PyTennessee 2017">PyTennessee</a> </li> <li><a href="https://python4kids.brendanscott.com" title="Python Tutorials for Kids 13+">Python 4 Kids</a> </li> <li><a href="http://www.awaretek.com/plf.html" title="Python411">Python 411 Podcast</a> </li> <li><a href="http://python-advocacy.blogspot.com/" title="About Python Advocacy">Python Advocacy</a> </li> <li><a href="https://blog.pythonanywhere.com/" title="PythonAnywhere News">Python Anywhere</a> </li> <li><a href="https://pythonbytes.fm/" title="Python Bytes">Python Bytes</a> </li> <li><a href="https://www.distributedpython.com/" title="">Python Celery - Weekly Celery Tutorials and How-tos</a> </li> <li><a href="https://pythoncircle.com/feed/" title="PythonCircle.com: New article for Python programmers every week">Python Circle</a> </li> <li><a href="https://pythondata.com" title="Python Data">Python Data</a> </li> <li><a href="http://www.pythondiary.com/" title="Python Diary latest blog entries">Python Diary</a> </li> <li><a href="https://python.github.io/editorial-board/" title="Python Docs Editorial Board">Python Docs Editorial Board</a> </li> <li><a href="https://www.pythondoeswhat.com/" title="Python Does What?!?">Python Does What?!</a> </li> <li><a href="https://devblogs.microsoft.com/python/" title="Microsoft for Python Developers Blog">Python Engineering at Microsoft</a> </li> <li><a href="https://www.pythonguis.com/" title="Python GUIs">Python GUIs</a> </li> <li><a href="https://pythoninsider.blogspot.com/" title="Python Insider">Python Insider</a> </li> <li><a href="https://www.pythonmorsels.com/topics/" title="Python Morsels">Python Morsels</a> </li> <li><a href="https://pythonpeople.fm" title="Python People">Python People</a> </li> <li><a href="http://www.pyptug.org/" title="PYthon Piedmont Triad User Group">Python Piedmont Triad User Group</a> </li> <li><a href="https://www.pythonpool.com" title="Python Pool">Python Pool</a> </li> <li><a href="https://pyfound.blogspot.com/" title="Python Software Foundation News">Python Software Foundation</a> </li> <li><a href="https://sweetness.hmmz.org/" title="sweetness.hmmz.org - Python">Python Sweetness</a> </li> <li><a href="http://python-groups.blogspot.com/" title="Happenings in Python Usergroups">Python User Groups</a> </li> <li><a href="https://www.pythonforbeginners.com" title="PythonForBeginners.com">Python for Beginners</a> </li> <li><a href="http://python-karan.blogspot.com/" title="Python on Karan">Python on Karan</a> </li> <li><a href="https://developerblog.myo.com/" title="Python - The Lab">Python with Myo</a> </li> <li><a href="http://pythonxynews.blogspot.com/" title="Python(x,y)">Python(x,y) News</a> </li> <li><a href="https://pythonclub.com.br/" title="PythonClub">PythonClub - A Brazilian collaborative blog about Python</a> </li> <li><a href="https://pythondebugging.com" title="Python Debugging">PythonDebugging.com</a> </li> <li><a href="https://coady.github.io/" title="Pythonicity">Pythonicity</a> </li> <li><a href="http://pythonology.blogspot.com/" title="Pythonology">Pythonology</a> </li> <li><a href="https://pythonspeed.com/" title="Python⇒Speed">Python⇒Speed</a> </li> <li><a href="http://ptspts.blogspot.com/search/label/planet-python" title="pts.blog">Péter Szabó</a> </li> <li><a href="http://blog.zsoldosp.eu" title="Do. Reflect. Learn. Repeat!">Péter Zsoldos</a> </li> <li><a href="https://labs.quansight.org" title="Quansight Labs">Quansight Labs Blog</a> </li> <li><a href="http://www.bitdance.com/blog" title="Dancing With the Bits">R David Murray</a> </li> <li><a href="https://blog.rmotr.com/tagged/python?source=rss----2e274f91448--python" title="Python in rmotr.com on Medium">RMOTR</a> </li> <li><a href="http://threebean.org/blog" title="[three]Bean">Ralph Bean</a> </li> <li><a href="https://blog.ram.rachum.com/" title="Ram's blog">Ram Rachum</a> </li> <li><a href="https://blog.randell.ph" title="Python – Randell's Blog">Randell Benavidez</a> </li> <li><a href="http://randlet.com/blog/rss/" title="Randle Taylor's Blog Feed">Randle Taylor</a> </li> <li><a href="http://randyzwitch.com" title="randyzwitch.com">Randy Zwitch</a> </li> <li><a href="https://rhettinger.wordpress.com" title="Deep Thoughts by Raymond Hettinger">Raymond Hettinger</a> </li> <li><a href="https://blog.readthedocs.com" title="Read the Docs Blog - Posts tagged python">Read the Docs</a> </li> <li><a href="https://realpython.com/" title="Real Python">Real Python</a> </li> <li><a href="https://developers.redhat.com/blog" title="Python – Red Hat Developer">Red Hat Developers</a> </li> <li><a href="http://renesd.blogspot.com/search/label/python" title="making apps, making webs.">Rene Dudfield</a> </li> <li><a href="https://lerner.co.il/category/python/" title="Python Archives — Reuven Lerner">Reuven Lerner</a> </li> <li><a href="http://posted-stuff.blogspot.com/search/label/python" title="Stuff What I Posted">Richard Tew</a> </li> <li><a href="http://blog.the-moon.net/search/label/python" title="The Moon Research Blog">Richard Wall</a> </li> <li><a href="http://rickardlindberg.me" title="Rickard's personal homepage: latest posts tagged python">Rickard Lindberg</a> </li> <li><a href="https://cmlzagk.github.io/" title="cmlzaGk - Python">Rishi Maker</a> </li> <li><a href="https://www.robg3d.com" title="Page not found – RobG3d">Rob Galanakis</a> </li> <li><a href="https://rbtcollins.wordpress.com" title="Python – Code happens">Robert Collins</a> </li> <li><a href="http://blog.zaremba.ch/" title="Robert Zaremba Scale it blog - python posts">Robert Zaremba</a> </li> <li><a href="https://www.theatreofnoise.com/search/label/dev" title="theatre of noise">Robin Parmar</a> </li> <li><a href="https://blog.rtwilson.com" title="Python – Robin's Blog">Robin Wilson</a> </li> <li><a href="https://linil.wordpress.com" title="">Rodrigo Araúj</a> </li> <li><a href="" title="python Archives - RoseHosting">RoseHosting Blog</a> </li> <li><a href="https://ruslanspivak.com/" title="Ruslan's Blog">Ruslan Spivak</a> </li> <li><a href="https://www.asciiarmor.com/" title="AsciiArmor">Ryan Cox</a> </li> <li><a href="https://slott-softwarearchitect.blogspot.com/search/label/python" title="S.Lott-Software Architect">S. Lott</a> </li> <li><a href="https://pyhelper.wordpress.com" title="Python Helper">S. R. Krishnan</a> </li> <li><a href="http://www.sdjournal.com/archives/categories/languages/python/" title="SDJournal posts in Python category">SDJournal</a> </li> <li><a href="http://pythonide.blogspot.com/" title="SPE IDE - Stani's Python Editor">SPE Weblog</a> </li> <li><a href="https://www.stxnext.com/blog" title="STX Next 2020 Blog">STX Next</a> </li> <li><a href="https://salimfadhley.wordpress.com" title="Salim Fadhley">Salim Fadhley</a> </li> <li><a href="https://sandipanweb.wordpress.com" title="Python – sandipanweb">Sandipan Dey</a> </li> <li><a href="http://sandrotosi.blogspot.com/search/label/Python" title="Sandro Tosi">Sandro Tosi</a> </li> <li><a href="http://seanmcgrath.blogspot.com/search/label/python" title="Sean McGrath">Sean McGrath</a> </li> <li><a href="https://k-d-w.org/tags/python/" title="python | Sebastian Pölsterl">Sebastian Pölsterl</a> </li> <li><a href="https://switowski.com" title="Sebastian Witowski">Sebastian Witowski</a> </li> <li><a href="https://www.chesnok.com/daily" title="python">Selena Deckelmann</a> </li> <li><a href="http://www.xtoinfinity.com/" title="Senthil Kumaran (Posts about python)">Senthil Kumaran</a> </li> <li><a href="http://sethmlarson.dev/" title="Seth Larson">Seth Michael Larson</a> </li> <li><a href="https://www.jjinux.com/search/label/python" title="JJinuxLand">Shannon -jj Behrens</a> </li> <li><a href="http://shiningpanda.com/" title="ShiningPanda">ShiningPanda</a> </li> <li><a href="http://simeonfranklin.com/blog/" title="simeonfranklin.com python feed">Simeon Franklin</a> </li> <li><a href="http://simeonvisser.com/" title="Simeon Visser (python)">Simeon Visser</a> </li> <li><a href="" title="aboutsimon.com">Simon</a> </li> <li><a href="http://www.brunningonline.net/simon/blog/" title="Small Values of Cool">Simon Brunning</a> </li> <li><a href="http://entitycrisis.blogspot.com/search/label/Python" title="Entity Crisis">Simon Wittber</a> </li> <li><a href="https://simpleisbetterthancomplex.com/" title="Simple is Better Than Complex">Simple is Better Than Complex</a> </li> <li><a href="https://www.softformance.com" title="python Archives - SOFTFORMANCE">SoftFormance</a> </li> <li><a href="https://brmmm3.github.io/" title="Speed matters blog">Speed Matters</a> </li> <li><a href="https://spikeekips.tumblr.com/" title="나는겁쟁이">Spike ekipS</a> </li> <li><a href="https://www.spyder-ide.org/blog" title="Welcome to Spyder's Blog">Spyder IDE</a> </li> <li><a href="https://stackabuse.com/" title="Stack Abuse">Stack Abuse</a> </li> <li><a href="https://smorbieu.gitlab.io/" title="Stanislas Morbieu - Python">Stanislas Morbieu</a> </li> <li><a href="https://www.starzel.de/blog/starzel-de-python-blog" title="Starzel.de Python Blog">Starzel.de</a> </li> <li><a href="http://blog.behnel.de/" title="Stefans Welt (Posts about Planet Python)">Stefan Behnel</a> </li> <li><a href="https://stefan.sofa-rockers.org/" title="Stefan Scherfke - python">Stefan Scherfke</a> </li> <li><a href="https://stefaniemolin.com/tags/Python" title="Stefanie Molin's Python articles">Stefanie Molin</a> </li> <li><a href="https://jod.al" title="jod.al - python">Stein Magnus Jodal</a> </li> <li><a href="https://pythonconquerstheuniverse.wordpress.com" title="Python Conquers The Universe">Stephen Ferg</a> </li> <li><a href="http://holdenweb.blogspot.com/search/label/python" title="For Some Value of &quot;Magic&quot;">Steve Holden</a> </li> <li><a href="http://rh0dium.blogspot.com/search/label/python" title="My Life">Steven Klass</a> </li> <li><a href="http://www.turingfinance.com" title="Python">Stuart Gordon Reid</a> </li> <li><a href="https://wirtel.be/tags/python/" title="python on Stéphane's Blog">Stéphane Wirtel</a> </li> <li><a href="https://www.harihareswara.net/posts/python/" title="Cogito, Ergo Sumana tag: Python">Sumana Harihareswara - Cogito, Ergo Sumana</a> </li> <li><a href="https://www.tutlane.com/tutorial/python" title="Python Tutorial - Tutlane">Suresh Dasari/Tutlane.com</a> </li> <li><a href="https://ict.swisscom.ch" title="Python – Swisscom ICT">Swisscom ICT</a> </li> <li><a href="https://talkpython.fm/" title="Talk Python To Me">Talk Python to Me</a> </li> <li><a href="https://blog.tedmiston.com/" title="Python - Taylor D. Edmiston">Taylor Edmiston</a> </li> <li><a href="https://techbeamers.com" title="Python Tutorials – TechBeamers">TechBeamers Python</a> </li> <li><a href="https://www.techiediaries.com" title="Techiediaries">Techiediaries - Django</a> </li> <li><a href="https://terriko.dreamwidth.org/" title="terriko">Terri Oda</a> </li> <li><a href="https://terry.fluidinfo.com" title="python – Terry Jones">Terry Jones</a> </li> <li><a href="https://testandcode.com" title="Test &amp;amp; Code">Test and Code</a> </li> <li><a href="https://testdriven.io/" title="TestDriven.io Blog Feed - Django">TestDriven.io</a> </li> <li><a href="http://www.thedatascientist.de" title="The Data Scientist » python">The Data Scientist</a> </li> <li><a href="https://www.thedigitalcatonline.com/" title="The Digital Cat - Python">The Digital Cat</a> </li> <li><a href="https://lunarcowboy.com/" title="The Lunar Cowboy - Python">The Lunar Cowboy</a> </li> <li><a href="https://no-title.victordomingos.com/" title="The No Title® Tech Blog - python">The No Title® Tech Blog</a> </li> <li><a href="http://blog.dowski.com/" title="The Occasional Occurence">The Occasional Occurrence</a> </li> <li><a href="https://fortintam.com/blog" title="Planet Python – The Open Sourcerer">The Open Sourcerer</a> </li> <li><a href="http://blog.parcon.opengroove.org/" title="The Parcon Blog">The Parcon Blog</a> </li> <li><a href="https://thepythoncodingbook.com/" title="The Python Coding Book">The Python Coding Blog</a> </li> <li><a href="http://pythonpapers.blogspot.com/" title="The Python Papers">The Python Papers</a> </li> <li><a href="https://www.pythonshow.com" title="The Python Show">The Python Show</a> </li> <li><a href="https://threeofwands.com/" title="python - The Three of Wands">The Three of Wands</a> </li> <li><a href="https://tibonihoo.net/" title="tlog (Posts about python)">Thibauld Nion</a> </li> <li><a href="https://thishosting.rocks" title="python – ThisHosting.Rocks">ThisHosting.Rocks</a> </li> <li><a href="https://wordaligned.org" title="Word Aligned">Thomas Guest</a> </li> <li><a href="http://thomas.apestaart.org/log" title="Python – thomas.apestaart.org">Thomas Vander Stichele</a> </li> <li><a href="https://www.tibobeijen.nl/tags/python/" title="Python on TBNL">Tibo Beijen</a> </li> <li><a href="https://reachtim.com/" title="ReachTim - Python">Tim Arnold / reachtim</a> </li> <li><a href="https://timgilbert.wordpress.com" title="Python – Tim Gilbert's Blog">Tim Gilbert</a> </li> <li><a href="https://kokorice.org/" title="Fresh from the plantation">Tim Knapp</a> </li> <li><a href="https://www.timlesher.com/search/label/python" title="Aftermarket Pipes">Tim Lesher</a> </li> <li><a href="http://journal.thobe.org/" title="Wardrobe strength">Tobias Ivarsson</a> </li> <li><a href="https://thewebhaswon.wordpress.com" title="python – The Rising Tide">Tom Christie</a> </li> <li><a href="http://sys-exit.blogspot.com/search/label/python" title="import sys / sys.exit()">Tomasz Ducin</a> </li> <li><a href="https://www.tomaz.me" title="tomaz.me - Tag: python">Tomaž Muraus</a> </li> <li><a href="https://tomerfiliba.com/blog" title="tomerfiliba.com | Python Blog">Tomer Filiba</a> </li> <li><a href="https://tonybreyal.wordpress.com" title="Python – Consistently Infrequent">Tony Breyal</a> </li> <li><a href="https://anonbadger.wordpress.com" title="Python – The Ramblings">Toshio Kuratomi</a> </li> <li><a href="http://technicaldiscovery.blogspot.com/search/label/python" title="Technical Discovery">Travis Oliphant</a> </li> <li><a href="https://treyhunner.com/" title="Trey Hunner - Python">Trey Hunner</a> </li> <li><a href="https://discuss.tryton.org/c/news/25" title="News - Tryton Discussion">Tryton News</a> </li> <li><a href="https://www.turnkeylinux.org/taxonomy/term/103/0" title="python">Turnkey Linux</a> </li> <li><a href="https://labs.twistedmatrix.com/" title="Twisted Matrix Laboratories">Twisted Matrix Labs</a> </li> <li><a href="https://typethepipe.com/categories/python/" title="Python | TypeThePipe">TypeThePipe</a> </li> <li><a href="http://vsbabu.org/mt/" title="vsbabu.org">V.S. Babu</a> </li> <li><a href="https://prayogshala.wordpress.com" title="Python – PrayogShala">Varun Nischal</a> </li> <li><a href="http://jugad2.blogspot.com/search/label/python" title="jugad2 - Vasudev Ram on software innovation">Vasudev Ram</a> </li> <li><a href="http://pymolurus.blogspot.com/" title="Python molurus (the Indian Python)">Vinay Sajip</a> </li> <li><a href="http://plumberjack.blogspot.com/" title="Plumber Jack">Vinay Sajip (Logging)</a> </li> <li><a href="https://vinayak.io/" title="Vinayak Mehta - python">Vinayak Mehta</a> </li> <li><a href="http://www.hardcoded.net/articles/" title="Hardcoded Software articles (python)">Virgil Dupras</a> </li> <li><a href="https://vperic.blogspot.com/search/label/Twisted" title="Vlada's technical blog">Vladimir Perić</a> </li> <li><a href="http://pieceofpy.com" title="Piece Of Py(thon)">Wayne Witzel</a> </li> <li><a href="https://www.pythonchat.com/" title="Python Chat">Weekly Python Chat</a> </li> <li><a href="http://python-weekly.blogspot.com/" title="# python weekly reports">Weekly Python StackOverflow Report</a> </li> <li><a href="https://1stvamp.org/tag/python" title="1stvamp.org">Wes Mason</a> </li> <li><a href="http://wescpy.blogspot.com/search/label/python" title="Core Python Programming">Wesley Chun</a> </li> <li><a href="https://bluesock.org/~willkg/blog/" title="Will's Blog (Posts about python)">Will Kahn-Greene</a> </li> <li><a href="https://www.willmcgugan.com/blog/tech/" title="Tech">Will McGugan</a> </li> <li><a href="http://willpython.blogspot.com/" title="Will's Python Notebook">Will Pierce</a> </li> <li><a href="https://blog.minchin.ca/" title="Minchin.ca - Python">William Minchin</a> </li> <li><a href="http://foolish-assertions.blogspot.com/search/label/python" title="Foolish Assertions">William Reade</a> </li> <li><a href="https://wingware.com/" title="Wing Tips">Wing Tips</a> </li> <li><a href="https://wingware.com/" title="Wingware News">Wingware</a> </li> <li><a href="https://wyattbaldwin.com/" title="Wyatt Baldwin - Category - planet python">Wyatt Baldwin</a> </li> <li><a href="https://tech.blog.aknin.name" title="python – NIL: .to write(1) ~ help:about">Yaniv Aknin</a> </li> <li><a href="http://www.ylarrivee.com" title="Yann Larrivée » python">Yann Larrivée</a> </li> <li><a href="https://uberpython.wordpress.com" title="python – Ubershmekel's Uberpython Pythonlog">Yuval Greenfield</a> </li> <li><a href="http://za.github.io/" title="Aurora Borealis">Zaki Akhmad</a> </li> <li><a href="https://zato.io/en/blog" title="Python API Integration Insights with Zato">Zato Blog</a> </li> <li><a href="https://zerotomastery.io" title="Zero To Mastery's Python RSS Feed">Zero to Mastery</a> </li> <li><a href="https://zerowithdot.com/" title="Zero-with-Dot Blog">Zero-with-Dot (Oleg Żero)</a> </li> <li><a href="https://medium.com/@ZeroDB_?source=rss-a0aa5238b2d8------2" title="Stories by ZeroDB on Medium">ZeroDB</a> </li> <li><a href="http://blog.bottlepy.org/" title="bottlepy-dev">bottlepy-dev</a> </li> <li><a href="" title="codeboje - Guiding developers since 1869 - Guaranteed Bullshit-Free by Tag python">codeboje</a> </li> <li><a href="https://death.andgravity.com/" title="death and gravity #python">death and gravity</a> </li> <li><a href="https://www.egenix.com/company/news" title="eGenix.com News &amp;amp; Events">eGenix.com</a> </li> <li><a href="http://hypothesis.works/articles/python/" title="Hypothesis articles tagged &quot;python&quot;">hypothesis.works articles</a> </li> <li><a href="http://scummos.blogspot.com/search/label/kdev-python" title="Linux, Games, Programming, and some other random stuff">kdev-python</a> </li> <li><a href="https://meejah.ca/" title="meejah.ca">meejah.ca</a> </li> <li><a href="http://nl-project.blogspot.com/search/label/python" title="nl-project">nl-project</a> </li> <li><a href="https://www.pgcli.com/" title="pgcli - python">pgcli</a> </li> <li><a href="http://py.checkio.org/blog/" title="Py.CheckiO Blog">py.CheckIO</a> </li> <li><a href="https://www.pygame.org/" title="pygame news">pygame</a> </li> <li><a href="http://pythonwise.blogspot.com/search/label/python" title="PythonWise">pythonwise</a> </li> <li><a href="https://blog.qutebrowser.org/" title="qutebrowser development blog - pyplanet">qutebrowser development blog</a> </li> <li><a href="https://recollection.saaj.me/" title="Recollection - python">saaj/recollection</a> </li> <li><a href="https://blog.scikit-learn.org/" title="scikit-learn Blog">scikit-learn</a> </li> <li><a href="https://testmon.org/" title="About testmon on Testmon">testmon</a> </li> <li><a href="https://tryexceptpass.org/" title="tryexceptpass">tryexceptpass</a> </li> <li><a href="https://wokslog.wordpress.com" title="python – Merwok’s System Log">Éric Araujo</a> </li> <li><a href="https://lukasz.langa.pl/python/" title="#Python - Łukasz Langa">Łukasz Langa</a> </li> <li> <i> To request addition or removal:<br /> Open an issue on <a href="https://github.com/python/planet/issues">github</a><br /> </i> </li> </ul></li> </ul> </div> </div> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10