Planet Python

Subscribe to Planet Python feed
Planet Python - http://planetpython.org/
Updated: 10 hours 47 min ago

PyBites: Failure does not exist, the obstacle is often the way

Wed, 2023-03-15 07:35

This content first appeared on our friends list, you can subscribe here.

I remember a long time ago I applied for a job and … I never heard back.

I applied for another job… rejected.

And for yet another job one of my former colleagues got it. 

As I had to deal with disappointment, I found it tough to not give up, to not to question myself. Luckily, I did get myself up after multiple “failures” and kept putting in the reps letting data trump emotion (by the way, I don’t believe in the word “failure”. We learn more from our failures than our successes!)

And after a while I did get a different job that, while I could not see it at the time, was strategically better and ended up changing my career.

Setbacks, disappointment, not meeting expectations. It can be very hard to deal with. We get rejected, ghosted, maybe even ridiculed, and we have to keep believing in ourselves, in spite of what seems the “evidence” that “this might not be for me”.

But here’s the thing. People that do keep at it, no matter what, end at the top. I know this is a cliche, but we’ve seen success story after success story where this has been the case. People were not handed position Y, they had to fight for it. These are people that often came from very humble beginnings.

So, this week we just want you to think about any disappointment you’ve had or might be going through right now and reflect on how it’s affecting your next steps.

And we’d like to ask you: What are you going to do next? Push, learn and grow? Or throw in the towel and give up?

The former will propel you forward, it’s what you can control. The latter is destructive and a downward spiral, getting you nowhere.

A few tips to better cope with this:

  • Take a short break. We have to process a lot and sometimes just letting things go for a bit makes all the difference. Even a good night of sleep, a weekend “offline” can totally change your perspective. Related podcast.
  • Find an accountability buddy to vent, share ideas and hold you to higher standards. Talk with other people, loneliness exacerbates the problem. Join our Slack community and engage with the great Pytonistas that have joined over the years. Related podcast.
  • Reflect on the situation and think about what you’ll do differently next time (because there will be a next time). A great tool is to keep a journal. Some people do it daily and say it greatly helps them reducing overall anxiety, but that daily habit can be hard, so at the very least do it when you’re going through tough times. On the flip side we encourage you to also note down your weekly wins (even “small” ones), this can be tremendously empowering. Related article.
  • Give back. Helping others is a great way to be less self centered while still building your portfolio with work you can show for / talk about. Doubts about your work? Externalizing the goal can be very motivating and again you’ll find accountability and other people to collaborate with.
  • Drop perfectionism. One setback does not ruin everything. Again, from failure you learn, and you often can still get back on track and finish strong. Often not everything is lost, it’s all about the averages and coming out stronger. Related podcast.

We hope these tips help you better handle setbacks.

– Bob and Julian

As a Python developer it can be tough to go at it alone, especially when disappointment and obstacles inevitably happen (be it with building apps, writing quality code, and finding a dev job or seeking a promotion).

Our PDM community is an incredibly supportive group of people that are often in a similar situation as you. The results people achieve both technically and mindset wise, boosts their confidence and resilience, and lifetime relations form, we’re really thrilled what our program has grown into. 

So, if you want to grow your career, better deal with the imposter syndrome we all face as developers, our PDM program is the place to make that life altering change.

Categories: FLOSS Project Planets

PyBites: 8 tips for succeeding in the software industry

Wed, 2023-03-15 07:25

Watch here:

Listen here:

Welcome back to the podcast. Today we share 8 tips in response to a question that we were tagged on @ Twitter:

  1. Communication is everything.
  2. Deliberate practice.
  3. Adopt a growth mindset.
  4. Be a generalist.
  5. Focus on the “compound movements”.
  6. Know the business domain you are in.
  7. Share your work / teach others.
  8. Network every single week.

As always we also discuss wins and books.

Links:

Mentioned books:

And last but not least thanks for all your feedback

You can reach out to us through our Slack or send an email to info at pybit dot es.

Categories: FLOSS Project Planets

Python GUIs: Handle command-line arguments in GUI applications with PyQt6

Wed, 2023-03-15 05:03

Sometimes you want to be able to pass command line arguments to your GUI applications. For example, you may want to be able to pass files which the application should open, or change the initial startup state.

In that case it can be helpful to be able to pass custom command-line arguments to your application. Qt supports this using the QCommandLineOption and QCommandLineParser classes, the first defining optional parameters to be identified from the command line, and the latter to actually perform the parsing.

In this short tutorial we'll create a small demo application which accepts arguments on the command line.

Building the Parser

We'll start by creating an outline of our application. As usual we create a QApplication instance, however, we won't initially create any windows. Instead, we construct our command-line parser function, which accepts our app instance and uses QCommandLineParser to parse the command line arguments.

python import sys from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import QCommandLineOption, QCommandLineParser def parse(app): """Parse the arguments and options of the given app object.""" parser = QCommandLineParser() parser.addHelpOption() parser.addVersionOption() parser.process(app) app = QApplication(sys.argv) app.setApplicationName("My Application") app.setApplicationVersion("1.0") parse(app) python import sys from PySide6.QtWidgets import QApplication from PySide6.QtCore import QCommandLineOption, QCommandLineParser def parse(app): """Parse the arguments and options of the given app object.""" parser = QCommandLineParser() parser.addHelpOption() parser.addVersionOption() parser.process(app) app = QApplication(sys.argv) app.setApplicationName("My Application") app.setApplicationVersion("1.0") parse(app)

We don't call app.exec() yet, to start the event loop. That's because we don't have any windows, so there would be no way to exit the app once it is started! We'll create the windows in a later step.

It is important to note that you must pass sys.argv to QApplication for the command line parser to work -- the parser processes the arguments from app. If you don't pass the arguments, there won't be anything to process.

With this outline structure in place we can move on to creating our command line options.

Adding Optional Parameters

We'll add two optional parameters -- the first to allow users to toggle the window start up as maximized, and the second to pass a Qt style to use for drawing the window.

The available styles are 'windows', 'windowsvista', 'fusion' and 'macos' -- although the platform specific styles are only available on their own platform. If you use macos on Windows, or vice versa, it will have no effect. However, 'fusion' can be used on all platforms.

python import sys from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import QCommandLineOption, QCommandLineParser QT_STYLES = ["windows", "windowsvista", "fusion", "macos"] def parse(app): """Parse the arguments and options of the given app object.""" parser = QCommandLineParser() parser.addHelpOption() parser.addVersionOption() maximize_option = QCommandLineOption( ["m", "maximize"], "Maximize the window on startup." ) parser.addOption(maximize_option) style_option = QCommandLineOption( "s", "Use the specified Qt style, one of: " + ', '.join(QT_STYLES), "style" ) parser.addOption(style_option) parser.process(app) app = QApplication(sys.argv) app.setApplicationName("My Application") app.setApplicationVersion("1.0") parse(app) app.exec() python import sys from PySide6.QtWidgets import QApplication from PySide6.QtCore import QCommandLineOption, QCommandLineParser QT_STYLES = ["windows", "windowsvista", "fusion", "macos"] def parse(app): """Parse the arguments and options of the given app object.""" parser = QCommandLineParser() parser.addHelpOption() parser.addVersionOption() maximize_option = QCommandLineOption( ["m", "maximize"], "Maximize the window on startup." ) parser.addOption(maximize_option) style_option = QCommandLineOption( "s", "Use the specified Qt style, one of: " + ', '.join(QT_STYLES), "style" ) parser.addOption(style_option) parser.process(app) app = QApplication(sys.argv) app.setApplicationName("My Application") app.setApplicationVersion("1.0") parse(app) app.exec()

The optional arguments are now in place. We'll now add positional arguments, which will be used to open specific files.

Adding Positional Arguments

Strictly speaking, positional arguments are any arguments not interpreted as optional arguments. You can define multiple positional arguments if you like but this is only used for help text display. You will still need to handle them yourself internally, and limit the number if necessary by throwing an error.

In our example we are specifying a single position argument file, but noting in the help text that you can provide more than one. There is no limit in our example -- if you pass more files, more windows will open.

python import sys from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import QCommandLineOption, QCommandLineParser QT_STYLES = ["windows", "windowsvista", "fusion", "macos"] def parse(app): """Parse the arguments and options of the given app object.""" parser = QCommandLineParser() parser.addHelpOption() parser.addVersionOption() parser.addPositionalArgument("file", "Files to open.", "[file file file...]") maximize_option = QCommandLineOption( ["m", "maximize"], "Maximize the window on startup." ) parser.addOption(maximize_option) style_option = QCommandLineOption( "s", "Use the specified Qt style, one of: " + ', '.join(QT_STYLES), "style" ) parser.addOption(style_option) parser.process(app) has_maximize_option = parser.isSet(maximize_option) app_style = parser.value(style_option) # Check for positional arguments (files to open). arguments = parser.positionalArguments() print("Has maximize option?", has_maximize_option) print("App style:", app_style) print("Arguments (files): ", arguments) app = QApplication(sys.argv) app.setApplicationName("My Application") app.setApplicationVersion("1.0") parse(app) app.exec() python import sys from PySide6.QtWidgets import QApplication from PySide6.QtCore import QCommandLineOption, QCommandLineParser QT_STYLES = ["windows", "windowsvista", "fusion", "macos"] def parse(app): """Parse the arguments and options of the given app object.""" parser = QCommandLineParser() parser.addHelpOption() parser.addVersionOption() parser.addPositionalArgument("file", "Files to open.", "[file file file...]") maximize_option = QCommandLineOption( ["m", "maximize"], "Maximize the window on startup." ) parser.addOption(maximize_option) style_option = QCommandLineOption( "s", "Use the specified Qt style, one of: " + ', '.join(QT_STYLES), "style" ) parser.addOption(style_option) parser.process(app) has_maximize_option = parser.isSet(maximize_option) app_style = parser.value(style_option) # Check for positional arguments (files to open). arguments = parser.positionalArguments() print("Has maximize option?", has_maximize_option) print("App style:", app_style) print("Arguments (files): ", arguments) app = QApplication(sys.argv) app.setApplicationName("My Application") app.setApplicationVersion("1.0") parse(app) app.exec()

We've added a series of print calls to display the arguments and options extracted by Qt's QCommandLineParser. If you run the application now you can experiment by passing different arguments and seeing the result on the command line.

For example --- with no arguments.

bash $ python command_line.py Has maximize option? False App style:

With -m maximize flag and a single file

bash $ python command_line.py -m example.txt Has maximize option? True App style: Arguments (files): ['example.txt']

With a single file and using Fusion style -- there is no window yet, so this will have effect yet!

bash $ python command_line.py -s fusion example.txt Has maximize option? False App style: fusion Arguments (files): ['example.txt']

With the argument handling in place, we can now write the remainder of the example.

Using the Arguments & Options

We'll be using a standard QPlainTextEdit widget as our file viewer. In Qt any widget without a parent is a window, so these editors will be floating independently on the desktop. If the -m flag is used we'll set these windows to be displayed maximized.

If windows are created, we'll need to start the Qt event loop to draw the windows and allow interaction with them. If no windows are created, we'll want to show the command-line help to help the user understand why nothing is happening! This will output the format of position and optional arguments that our app takes.

python import sys from PySide6.QtWidgets import QApplication, QPlainTextEdit from PySide6.QtCore import QCommandLineOption, QCommandLineParser QT_STYLES = ["windows", "windowsvista", "fusion", "macos"] windows = [] # Store references to our created windows. def parse(app): """Parse the arguments and options of the given app object.""" parser = QCommandLineParser() parser.addHelpOption() parser.addVersionOption() parser.addPositionalArgument("file", "Files to open.", "[file file file...]") maximize_option = QCommandLineOption( ["m", "maximize"], "Maximize the window on startup." ) parser.addOption(maximize_option) style_option = QCommandLineOption( "s", "Use the specified Qt style, one of: " + ', '.join(QT_STYLES), "style" ) parser.addOption(style_option) parser.process(app) has_maximize_option = parser.isSet(maximize_option) app_style = parser.value(style_option) if app_style and app_style in QT_STYLES: app.setStyle(app_style) # Check for positional arguments (files to open). arguments = parser.positionalArguments() # Iterate all arguments and open the files. for tfile in arguments: try: with open(tfile, 'r') as f: text = f.read() except Exception: # Skip this file if there is an error. continue window = QPlainTextEdit(text) # Open the file in a maximized window, if set. if has_maximize_option: window.showMaximized() # Keep a reference to the window in our global list, to stop them # being deleted. We can test this list to see whether to show the # help (below) or start the event loop (at the bottom). windows.append(window) if not windows: # If we haven't created any windows, show the help. parser.showHelp() app = QApplication(sys.argv) app.setApplicationName("My Application") app.setApplicationVersion("1.0") parse(app) if windows: # We've created windows, start the event loop. app.exec() python import sys from PySide6.QtWidgets import QApplication, QPlainTextEdit from PySide6.QtCore import QCommandLineOption, QCommandLineParser QT_STYLES = ["windows", "windowsvista", "fusion", "macos"] windows = [] # Store references to our created windows. def parse(app): """Parse the arguments and options of the given app object.""" parser = QCommandLineParser() parser.addHelpOption() parser.addVersionOption() parser.addPositionalArgument("file", "Files to open.", "[file file file...]") maximize_option = QCommandLineOption( ["m", "maximize"], "Maximize the window on startup." ) parser.addOption(maximize_option) style_option = QCommandLineOption( "s", "Use the specified Qt style, one of: " + ', '.join(QT_STYLES), "style" ) parser.addOption(style_option) parser.process(app) has_maximize_option = parser.isSet(maximize_option) app_style = parser.value(style_option) if app_style and app_style in QT_STYLES: app.setStyle(app_style) # Check for positional arguments (files to open). arguments = parser.positionalArguments() # Iterate all arguments and open the files. for tfile in arguments: try: with open(tfile, 'r') as f: text = f.read() except Exception: # Skip this file if there is an error. continue window = QPlainTextEdit(text) # Open the file in a maximized window, if set. if has_maximize_option: window.showMaximized() # Keep a reference to the window in our global list, to stop them # being deleted. We can test this list to see whether to show the # help (below) or start the event loop (at the bottom). windows.append(window) if not windows: # If we haven't created any windows, show the help. parser.showHelp() app = QApplication(sys.argv) app.setApplicationName("My Application") app.setApplicationVersion("1.0") parse(app) if windows: # We've created windows, start the event loop. app.exec()

The arguments are handled and processed as before however, now they actually have an effect!

Firstly, if the user passes the -s <style> option we will apply the specified style to our app instance -- first checking to see if it is one of the known valid styles.

python if app_style and app_style in QT_STYLES: app.setStyle(app_style)

Next we take the list of position arguments and iterate, creating a QPlainTextEdit window and displaying the text in it. If has_maximize_option has been set, these windows are all set to be maximized with window.showMaximized().

References to the windows are stored in a global list windows, so they are not cleaned up (deleted) on exiting the function. After creating windows we test to see if this is empty, and if not show the help:

bash $ python command_line.py Usage: command_line.py [options] [file file file...] Options: -?, -h, --help Displays help on commandline options. --help-all Displays help including Qt specific options. -v, --version Displays version information. -m, --maximize Maximize the window on startup. -s <style> Use the specified Qt style, one of: windows, windowsvista, fusion, macos Arguments: file Files to open.

If there are windows, we finally start up the event loop to display them and allow the user to interact with the application.

python if windows: # We've created windows, start the event loop. app.exec() Conclusion

In this short tutorial we've learned how to develop an app which accepts custom arguments and options on the command line, and uses them to modify the UI behavior. You can use this same approach in your own applications to provide command-line control over the behavior of your own applications.

For an in-depth guide to building GUIs with Python see my PySide6 book.

Categories: FLOSS Project Planets

Matt Layman: Sync or Async? Unpacking the Mysteries of Django Signals

Tue, 2023-03-14 20:00
Django is a popular web framework for Python developers, known for its robustness, flexibility, and security. One of the features that make Django powerful is its signal system. Signals allow developers to trigger certain actions when specific events occur, such as when a model is saved or deleted. However, there is often confusion about whether Django signals are asynchronous or not. In this article, we will explore this question and discuss the tradeoffs associated with using Django signals.
Categories: FLOSS Project Planets

PyCoder’s Weekly: Issue #568 (March 14, 2023)

Tue, 2023-03-14 15:30

#568 – MARCH 14, 2023
View in Browser »

Sharing Your Python App Across Platforms With BeeWare

Are you interested in deploying your Python project everywhere? This week on the show, Russell Keith-Magee, founder and maintainer of the BeeWare project, returns. Russell shares recent updates to Briefcase, a tool that converts a Python application into native installers on macOS, Windows, Linux, and mobile devices.
REAL PYTHON podcast

Overhead of Python Asyncio Tasks

The Textual library uses a lot of asyncio tasks. In order to determine whether to spend time optimizing them, Will measured the cost of creating asyncio tasks. TLDR; optimize something else. This article also spawned a conversation on Hacker News.
WILL MCGUGAN

Retire Your Legacy CMS With ButterCMS

ButterCMS is your new content backend. We’re SaaS so we host, maintain, and scale the CMS. Enable your marketing team to update website + app content without needing you. Try the #1 rated SaaS Headless CMS for your Python app today. Free for 30 days →
BUTTERCMS sponsor

Julia and Python Better Together

Julia is a popular programming language among data scientists, but if you ever code in that space and miss some of the key Python libraries, this article is for you. Julia packages can wrap other languages, so you can have the best of both worlds.
BOGUMIŁ KAMIŃSKI

Python 3.12.0 Alpha 6 Released

CPYTHON DEV BLOG

Django Developers Survey 2022 Results Available

DJANGO SOFTWARE FOUNDATION

Discussions Nearly 40% of Software Engineers Will Only Work Remotely

HACKER NEWS

Articles & Tutorials Predicting Wine Quality Using Chemical Properties

Alfredo discovered some datasets about wine quality including chemical properties and decided it was time to do some predictive model building. This article walks you through what he did with torch, sklearn, numpy, pandas, and seaborn to predict wine quality.
ALFREDO GONZÁLEZ-ESPINOZA

Are Those Numbers Realistic or Fake? Try Using Benford’s Law

How can you tell whether a set of figures is trustworthy? It’s not always simple, but Benford’s Law gives you one way to find out. There’s even a Python Package to help you check: randalyze.
JASON ROSS

The Best Way to Structure Your NoSQL Data Using Python

Data modeling can be challenging. The question that most often comes up is, “How do I structure my data?” The short answer: it depends. That’s why the Redis folks wrote a comprehensive e-book that goes through 8 different optimal scenarios and shows how to model them in Redis →
REDIS LABS sponsor

Building a ChatGPT-based Assistant With Python

This article demonstrates a workflow for integrating multiple AI services to perform speech-to-text (STT), natural language processing (NLP), and text-to-speech (TTS) using OpenAI’s ChatGPT and Whisper API’s in Python.
FAIZANBASHIR.ME • Shared by Faizan Bashir

How to Strangle Old Code Using Python Decorators

The “strangler pattern” is a way of simultaneously running old and replacement code to determine through live behavior whether the replacement works. This article shows you how to use decorators to help.
JON JAGGER

Deploying a Django App to Azure App Service

This tutorial looks at how to deploy a Django application to Azure App Service. Includes details on configuring your Django and what Azure services to use to get going quickly.
NIK TOMAZIC

Python’s Mutable vs Immutable Types: What’s the Difference?

In this tutorial, you’ll learn how Python mutable and immutable data types work internally and how you can take advantage of mutability or immutability to power your code.
REAL PYTHON

Formatting Gone Wrong

Your code formatter may have reformatted your API key. This can cause confusing errors. Read Kojo’s tale to learn what happened and how he figured it out.
KOJO IDRISSA

De-Duplicating a List While Maintaining Order

There’s more than one way deduplicate an iterable in Python. Which approach you take will depend on whether you care about the order of your items.
TREY HUNNER • Shared by Trey Hunner

Find Your Next Tech Job Through Hired

Hired is home to thousands of companies, from startups to Fortune 500s, that are actively hiring the best engineers, designers, data scientists, and more. Create a profile to let hiring managers extend interview requests to you. Sign up for free today!
HIRED sponsor

Python Assertions, or Checking if a Cat Is a Dog

The articles explains the rules of using assertions in Python, and when not to use them. Includes details on the __debug__ object.
MARCIN KOZAK • Shared by Marcin

XKCD-style Plots in Python With Matplotlib

This short article shows how to add a twist to your matplotlib plots by styling them like the web-famous comic xkcd.
RODRIGO GIRÃO SERRÃO • Shared by Rodrigo Girão Serrão

Projects & Code Interact With ChatGPT Through a Single-File Python Script

GITHUB.COM/REORX • Shared by Reorx

meerkat: Interactive Data Frames for Unstructured Data

GITHUB.COM/HAZYRESEARCH

sketch: AI Code-Writing Assistant for Pandas

GITHUB.COM/APPROXIMATELABS

replbuilder: Tool for Building Custom REPLs

GITHUB.COM/APEROCKY

pylyzer: A Fast Static Code Analyzer for Python

GITHUB.COM/MTSHIBA

Events Python Web Conf 2023

March 13 to March 18, 2023
PYTHONWEBCONF.COM

The Long White Computing Cloud

March 15, 2023
MEETUP.COM

Weekly Real Python Office Hours Q&A (Virtual)

March 15, 2023
REALPYTHON.COM

PyData Bristol Meetup

March 16, 2023
MEETUP.COM

PyLadies Dublin

March 16, 2023
PYLADIES.COM

Chattanooga Python User Group

March 17 to March 18, 2023
MEETUP.COM

PyCascades

March 18 to March 20, 2023 in Vancouver, BC + Remote
PYCASCADES.COM

Happy Pythoning!
This was PyCoder’s Weekly Issue #568.
View in Browser »

[ Subscribe to 🐍 PyCoder’s Weekly 💌 – Get the best Python news, articles, and tutorials delivered to your inbox once a week >> Click here to learn more ]

Categories: FLOSS Project Planets

Real Python: Documenting Python Projects With Sphinx and Read the Docs

Tue, 2023-03-14 10:00

Sphinx is a document generation tool that’s become the de facto standard for Python projects. It uses the reStructuredText (RST) markup language to define document structure and styling, and it can output in a wide variety of formats, including HTML, ePub, man pages, and much more. Sphinx is extendable and has plugins for incorporating pydoc comments from your code into your docs and for using MyST Markdown instead of RST.

Read the Docs is a free document hosting site where many Python projects host their documentation. It integrates with GitHub, GitLab, and Bitbucket to automatically pull new documentation sources from your repositories and build their Sphinx sources.

In this video course, you’ll learn how to:

  • Write your documentation with Sphinx
  • Structure and style your document with RST syntax
  • Incorporate your pydoc comments into your documentation
  • Host your documentation on Read the Docs

With these skills, you’ll be able to write clear, reliable documentation that’ll help your users get the most out of your project.

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Categories: FLOSS Project Planets

Nicola Iarocci: Eve 2.1.0 has just been released

Tue, 2023-03-14 02:05
Today I released Eve v2.1, which comes with official Flask 2.2+ support and the ability to modify the pagination limit on a per-resource basis thanks to the new pagination_limit setting. You can find the release on PyPI, while the changelog is available here—special thanks to Pieter De Clercq and smeng9 for the help with this release. Subscribe to the newsletter, the RSS feed, or follow me on Mastodon
Categories: FLOSS Project Planets

IslandT: Python Example – Group telephone number using the re module

Mon, 2023-03-13 23:49

Below Python example will use the re module to group the telephone number without the ‘-‘ sign and the period ‘.’.

Let’s say the phone number has been written in this manner.

123-456-7810.

The below python program will group the numbers by taking out both the ‘-‘ sign and ‘.’.

find = re.compile(r'\d+(?=\-|\.)') match = find.findall('123-456-7810.')

The match array now stored the following numbers:-

['123', '456', '7810']

Next what if I just want to extract all the numbers without the ‘-‘ sign? Then use the below program.

find = re.compile(r'\d+(?!\d)') match = find.findall('123-456-7810')
Categories: FLOSS Project Planets

Matt Layman: Time Travel with django-simple-history

Mon, 2023-03-13 20:00
If you’re interested in Django development, you might have come across the django-simple-history package. It’s a great tool that can help you keep track of changes made to your models over time. In this article, we’ll take a closer look at django-simple-history and how it can benefit your projects. What is django-simple-history? django-simple-history is a third-party Django package that provides version control for your models. It allows you to keep track of changes made to your models, including who made the change, when it was made, and what the change was.
Categories: FLOSS Project Planets

Ned Batchelder: Watchgha

Mon, 2023-03-13 18:43

I wrote a simple thing to watch the progress of GitHub Actions: watchgha.

I started by using gh run list, and tailoring the output, but that required running the command obsessively to see the changes. Then I tried gh run watch, but I didn’t like the way it worked. You had to pick one action to watch, but my branch has two actions that run automatically, and I need to know when both are done. Plus, it lists all of the steps, which makes my complex action matrix fill more than a screen, so I can’t even see the progress.

So I wrote my own. It buckets together the start times of the runs to present them as a coherent run triggered by a single event. It keeps showing runs as long as they have a new action to show. It presents the steps in a compact form, and collapses jobs and runs once they succeed.

Give it a try and let me know if it works for you.

Categories: FLOSS Project Planets

PyBites: Blaise Pabon on his developer journey, open source and why Python is great

Mon, 2023-03-13 14:00

Listen here:

Or watch here:

Welcome back to the Pybites podcast. This week we have a very special guest: Blaise Pabon.

We talk about his background in software development, how he started with Python and his journey with us in PDM.

We also pick his brains about why Python is such a great language, the importance of open source and his active role in it, including a myriad of developer communities he actively takes part in.

Lastly, we talk about the books we’re currently reading.

Links:
– Vitrina: a portfolio development kit for DevOps
– Boston Python hosts several online meetings a week
– The mother of all (web) demo apps
– Cucumberbdd New contributors ensemble programming sessions
– Reach out to Blaise: blaise at gmail dot com | Slack

Books:
– Antifragile
– The Tombs of Atuan (Earthsea series)
– How to write

Categories: FLOSS Project Planets

Real Python: Python News: What's New From February 2023

Mon, 2023-03-13 10:00

February is the shortest month, but it brought no shortage of activity in the Python world! Exciting developments include a new company aiming to improve cloud services for developers, publication of the PyCon US 2023 schedule, and the first release candidate for pandas 2.0.0.

In the world of artifical intelligence, OpenAI has continued to make strides. But while the Big Fix has worked to reduce vulnerabily for programmers, more malicious programs showed up on PyPI.

Read on to dive into the biggest Python news from the last month.

Join Now: Click here to join the Real Python Newsletter and you'll never miss another Python tutorial, course update, or post.

Pydantic Launches Commercial Venture

With over 40 million downloads per month, pydantic is the most-used data validation library in Python. So when its founder, Samuel Colvin, announces successful seed funding, there’s plenty of reason to believe there’s a game changer in the works.

In his announcement, Colvin compares the current state of cloud services to that of a tractor fifteen years after its invention. In both cases, the technology gets the job done, but without much consideration for the person in the driver’s seat.

Colvin’s new company builds on what pydantic has learned about putting developer experience and expertise first. The exact details of this new venture are still under wraps, but it sets out to answer these questions:

What if we could build a platform with the best of all worlds? Taking the final step in reducing the boilerplate and busy-work of building web applications — allowing developers to write nothing more than the core logic which makes their application unique and valuable? (Source)

This doesn’t mean that the open-source project is going anywhere. In fact, pydantic V2 is on its way and will be around seventeen times faster than V1, thanks to being rewritten in Rust.

To stay on up to date on what’s happening with pydantic’s new venture, subscribe to the GitHub issue.

Read the full article at https://realpython.com/python-news-february-2023/ »

[ Improve Your Python With 🐍 Python Tricks 💌 – Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

Categories: FLOSS Project Planets

Python for Beginners: Overwrite a File in Python

Mon, 2023-03-13 09:00

File handling is one of the first tasks we do while working with data in python. Sometimes, we need to change the contents of the original file or completely overwrite it. This article discusses different ways to overwrite a file in python.

Table of Contents
  1. Overwrite a File Using open() Function in Write Mode
  2. Using Read Mode and Write Mode Subsequently
  3. Overwrite a File Using seek() and truncate() Method in Python
  4. Conclusion
Overwrite a File Using open() Function in Write Mode

To overwrite a file in python, you can directly open the file in write mode. For this, you can use the open() function. The open() function takes a string containing the file name as its first input argument and the python literal “w” as its second input argument.

If the file with the given name doesn’t exist in the file system, the open() function creates a new file and returns the file pointer. If the file is already present in the system, the open() function deletes all the file contents and returns the file pointer. 

Once we get the file pointer, we can write the new data to the file using the file pointer and the write() method. The write() method, when invoked on the file pointer, takes the file content as its input argument and writes it to the file. Next, we will close the file using the close() method. After this, we will get the new file with overwritten content.

For instance, suppose that we want to overwrite the following text file in python.

Sample text file

To overwrite the above file, we will use the following code.

file=open("example.txt","w") newText="I am the updated text from python program." file.write(newText) file.close()

The text file after execution of the above code looks as follows.

In this example, we have directly opened the text file in write mode. Due to this, the original content of the file gets erased. After this, we have overwritten the file using new text and closed the file using the close() method.

Using Read Mode and Write Mode Subsequently

In the above example, we cannot read the original file contents. If you want to read the original file contents, modify it and then overwrite the original file, you can use the following steps.

  • First, we will open the file in read mode using the open() function. The open() function will return a file pointer after execution. 
  • Then, we will read the file contents using the read() method. The read() method, when invoked on the file pointer, returns the file contents as a string. 
  • After reading the file contents, we will close the file using the close() method. 
  • Now, we will again open the file, but in write mode. 
  • After opening the file in write mode, we will overwrite the file contents using the write() method. 
  • Finally, we will close the file using the close() method.

After executing the above steps, we can overwrite a file in python. You can observe this in the following example.

file=open("example.txt","r") fileContent=file.read() print("The file contents are:") print(fileContent) file.close() file=open("example.txt","w") newText="I am the updated text from python program." file.write(newText) file.close()

Output:

The file contents are: This a sample text file created for pythonforbeginners.

In this example, we first opened the file in read mode and read the contents as shown above. Then, we overwrote the file contents by opening it again in write mode. After execution of the entire code, the text file looks as shown below.

Modified text file

Although this approach works for us, it is just a workaround. We aren’t actually overwriting the file but reading and writing to the file after opening it separately. Instead of this approach, we can use the seek() and truncate() methods to overwrite a file in python. 

Overwrite a File Using seek() and truncate() Method in Python

To overwrite a file in a single step after reading its contents, we can use the read(), seek() truncate() and write() methods.

  • When we read the contents of a file using the read() method, the file pointer is moved to the end of the file. We can use the seek() method to again move to the start of the file. The seek() method, when invoked on the file pointer, takes the desired position of the file pointer as its input argument. After execution, it moves the file pointer to the desired location. 
  • To remove the contents of the file after reading it, we can use the truncate() method. The truncate() method, when invoked on a file pointer, truncates the file. In other words, it removes all the file contents after the current position of the file pointer.

To overwrite a file in python using the seek() and truncate() methods, we can use the following steps.

  • First, we will open the file in append mode using the open() function. 
  • Next, we will read the file contents using the read() method. 
  • After reading the file contents, we will move the file pointer to the start of the file using the seek() method. For this, we will invoke the seek() method on the file pointer with 0 as its input. The file pointer will be moved to the first character in the file.
  • Next, we will use the truncate() method to delete all the contents of the file.
  • After truncating, we will overwrite the file with new file contents using the write() method.
  • Finally, we will close the file using the close() method.

After executing the above steps, we can easily overwrite a file in python. You can observe this in the following example.

file=open("example.txt","r+") fileContent=file.read() print("The file contents are:") print(fileContent) newText="I am the updated text from python program." file.seek(0) file.truncate() file.write(newText) file.close()

Output:

The file contents are: This a sample text file created for pythonforbeginners.

After execution of the above code, the text file looks as follows.

Conclusion

In this article, we discussed different ways to overwrite a file in python. To learn more about python programming, you can read this article on how to convert a pandas series into a dataframe. You might also like this article on how to read a file line by line in python.

I hope you enjoyed reading this article. Stay tuned for more informative articles.

Happy Learning!

The post Overwrite a File in Python appeared first on PythonForBeginners.com.

Categories: FLOSS Project Planets

Mike Driscoll: PyDev of the Week: Logan Thomas

Mon, 2023-03-13 08:30

This week we welcome Logan Thomas as our PyDev of the Week! If you’d like to see some of the things that Logan has been up to, you can do so on Logan’s GitHub profile or by visiting his personal website. Logan works at Enthought as a teacher. If you haven’t done so, you should check out their list of courses.

Now let’s spend some time getting to know Logan better!

Can you tell us a little about yourself (hobbies, education, etc):

I currently work for Enthought as a Scientific Software Developer and Technical Trainer. Previously, I worked for a protective design firm as a machine learning engineer and I’ve also been employed as a data scientist in the digital media industry.

My educational background consists of mathematics, statistics, and mechanical engineering. During my final semester of graduate school at the University of Florida, I took a data mining class from the Department of Information Science and was hooked. I’ve worked in data science ever since.

I’m passionate about software and love to (humbly) share my knowledge with others. Working for Enthought gives me the opportunity to help students, and larger organizations, accelerate their journey toward greater digital maturity. We focus on digital transformation – transforming an organization’s business model to take full advantage of the possibilities that new technologies can provide. It’s so fulfilling being able to help scientists and engineers develop the digital skills and tools they need to be able to automate away the trivial tasks and free their minds of this cognitive load to focus on the things they love – the science and engineering behind the problems they face.

Outside of work, I love to stay active by running, brewing a good cup of coffee, and have recently started smoking brisket / pork (a must if you live in Texas like me).

Why did you start using Python?

I was originally hesitant to get into software development as both my parents are software engineers and I wanted my own career path. But, with their encouragement, I took an Introduction to Programming course during my undergraduate degree. The class was taught in Visual Basic and given it was my first true taste of any type of software development, I didn’t have the best experience. Thus, I decided to stay away from computer science and ended up going to graduate school for mechanical engineering. I’m glad the story didn’t end there.

In one of my mechanical design courses, we used a CAD (computer-aided design) program that supported Python scripting (if you knew how to use it!). I remember spending an entire weekend running simulations by hand with Microsoft Excel. When I asked my friend how he managed to get his testing done so quickly, he showed me roughly 10 lines of Python code he wrote to automate the simulation loop. After that exchange, I made it my goal to give computer science another try and take advantage of the superpower a general-purpose programming language like Python could provide.

What other programming languages do you know and which is your favorite?

Since graduate school, I’ve worked in the data science space for about 8 years – all of which my primary programming language has been Python. I love Python for its ease of use and ability to stretch across domain areas like scripting / automation, data analysis / machine learning, and even web design. For me, Python is the clear favorite.

Besides Python, I have the most experience with MATLAB, R, SQL, and Spark. I’ve dabbled in a few other languages as well: Scala, Ruby, C++, HTML / CSS.

What projects are you working on now?

Currently, I am working on building a curriculum for Enthought Academy – a scientific Python catalog of courses for R&D professionals. We have some really great courses in the queue for 2023! I’m putting final touches on our new Data Analysis course that covers Python libraries like NumPy, pandas, Xarray, and Awkward Array.

Personally, I’m working on a new project called Coffee & Code. The idea is to share small code snippets and video shorts for exploring the Python programming language — all during a morning coffee break. My first major project will be trying to teach my brother, who has 0 programming experience, the Python programming language. I plan to film it live and release videos as we walk through installation, environment setup, and language syntax together.

I’m also on the planning committee for two major Python conferences: PyTexas and SciPy Conference. I love both of these events and can’t wait for their 2023 appearance.

Which Python libraries are your favorite (core or 3rd party)?

There are so many to choose from! From the standard library, I have to give a shout-out to the collections module and itertools. The namedtuple, double-ended queue (deque), Counter, and defaultdict are gamechangers. The itertool recipes in the official Python documentation are a goldmine.

My favorite third-party libraries from the scientific Python stack are NumPy and scikit-learn. I use PyTorch and Tensorflow with Keras (in that order) for deep learning. I also have to give credit to IPython. It is my default choice for any type of interactive computing and is quite honestly my development environment of choice (when partnered with Vim). One lesser-known library that I have come to enjoy is DEAP (Distributed Evolutionary Algorithms in Python). I worked with this library extensively for a genetic programming project in the past.

I see you are an instructor at Enthought. What sorts of classes do you teach?

We teach all sorts of classes at Enthought. Given that all of our instructors started out in industry first, we can boast that our curriculum is “for scientists and engineers by scientists and engineers”. We have introduction to Python and programming courses as well as more advanced machine learning and deep learning courses. We also have a software engineering course, data analysis course, data management course, and desktop application development course. All of these live in what we call a learning path or “track”. We have an Enthought Academy Curriculum Map that displays the various tracks we offer: Data Analysis, Machine Learning, Tool Maker, and Manager.

What are some common issues that you see new students struggling with?

I love Python but can admit that there are some rough edges when first getting started. Most students struggle with understanding virtual environments and why they need one if they’ve never encountered dependency management before. Other students are surprised at how many third-party libraries exist in the ecosystem. I often get questions on which ones to use, how to know which ones are safe, and where to look for them.

Some students struggle with IDEs and which development environment to use. I’ve gotten questions like “which is the single best tool: JupyterLab, IPython, VSCode, PyCharm, Spyder, …”. For new students, I try to help them understand that these are tools and there may not be a single correct answer. Sometimes I reach for VSCode when needing to navigate a large code base. If I’m sharing an analysis with a colleague or onboarding a new analyst, I may reach for a Jupyter Notebook. If I’m prototyping a new algorithm, I’ll probably stay in IPython. But, these are my preferences and may not be the same for all students. When looking at these as tools in a toolbelt, rather than a single permanent choice, it becomes easier to pick the right one for the task at hand.

Is there anything else you’d like to say?

Thank you for the opportunity! I love to meet new people in the Python space and appreciate you reaching out!

The post PyDev of the Week: Logan Thomas appeared first on Mouse Vs Python.

Categories: FLOSS Project Planets

Kay Hayen: Python 3.11 and Nuitka experimental support

Mon, 2023-03-13 08:05

In my all in with Nuitka post and my first post Python 3.11 and Nuitka and then progress post Python 3.11 and Nuitka Progress , I promised to give you more updates on Python 3.11 and in general.

So this is where 3.11 is at, and the TLDR is, experimental support has arrives with Nuitka 1.5 release, follow develop branch for best support, and 1.6 is expected to support new 3.11 features.

What is now

The 1.5 release passes the CPython3.10 test suite practically as good as with Python3.11 as with Python3.10, with only a handful of tests failing and these do not seem significant, and it is expected to be resolved later when making the CPython3.11 test suite working.

The 1.5 release now gives this kind of output.

Nuitka:WARNING: The Python version '3.11' is not officially supported by Nuitka '1.5', but an Nuitka:WARNING: upcoming release will change that. In the mean time use Python version '3.10' Nuitka:WARNING: instead or newer Nuitka.

Using develop should always be relatively good, it doesn’t often have regressions, but Python3.11 improvements will accumulate there until 1.6 release happens. Follow it there if you want. However, checking those standalone cases that can be done, as many packages are not available for 3.11 yet, I have not found a single issue.

What you can do?

Try your software with Nuitka and Python3.11 now. Very likely your code base is not using 3.11 specific features, or is it? If it is, of course you may have to wait until develop catches up with new features and changes in behavior.

In case you are wondering, how I can invest this much time into doing all of what I do, consider becoming a subscriber of Nuitka commercial, even if you do not need the IP protection features it mostly has. All commonly essential packaging and performance features are entirely free, and I have put incredible amounts of works in this, and I need to now make a living off it, while I do not plan to make Nuitka annoying or unusable for non-commercial non-subscribers at all.

What was done?

Getting all of the test suite to work, is a big thing already. Also a bunch of performance degradations have been addressed. However right now, attribute lookups and updates e.g. are not as well optimized, and that despite and of course because Python 3.11 changed the core a lot in this area.

The Process

This was largely explained in my previous posts. I will just put where we are now and skip completed steps and avoid repeating it too much.

In the next phase, during 1.6 development the 3.11 test suite is used in the same way as the 3.10 test suite. Then we will get to support new features, new behaviors, newly allowed things, and achieve super compatibility with 3.11 as we always do for every CPython release. All the while doing this, the CPython3.10 test suite will be executed with 3.11 by my internal CI, immediately reporting when things change for the worse.

This phase is starting today actually.

When

It is very hard to predict what will be encountered in the test suite. It didn’t look like many things are there, but e.g. exception groups might be an invasive feature, otherwise I am not aware of too many things at this point. It sure feels close now.

These new features will be relatively unimportant to the masses of users who didn’t immediately change their code to use 3.11 only features.

The worst things with debugging is that I just never know how much time it will be. Often things are very quick to add to Nuitka, and sometimes they hurt a lot or cause regressions for other Python versions by mistake.

Benefits for older Python too

I mentioned stuff before, that I will not repeat only new stuff.

Most likely, attribute lookups will lead to adding the same JIT approach the Python 3.11 allows for now, and maybe that will be possible to backport to old Python as well. Not sure yet. For now, they are actually worse than with 3.10, while CPython made them faster.

Expected results

Not quite good for benchmarking at this time. From the comparisons I did, the compiled code of 3.10 and 3.11 seemed equally fast, allowing CPython to catch up. When Nuitka takes advantage of the core changes to dict and attributes more closely, hope is that will change.

So in a sense, using 3.11 with Nuitka over 3.10 actually doesn’t have much of a point yet.

I need to repeat this. People tend to expect that gains from Nuitka and enhancements of CPython stack up. The truth of the matter is, no they do not. CPython is now applying some tricks that Nuitka already did, some a decade ago. Not using its bytecode will then become less of a benefit, but that’s OK, this is not what Nuitka is about.

We need to get somewhere else entirely anyway, in terms of speed up. I will be talking about PGO and C types a lot in the coming year, that is at least the hope. The boost of 1.4 and 1.5 was only be the start. Once 3.11 support is sorted out, int will be getting dedicated code too, that’s where things will become interesting.

Final Words

So, this post is kind of too late. My excuse is that due to having Corona, I did kind of close down on some of the things, and actually started to do optimization that will lead towards more scalable class code. This is also already in 1.5 and important.

But now that I feel better, I actually forced myself to post this. I am getting better at this. Now back and starting the CPython3.11 test suite, I will get back to you once I have some progress with that. Unfortunately adding the suite is probably also a couple of days work, just technically before I encounter interesting stuff.

Categories: FLOSS Project Planets

Python Bytes: #327 Untangling XML with Pydantic

Mon, 2023-03-13 04:00
<a href='https://www.youtube.com/watch?v=rduFjpb-fAw' style='font-weight: bold;'>Watch on YouTube</a><br> <br> <p><strong>About the show</strong></p> <p>Sponsored by <a href="https://pythonbytes.fm/compiler"><strong>Compiler Podcast from Red Hat</strong></a>.</p> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy"><strong>@mkennedy@fosstodon.org</strong></a></li> <li>Brian: <a href="https://fosstodon.org/@brianokken"><strong>@brianokken@fosstodon.org</strong></a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes"><strong>@pythonbytes@fosstodon.org</strong></a></li> </ul> <p>Join us on YouTube at <a href="https://pythonbytes.fm/stream/live"><strong>pythonbytes.fm/live</strong></a> to be part of the audience. Usually Tuesdays at 11am PT. Older video versions available there too.</p> <p><strong>Michael #1:</strong> <a href="https://pydantic-xml.readthedocs.io/en/latest/"><strong>pydantic-xml extension</strong></a></p> <ul> <li>via Ilan</li> <li>Recall untangle. How about some pydantic in the mix?</li> <li>pydantic-xml is a pydantic extension providing model fields xml binding and xml serialization / deserialization. It is closely integrated with pydantic which means it supports most of its features.</li> </ul> <p><strong>Brian #2:</strong> <a href="https://snarky.ca/how-virtual-environments-work"><strong>How virtual environments work</strong></a></p> <ul> <li>Brett Cannon</li> <li>This should be required reading for anyone learning Python. <ul> <li>Maybe right after “Hello World” and right before “My first pytest test”, approximately.</li> </ul></li> <li>Some history of environments <ul> <li>Back in the day, there was global and your directory.</li> </ul></li> <li>How environments work <ul> <li>structure: bin, include, and lib</li> <li>pyvenv.cfg configuration file </li> </ul></li> <li>How Python uses virtual environments</li> <li>What activation does, and that it’s <strong>optional.</strong> <ul> <li>Yes, activation is optional. </li> </ul></li> <li>A new project called microvenv that helps VS Code. <ul> <li>Mostly to fix the “Debian doesn’t ship python3 with venv” problem.</li> <li>It doesn’t include script activation stuff</li> <li>It’s super small, less than 100 lines of code, in one file.</li> </ul></li> </ul> <p><strong>Michael #3:</strong> <a href="https://github.com/raaidarshad/dbdeclare"><strong>DbDeclare</strong></a></p> <ul> <li>Declarative layer for your database.</li> <li>https://raaidarshad.github.io/dbdeclare/guide/controller/#example</li> <li>Sent in by creator raaid</li> <li>DbDeclare is a Python package that helps you create and manage entities in your database cluster, like databases, roles, access control, and (eventually) more. </li> <li>It aims to fill the gap between SQLAlchemy (SQLA) and infrastructure as code (IaC).</li> <li>You can: <ul> <li>Declare desired state in Python</li> <li>Avoid maintaining raw SQL</li> <li>Tightly integrate your databases, roles, access control, and more with your tables</li> </ul></li> <li>Migrations like alembic coming too.</li> </ul> <p><strong>Brian #4:</strong> <a href="https://sethmlarson.dev/nox-pyenv-all-python-versions"><strong>Testing multiple Python versions with nox and pyenv</strong></a></p> <ul> <li>Seth Michael Larson</li> <li>This is a cool “what to do first” with nox.</li> <li>Specifically, how to use it to run pytest against your project on multiple versions of Python.</li> <li><p>Example noxfile.py is super small</p> <pre><code> import nox @nox.session(python=["3.8", "3.9", "3.10", "3.11", "3.12", "pypy3"]) def test(session): session.install(".") session.install("-rdev-requirements.txt") session.run("pytest", "tests/") </code></pre></li> <li><p>How to run everything, <code>nox</code> or <code>nox -s test</code>.</p></li> <li>How to run single sessions, <code>nox -s test-311</code> for just Python 3.11</li> <li>Also how to get this to work with pyenv. <ul> <li><code>pyenv global 3.8 3.9 3.10 3.11 3.12-dev</code></li> </ul></li> <li>This reminds me that I keep meaning to write a workflow comparison post about nox and tox.</li> </ul> <p><strong>Extras</strong> </p> <p>Michael:</p> <ul> <li><a href="https://www.bleepingcomputer.com/news/security/github-makes-2fa-mandatory-next-week-for-active-developers/">GitHub makes 2FA mandatory next week for active developers</a></li> <li>New adventure bike [<a href="https://python-bytes-static.nyc3.digitaloceanspaces.com/IMG_0205.jpeg">image 1</a>, <a href="https://python-bytes-static.nyc3.digitaloceanspaces.com/IMG_1002.jpeg">image 2</a>]. <ul> <li>Who’s got good ideas for where to ride in the PNW? </li> <li>Wondering why I got it, here’s <a href="https://www.youtube.com/watch?v=U1HfsqjnEc0">a fun video</a>.</li> </ul></li> </ul> <p><strong>Joke:</strong> <a href="https://www.reddit.com/r/ProgrammerHumor/comments/10p4wo0/anybody_else_having_this_kind_of_colleague_way_to/">Case of the Mondays</a></p>
Categories: FLOSS Project Planets

Tryton News: Tryton Unconference Berlin Call for Sponsors and Presentations

Mon, 2023-03-13 03:00

The next Tryton Unconference is scheduled to begin in just over two months. As you can see in the linked topic the community is already booking their flights and accommodation, if you’ve already done this then please share your booking details so we can all meet up and spend some time together.

As is the case for all unconferences the content is provided by its participants, so if you have a topic that you want to present then please submit details of your talk by email to tub2023@tryton.org. We accept long presentations (30 minutes) and lighting talks (of just 5-10 minutes). So please include a estimate for the duration of your talk when submitting it.

If you don’t feel confident enough to present a topic all by yourself, or there is something you don’t fully understand and want explained, then please create a topic on the forum to see if anyone else is interested in joining forces, or doing a talk to try and make things clearer for you.

I would like to announce that the Foundation board has agreed to accept sponsors for this event. Sponsorship starts from 500€ and grants free entry to the unconference and the inclusion of your logo on the event’s page. If you are interested in sponsoring the event, please send us a message.

We will do our best to live stream the talks and make them available on Youtube. If you can help us with this, then please also raise your voice.

I hope to see you all in Berlin!

1 post - 1 participant

Read full topic

Categories: FLOSS Project Planets

The Python Coding Blog: Anatomy of a 2D Game using Python’s turtle and Object-Oriented Programming

Sun, 2023-03-12 14:21

When I was young, we played arcade games in their original form on tall rectangular coin-operated machines with buttons and joysticks. These games had a resurgence as smartphone apps in recent years, useful to keep one occupied during a long commute. In this article, I’ll resurrect one as a 2D Python game and use it to show the “anatomy” of such a game.

You can follow this step-by-step tutorial even if you’re unfamiliar with all the topics. In particular, this tutorial will rely on Python’s turtle module and uses the object-oriented programming (OOP) paradigm. However, you don’t need expertise in either topic, as I’ll explain the key concepts you’ll need. However, if you’re already familiar with OOP, you can easily skip the clearly-marked OOP sections.

This is the game you’ll write:

The rules of the game are simple. Click on a ball to bat it up. How long can you last before you lose ten balls?

In addition to getting acquainted with OOP principles, this tutorial will show you how such games are built step-by-step.

Note about content in this article: If you’re already familiar with the key concepts in object-oriented programming, you can skip blocks like this one. If you’re new or relatively new to the topic, I recommend you read these sections as well.

The Anatomy of a 2D Python Game | Summary

I’ll break this game down into eight key steps:

  1. Create a class named Ball and set up what should happen when you create a ball
  2. Make the ball move forward
  3. Add gravity to pull the ball downwards
  4. Add the ability to bat the ball upwards
  5. Create more balls, with each ball created after a certain time interval
  6. Control the speed of the game by setting a frame rate
  7. Add a timer and an end to the game
  8. Add finishing touches to the game

Are you ready to start coding?

A visual summary of the anatomy of a 2D Python game

Here’s another summary of the steps you’ll take to create this 2D Python game. This is a visual summary:

1. Create a Class Named Ball

You’ll work on two separate scripts to create this game:

  • juggling_ball.py
  • juggling_balls_game.py

You’ll use the first one, juggling_ball.py, to create a class called Ball which will act as a template for all the balls you’ll use in the game. In the second script, juggling_balls_game.py, you’ll use this class to write the game.

It’s helpful to separate the class definitions into a standalone module to provide a clear structure and to make it easier to reuse the class in other scripts.

Let’s start working on the class in juggling_ball.py. If you want to read about object-oriented programming in Python in more detail before you dive into this project, you can read Chapter 7 | Object-Oriented Programming. However, I’ll also summarise the key points in this article in separate blocks. And remember that if you’re already familiar with the key concepts in OOP, you can skip these additional note blocks, like the one below.

A class is like a template for creating many objects using that template. When you define a class, such as the class Ball in this project, you’re not creating a ball. Instead, you’re defining the instructions needed to create a ball.

The __init__() special method is normally the first method you define in a class and includes all the steps you want to execute each time you create an object using this class.

Create the class

Let’s start this 2D Python game. You can define the class and its __init__() special method:

# juggling_ball.py import random import turtle class Ball(turtle.Turtle): def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), )

The class Ball inherits from turtle.Turtle which means that an object which is a Ball is also a Turtle. You also call the initialisation method for the Turtle class when you call super().__init__().

The __init__() method creates two data attributes, .width and .height. These attributes set the size in pixels of the area in which the program will create the ball.

self is the name you use as a placeholder to refer to the object that you’ll create. Recall that when you define a class, you’re not creating any objects. At this definition stage, you’re creating the template. Therefore, you need a placeholder name to refer to the objects you’ll create in the future. The convention is to use self for this placeholder name.

You create two new data attributes when you write:
self.width = width
self.height = height

An attribute belongs to an object in a class. There are two types of attributes:
– Data attributes
– Methods

You can think of data attributes as variables attached to an object. Therefore, an object “carries” its data with it. The data attributes you create are self.width and self.height.

The other type of attribute is a method. You’ll read more about methods shortly.

The rest of the __init__() method calls Turtle methods to set the initial state of each ball. Here’s a summary of the four turtle.Turtle methods used:

  • .shape() changes the shape of the turtle
  • .color() sets the colour of the turtle (and anything it draws)
  • .penup() makes sure the turtle will not draw a line when it moves
  • .setposition() places the turtle at a specific _xy-_coordinate on the screen. The centre is (0, 0).

You set the shape of the turtle to be a circle (it’s actually a disc, but the name of the shape in turtle is “circle”). You use a random value between 0 and 1 for each of the red, green, and blue colour values when you call self.color(). And you set the turtle’s position to a random integer within the bounds of the region defined by the arguments of the __init__() method. You use the floor division operator // to ensure you get an integer value when dividing the width and height by 2.

It’s a good idea to define the .__repr__() special method for a class. As you won’t use this explicitly in this project, I won’t add it to the code in the main article. However, it’s included in the final code in the appendix.

Test the class

You can test the class you just created in the second script you’ll work on as you progress through this project, juggling_balls_game.py:

# juggling_balls_game.py import turtle from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 window = turtle.Screen() window.setup(WIDTH, HEIGHT) Ball(WIDTH, HEIGHT) Ball(WIDTH, HEIGHT) turtle.done()

You create a screen and set its size to 600 x 600 pixels. Next, you create two instances of the Ball class. Even though you define Ball in another script, you import it into the scope of your game program using from juggling_ball import Ball.

Here’s the output from this script:

You call Ball() twice in the script. Therefore, you create two instances of the class Ball. Each one has a random colour and moves to a random position on the screen as soon as it’s created.

An instance of a class is each object you create using that class. The class definition is the template for creating objects. You only have one class definition, but you can have several instances of that class.

Data attributes, which we discussed earlier, are sometimes also referred to as instance variables since they are variables attached to an instance. You’ll also read about instance methods soon.

You may have noticed that when you create the two balls, you can see them moving from the centre of the screen where they’re created to their ‘starting’ position. You want more control on when to display the objects on the screen.

To achieve this, you can set window.tracer(0) as soon as you create the screen and then use window.update() when you want to display the turtles on the screen. Any changes that happen to the position and orientation of Turtle objects (and Ball objects, too) will occur “behind the scenes” until you call window.update():

# juggling_balls_game.py import turtle from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) Ball(WIDTH, HEIGHT) Ball(WIDTH, HEIGHT) window.update() turtle.done()

When you run the script now, the balls will appear instantly in the correct starting positions. The final call to turtle.done() runs the main loop of a turtle graphics program and is needed at the end of the script to keep the program running once the final line is reached.

You’re now ready to create a method in the Ball class to make the ball move forward.

2. Make Ball Move Forward

Let’s shift back to juggling_ball.py where you define the class. You’ll start by making the ball move upwards at a constant speed.

You can set the maximum velocity that a ball can have as a class attribute .max_velocity and then create a data attribute .velocity which will be different for each instance. The value of .velocity is a random number that’s limited by the maximum value defined at the class level:

# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity)

You also change the ball’s heading using the Turtle method .setheading() so that the object is pointing upwards.

A class attribute is defined for the class overall, not for each instance. This can be used when the same value is needed for every instance you’ll create of the class. You can access a class attribute like you access instance attributes. For example, you use self.max_velocity in the example above.

Next, you can create the method move() which moves the ball forward by the value stored in self.velocity.

A method is a function that’s part of a class. You’ll only consider instance methods in this project. You can think of an instance method as a function that’s attached to an instance of the class.

In Python, you access these using the dot notation. For example, if you have a list called numbers, you can call numbers.append() or numbers.pop(). Both .append() and .pop() are methods of the class list, and every instance of a list carries these methods with it.

The method .move() is an instance method, which means the object itself is passed to the method as its first argument. This is why the parameter self is within the parentheses when you define .move().

We’re not using real world units such as metres in this project. For now, you can think of this velocity as a value measured in pixels per frame instead of metres per second. The duration of each frame is equal to the time it takes for one iteration of the while loop to complete.

Therefore, if you call .move() once per frame in the game, you want the ball to move by the number of pixels in .velocity during that frame. Let’s add the .move() method:

# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity)

You can test the new additions to the class Ball in juggling_balls_game.py:

# juggling_balls_game.py import turtle from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) ball = Ball(WIDTH, HEIGHT) while True: ball.move() window.update() turtle.done()

You test your code using a single ball for now and you call ball.move() within a while True loop.

Recall that .move() is a method of the class Ball. In the class definition in juggling_ball.py, you use the placeholder name self to refer to any future instance of the class you create. However, now you’re creating an instance of the class Ball, and you name it ball. Therefore, you can access all the attributes using this instance’s name, for example by calling ball.move().

Here’s the output from this script so far:

Note: the speed at which your while loop will run depends on your setup and operating system. We’ll deal with this later in this project. However, if your ball is moving too fast, you can slow it down by dividing its velocity by 10 or 100, say, when you define self.velocity in the __init__() method. If you can’t see any ball when you run this script, the ball may be moving so quickly out of the screen that you need to slow it down significantly.

You can create a ball that moves upwards with a constant speed. The next step in this 2D Python game is to account for gravity to pull the ball down.

3. Add Gravity to Pull Ball Downwards

Last time I checked, when you toss a ball upward, it will slow down, reach a point when, it’s stationary in the air for the briefest of moments, and then starts falling down towards the ground.

Let’s add the effect of gravity to the game. Gravity is a force that accelerates an object. This acceleration is given in metres per second squared (m/s^2). The acceleration due to the Earth’s gravity is 9.8m/s^2, and this is always a downward acceleration. Therefore, when an object is moving upwards, gravity will decelerate the object until its velocity is zero. Then it will start accelerating downwards.

In this project, we’re not using real-world units. So you can think of the acceleration due to gravity as a value in pixels per frame squared. “Frame” is a unit of time in this context, as it refers to the duration of the frame.

Acceleration is the rate of change of velocity. In the real world, we use the change of velocity per second. However, in the game you’re using change of velocity per frame. Later in this article, you’ll consider the time it takes for the while loop to run so you can set the frame time.

You can add a class attribute to define the acceleration due to gravity and define a method called .fall():

# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 gravity = 0.07 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity) self.fall() def fall(self): self.velocity -= self.gravity

The method .fall() changes the value of the data attribute .velocity by subtracting the value stored in the class attribute .gravity from the current .velocity. You also call self.fall() within .move() so that each time the ball moves in a frame, it’s also pulled back by gravity. In this example, the value of .gravity is 0.07. Recall that you’re measuring velocity in pixels per frame. Therefore, gravity reduces the velocity by 0.07 pixels per frame in each frame.

You could merge the code in .fall() within .move(). However, creating separate methods gives you more flexibility in the future. Let’s assume you want a version of the game in which something that happens in the game suspends gravity. Having separate methods will make future modifications easier.

You could also choose not to call self.fall() within .move() and call the method directly within the game loop in juggling_balls_game.py.

You need to consider another issue now that the balls will be pulled down towards the ground. At some point, the balls will leave the screen from the bottom edge. Once this happens, you want the program to detect this so you can deal with this. You can create another method is_below_lower_edge():

# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 gravity = 0.07 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity) self.fall() def fall(self): self.velocity -= self.gravity def is_below_lower_edge(self): if self.ycor() < -self.height // 2: self.hideturtle() return True return False

The method .is_below_lower_edge() is an instance method which returns a Boolean value. The method hides the turtle object and returns True if the ball has dipped below the lower edge of the screen. Otherwise, it returns False.

Methods are functions. Therefore, like all functions, they always return a value. You’ll often find methods such as .move() and .fall() that don’t have an explicit return statement. These methods change the state of one or more of the object’s attributes. These methods still return a value. As with all functions that don’t have a return statement, these methods return None.

The purpose of .is_below_lower_edge() is different. Although it’s also changing the object’s state when it calls self.hideturtle(), its main purpose is to return True or False to indicate whether the ball has dropped below the lower edge of the screen.

It’s time to check whether gravity works. You don’t need to change juggling_balls_game.py since the call to ball.move() in the while loop remains the same. Here’s the result of running the script now:

You can see the ball is tossed up in the air. It slows down. Then it accelerates downwards until it leaves the screen. You can also temporarily add the following line in the while loop to check that .is_below_lower_edge() works:

print(ball.is_below_lower_edge())

Since this method returns True or False, you’ll see its decision printed out each time the loop iterates.

This 2D Python game is shaping up nicely. Next, you need to add the option to bat the ball upwards.

4. Add the Ability to Bat Ball Upwards

There are two steps you’ll need to code to bat a ball upwards:

  • Create a method in Ball to bat the ball upwards by adding positive (upward) velocity
  • Call this method each time the player clicks somewhere close to the ball

You can start adding another class attribute .bat_velocity_change and the method .bat_up() in Ball:

# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 gravity = 0.07 bat_velocity_change = 8 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint(-self.width // 2, self.width // 2), random.randint(-self.height // 2, self.height // 2), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity) self.fall() def fall(self): self.velocity -= self.gravity def is_below_lower_edge(self): if self.ycor() < -self.height // 2: self.hideturtle() return True return False def bat_up(self): self.velocity += self.bat_velocity_change

Each time you call the method .bat_up(), the velocity of the ball increases by the value in the class attribute .bat_velocity_change.

If the ball’s velocity is, say, -10, then “batting it up” will increase the velocity to -2 since .bat_velocity_change is 8. This means the ball will keep falling but at a lower speed.

Suppose the ball’s velocity is -3, then batting up changes this to 5 so the ball will start moving upwards. And if the ball is already moving upwards when you bat it, its upward speed will increase.

You can now shift your attention to juggling_balls_game.py. You need to make no further significant changes to the class Ball itself.

In the game, you need to call the ball’s .bat_up() method when the player clicks within a certain distance of the ball. You can use .onclick() from the turtle module for this:

# juggling_balls_game.py import turtle from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 batting_tolerance = 40 window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) ball = Ball(WIDTH, HEIGHT) while True: ball.move() window.update() turtle.done()

The variable batting_tolerance determines how close you need to be to the centre of the ball for the batting to take effect.

You define the function click_ball(x, y) with two parameters representing xy-coordinates. If the location of these coordinates is within the batting tolerance, then the ball’s .bat_up() method is called.

The call to window.onclick(click_ball) calls the function click_ball() and passes the xy-coordinates to it.

When you run this script, you’ll get the following output. You can test the code by clicking close to the ball:

Now, juggling one ball is nice and easy. How about juggling many balls?

5. Create More Instances of Ball Using a Timer

You can make a few changes to juggling_balls_game.py to have balls appear every few seconds. To achieve this, you’ll need to:

  1. Create a list to store all the Ball instances
  2. Create a new Ball instance every few seconds and add it to the list
  3. Move each of the Ball instances in the while loop
  4. Check all the Ball instances within click_ball() to see if the player clicked the ball

Start by tackling the first two of these steps. You define a tuple named spawn_interval_range. The program will create a new Ball every few seconds and add it to the list balls. The code will choose a new time interval from the range set in spawn_interval_range.

Since all the Ball instances are stored in the list balls, you’ll need to:

  • Add a for loop in the game loop so that all Ball instances move in each frame
  • Add a for loop in bat_up() to check all Ball instances for proximity to the click coordinates

You can update the code with these changes:

# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 batting_tolerance = 40 spawn_interval_range = (1, 5) window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] spawn_timer = time.time() spawn_interval = 0 while True: if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() window.update() turtle.done()

You start with a spawn_interval of 0 so that a Ball is created in the first iteration of the while loop. The instance of Ball is created and placed directly in the list balls. Each time a ball is created, the code generates a new spawn_interval from the range set at the top of the script.

You then loop through all the instances of Ball in balls to call their .move() method. You use a similar loop in click_ball()

This is where we can see the benefit of OOP and classes for this type of program. You create each ball using the same template: the class Ball. This means all Ball instances have the same data attributes and can access the same methods. However, the values of their data attributes can be different.

Each ball starts at a different location and has a different colour. The .velocity data attribute for each Ball has a different value, too. And therefore, each ball will move independently of the others.

By creating all balls from the same template, you ensure they’re all similar. But they’re not identical as they have different values for their data attributes.

You can run the script to see a basic version of the game which you can test:

The program creates balls every few seconds, and you can click any of them to bat them up. However, if you play this version long enough, you may notice some odd behaviour. In the next section, you’ll see why this happens and how to fix it.

6. Add a Frame Rate to Control the Game Speed

Have a look at this video of balls created by the current script. In particular, look at what happens to those balls that rise above the top edge of the screen. Does their movement look realistic?

Did you notice how, when the ball leaves the top of the screen, it immediately reappears at the same spot falling at high speed? It’s as though the ball is bouncing off the top of the screen. However, it’s not meant to do this. This is different from what you’d expect if the ball was still rising and falling normally while it was out of sight.

Let’s first see why this happens. Then you’ll fix this problem.

How long does one iteration of the while loop take?

Earlier, we discussed how the ball’s velocity is currently a value in pixels per frame. This means the ball will move by a certain number of pixels in each frame. You’re using the time it takes for each frame of the game as the unit of time.

However, there’s a problem with this logic. There are no guarantees that each frame takes the same amount of time. At the moment, the length of each frame is determined by how long it takes for the program to run one iteration of the while loop.

Look at the lines of code in the while loop. Which one do you think is the bottleneck that’s taking up most of the time?

Let’s try a small experiment. To make this a fair test, you’ll initialise the random number generator using a fixed seed so that the same “random” values are picked each time you run the program.

Let’s time 500 iterations of the while loop:

# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball random.seed(0) WIDTH = 600 HEIGHT = 600 batting_tolerance = 40 spawn_interval_range = (1, 5) window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] spawn_timer = time.time() spawn_interval = 0 start = time.time() count = 0 while count < 500: count += 1 if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() window.update() print(time.time() - start) turtle.done()

You run the while loop until count reaches 500 and print out the number of seconds it takes. When I run this on my system, the output is:

8.363317966461182

It took just over eight seconds to run 500 iterations of the loop.

Now, you can comment the line with window.update() at the end of the while loop. This will prevent the turtles from being displayed on the screen. All the remaining operations are still executed. The code now outputs the following:

0.004825115203857422

The same 500 iterations of the while loop now take around 5ms to run. That’s almost 2,000 times faster!

Updating the display on the screen is by far the slowest part of all the steps which occur in the while loop. Therefore, if there’s only one ball on the screen and it leaves the screen, the program no longer needs to display it. The loop speeds up significantly. This is why using pixels per frame as the ball’s velocity is flawed. The same pixels per frame value results in a much faster speed when the frame time is a lot shorter!

And even if this extreme change in frame time wasn’t an issue, for example, if you’re guaranteed to always have at least one ball on the screen at any one time, you still have no control over how fast the game runs or whether the frame rate will be constant as the game progresses.

How long do you want one iteration of the while loop to take?

To fix this problem and have more control over how long each frame takes, you can set your desired frame rate and then make sure each iteration of the while loop lasts for as long as needed. This is the fixed frame rate approach for running a game and works fine as long as an iteration in the while loop performs all its operations quicker than the frame time.

You can set the frame rate to 30 frames per second (fps), which is fine on most computers. However, you can choose a slower frame rate if needed. A frame rate of 30fps means that each frame takes 1/30 seconds—that’s 0.0333 seconds per frame.

Now that you’ve set the time each frame of the game should take, you can time how long the operations in the while loop take and add a delay at the end of the loop for the remaining time. This ensures each frame lasts 1/30 seconds.

You can implement the fixed frame rate in juggling_balls_game.py:

# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] spawn_timer = time.time() spawn_interval = 0 while True: frame_start_time = time.time() if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() window.update() loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) turtle.done()

You start the frame timer at the beginning of the while loop. Once all frame operations are complete, you assign the amount of time taken to the variable loop_time. If this is less than the required frame time, you add a delay for the remaining time.

When you run the script now, the game will run more smoothly as you have a fixed frame rate. The velocity of the balls, measured in pixels per frame, is now a consistent value since the frame time is fixed.

You’ve completed the main aspects of the game. However, you need to have a challenge to turn this into a proper game. In the next section, you’ll add a timer and an aim in the game.

7. Add a Timer and an End to the Game

To turn this into a game, you need to:

  • Add a timer
  • Keep track of how many balls are lost

You can start by adding a timer and displaying it in the title bar:

# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] game_timer = time.time() spawn_timer = time.time() spawn_interval = 0 while True: frame_start_time = time.time() if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() window.title(f"Time: {time.time() - game_timer:3.1f}") window.update() loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) turtle.done()

You start the game timer just before the game loop, and you display the time elapsed in the title bar in each frame of the game. The format specifier :3.1f in the f-string sets the width of the float displayed to three characters and the number of values after the decimal point to one.

Next, you can set the limit of balls you can lose before it’s ‘Game Over’! You must check whether a ball has left the screen through the bottom edge. You’ll recall you wrote the method .is_below_lower_edge() for the class Ball. This method returns a Boolean. Therefore, you can use it directly within an if statement:

# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) balls_lost_limit = 10 window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.tracer(0) def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] game_timer = time.time() spawn_timer = time.time() spawn_interval = 0 balls_lost = 0 while balls_lost < balls_lost_limit: frame_start_time = time.time() if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() for ball in balls: ball.move() if ball.is_below_lower_edge(): window.update() balls.remove(ball) turtle.turtles().remove(ball) balls_lost += 1 window.title( f"Time: {time.time() - game_timer:3.1f} | Balls lost: {balls_lost}" ) window.update() loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) turtle.done()

You check whether each instance of Ball has left the screen through the bottom edge as soon as you move the ball. If the ball fell through the bottom of the screen:

  • You update the screen so that the ball is no longer displayed. Otherwise, you may still see the top part of the ball at the bottom of the screen
  • You remove the ball from the list of all balls
  • You also need to remove the ball from the list of turtles kept by the turtle module to ensure the objects you no longer need don’t stay in memory
  • You add 1 to the number of balls you’ve lost

You also show the number of balls lost by adding this value to the title bar along with the amount of time elapsed. The while loop will stop iterating once you’ve lost ten balls, which is the value of balls_lost_limit.

You now have a functioning game. But you can add some finishing touches to make it better.

8. Complete the Game With Finishing Touches

When writing these types of games, the “finishing touches” can take as little or as long as you want. You can always do more refining and further tweaks to make the game look and feel better.

You’ll only make a few finishing touches in this article, but you can refine your game further if you wish:

  • Change the background colour to dark grey
  • Add a final screen to show the time taken in the game
  • Ensure the balls are not created too close to the sides of the screen

You can change the background colour using .bgcolor(), which is one of the methods in the turtle module.

To add a final message on the screen, you can update the screen after the while loop and use .write(), which is a method of the Turtle class:

# juggling_balls_game.py import turtle import time import random from juggling_ball import Ball # Game parameters WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) balls_lost_limit = 10 # Setup window window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.bgcolor(0.15, 0.15, 0.15) window.tracer(0) # Batting function def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] # Game loop game_timer = time.time() spawn_timer = time.time() spawn_interval = 0 balls_lost = 0 while balls_lost < balls_lost_limit: frame_start_time = time.time() # Spawn new ball if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() # Move balls for ball in balls: ball.move() if ball.is_below_lower_edge(): window.update() balls.remove(ball) turtle.turtles().remove(ball) balls_lost += 1 # Update window title window.title( f"Time: {time.time() - game_timer:3.1f} | Balls lost: {balls_lost}" ) # Refresh screen window.update() # Control frame rate loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) # Game over final_time = time.time() - game_timer # Hide balls for ball in balls: ball.hideturtle() window.update() # Show game over text text = turtle.Turtle() text.hideturtle() text.color("white") text.write( f"Game Over | Time taken: {final_time:2.1f}", align="center", font=("Courier", 20, "normal") ) turtle.done()

After the while loop, when the game ends, you stop the game timer and clear all the remaining balls from the screen. You create a Turtle object to write the final message on the screen.

To add a border at the edge of the screen to make sure no balls are created to close too the edge, you can go back to juggling_ball.py and modify the region where a ball can be created:

# juggling_ball.py import random import turtle class Ball(turtle.Turtle): max_velocity = 5 gravity = 0.07 bat_velocity_change = 8 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint( (-self.width // 2) + 20, (self.width // 2) - 20 ), random.randint( (-self.height // 2) + 20, (self.height // 2) - 20 ), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def move(self): self.forward(self.velocity) self.fall() def fall(self): self.velocity -= self.gravity def is_below_lower_edge(self): if self.ycor() < -self.height // 2: self.hideturtle() return True return False def bat_up(self): self.velocity += self.bat_velocity_change

And this completes the game! Unless you want to keep tweaking and adding more features. Here’s what the game looks like now:

Final Words

In this project, you created a 2D Python game. You started by creating a class called Ball, which inherits from turtle.Turtle. This means that you didn’t have to start from scratch to create Ball. Instead, you built on top of an existing class.

Here’s a summary of the key stages when writing this game:

  • Create a class named Ball and set up what should happen when you create a ball
  • Make the ball move forward
  • Add gravity to pull the ball downwards
  • Add the ability to bat the ball upwards
  • Create more balls, with each ball created after a certain time interval
  • Control the speed of the game by setting a frame rate
  • Add a timer and an end to the game
  • Add finishing touches to the game

You first create the class Ball and add data attributes and methods. When you create an instance of Ball, the object you create already has all the properties and functionality you need a ball to have.

Once you defined the class Ball, writing the game is simpler because each Ball instance carries everything you need it to do with it.

And now, can you beat your high score in the game?

Get the latest blog updates

No spam promise. You’ll get an email when a new blog post is published

Appendix

Here are the final versions of juggling_ball.py and juggling_balls_game.py:

juggling_ball.py # juggling_ball.py import random import turtle class Ball(turtle.Turtle): """Create balls to use for juggling""" max_velocity = 5 gravity = 0.07 bat_velocity_change = 8 def __init__(self, width, height): super().__init__() self.width = width self.height = height self.shape("circle") self.color( random.random(), random.random(), random.random(), ) self.penup() self.setposition( random.randint( (-self.width // 2) + 20, (self.width // 2) - 20 ), random.randint( (-self.height // 2) + 20, (self.height // 2) - 20 ), ) self.setheading(90) self.velocity = random.randint(1, self.max_velocity) def __repr__(self): return f"{type(self).__name__}({self.width}, {self.height})" def move(self): """Move the ball forward by the amount required in a frame""" self.forward(self.velocity) self.fall() def fall(self): """Take the effect of gravity into account""" self.velocity -= self.gravity def is_below_lower_edge(self): """ Check is object fell through the bottom :return: True if object fell through the bottom. False if object is still above the bottom edge """ if self.ycor() < -self.height // 2: self.hideturtle() return True return False def bat_up(self): """Bat the ball upwards by increasing its velocity""" self.velocity += self.bat_velocity_change juggling_balls_game.py # juggling_balls_game.py import turtle import time import random from juggling_ball import Ball # Game parameters WIDTH = 600 HEIGHT = 600 frame_rate = 30 batting_tolerance = 40 spawn_interval_range = (1, 5) balls_lost_limit = 10 # Setup window window = turtle.Screen() window.setup(WIDTH, HEIGHT) window.bgcolor(0.15, 0.15, 0.15) window.tracer(0) # Batting function def click_ball(x, y): for ball in balls: if ball.distance(x, y) < batting_tolerance: ball.bat_up() window.onclick(click_ball) balls = [] # Game loop game_timer = time.time() spawn_timer = time.time() spawn_interval = 0 balls_lost = 0 while balls_lost < balls_lost_limit: frame_start_time = time.time() # Spawn new ball if time.time() - spawn_timer > spawn_interval: balls.append(Ball(WIDTH, HEIGHT)) spawn_interval = random.randint(*spawn_interval_range) spawn_timer = time.time() # Move balls for ball in balls: ball.move() if ball.is_below_lower_edge(): window.update() balls.remove(ball) turtle.turtles().remove(ball) balls_lost += 1 # Update window title window.title( f"Time: {time.time() - game_timer:3.1f} | Balls lost: {balls_lost}" ) # Refresh screen window.update() # Control frame rate loop_time = time.time() - frame_start_time if loop_time < 1 / frame_rate: time.sleep(1 / frame_rate - loop_time) # Game over final_time = time.time() - game_timer # Hide balls for ball in balls: ball.hideturtle() window.update() # Show game over text text = turtle.Turtle() text.hideturtle() text.color("white") text.write( f"Game Over | Time taken: {final_time:2.1f}", align="center", font=("Courier", 20, "normal") ) turtle.done()

The post Anatomy of a 2D Game using Python’s turtle and Object-Oriented Programming appeared first on The Python Coding Book.

Categories: FLOSS Project Planets

Brett Cannon: How virtual environments work

Sun, 2023-03-12 13:18

After needing to do a deep dive on the venv module (which I will explain later in this blog post as to why), I thought I would explain how virtual environments work to help demystify them.

Why do virtual environments exist?

Back in my the day, there was no concept of environments in Python: all you had was your Python installation and the current directory. That meant when you installed something you either installed it globally into your Python interpreter or you just dumped it into the current directory. Both of these approaches had their drawbacks.

Installing globally meant you didn&apost have any isolation between your projects. This led to issues like version conflicts between what one of your projects might need compared to another one. It also meant you had no idea what requirements your project actually had since you had no way of actually testing your assumptions of what you needed. This was an issue if you needed to share you code with someone else as you didn&apost have a way to test that you weren&apost accidentally wrong about what your dependencies were.

Installing into your local directory didn&apost isolate your installs based on Python version or interpreter version (or even interpreter build type, back when you had to compile your extension modules differently for debug and release builds of Python). So while you could install everything into the same directory as your own code (which you did, and thus didn&apost use src directory layouts for simplicity), there wasn&apost a way to install different wheels for each Python interpreter you had on your machine so you could have multiple environments per project (I&aposm glossing over the fact that back in my the day you also didn&apost have wheels or editable installs).

Enter virtual environments. Suddenly you had a way to install projects as a group that was tied to a specific Python interpreter. That got us the isolation/separation of only installing things you depend on (and being able to verify that through your testing), as well has having as many environments as you want to go with your projects (e.g. an environment for each version of Python that you support). So all sorts of wins! It&aposs an important feature to have while doing development (which is why it can be rather frustrating for users when Python distributors leave venv out).

How do virtual environments work?&#x1F4A1;Virtual environments are different than conda environments (in my opinion; some people disagree with me on this view). The key difference is that conda environments allow projects to install arbitrary shell scripts which are run when you activate a conda environment (which is done implicitly when you use conda run). This is why you are always expected to activate a conda environment, as some conda packages require those those shell scripts run. I won&apost be covering conda environments in this post.Their structure

There are two parts to virtual environments: their directories and their configuration file. As a running example, I&aposm going to assume you ran the command py -m venv --without-pip .venv in some directory on a Unix-based OS (you can substitute py with whatever Python interpreter you want, including the Python Launcher for Unix).

❗For simplicity I&aposm going to focus on the Unix case and not cover Windows in depth.

A virtual environment has 3 directories and potentially a symlink in the virtual environment directory (i.e. within .venv):

  1. bin ( Scripts on Windows)
  2. include ( Include on Windows)
  3. lib/pythonX.Y/site-packages where X.Y is the Python version (Lib/site-packages on Windows)
  4. lib64 symlinked to lib if you&aposre using a 64-bit build of Python that&aposs on a POSIX-based OS that&aposs not macOS

The Python executable for the virtual environment ends up in bin as various symlinks back to the original interpreter (e.g. .venv/bin/python is a symlink; Windows has a different story). The site-packages directory is where projects get installed into the virtual environment (including pip if you choose to have it installed into the virtual environment). The include directory is for any header files that might get installed for some reason from a project. The lib64 symlink is for consistency on those Unix OSs where they have such directories.

The configuration file is pyvenv.cfg and it lives at the top of your virtual environment directory (e.v. .venv/pyvenv.cfg). As of Python 3.11, it contains a few entries:

  1. home (the directory where the executable used to create the virtual environment lives; os.path.dirname(sys._base_executable))
  2. include-system-packages (should the global site-packages be included, effectively turning off isolation?)
  3. version (the Python version down to the micro version, but not with the release level, e.g. 3.12.0, but not 3.12.0a6)
  4. executable (the executable used to create the virtual environment; os.path.realpath(sys._base_executable))
  5. command (the CLI command that could have recreated the virtual environment)

On my machine, the pyvenv.cfg contents are:

home = /home/linuxbrew/.linuxbrew/opt/python@3.11/bin include-system-site-packages = false version = 3.11.2 executable = /home/linuxbrew/.linuxbrew/Cellar/python@3.11/3.11.2_1/bin/python3.11 command = /home/linuxbrew/.linuxbrew/opt/python@3.11/bin/python3.11 -m venv --without-pip /tmp/.venvExample pyvenv.cfg

One interesting thing to note is pyvenv.cfg is not a valid INI file according to the configparser module due to lacking any sections. To read fields in the file you are expected to use line.partition("=") and to strip the resulting key and value.

And that&aposs all there is to a virtual environment! When you don&apost install pip they are extremely fast to create: 3 files, a symlink, and a single file. And they are simple enough you can probably create one manually.

One point I would like to make is how virtual environments are designed to be disposable and not relocatable. Because of their simplicity, virtual environments are viewed as something you can throw away and recreate quickly (if it takes your OS a long time to create 3 directories, a symlink, and a file consisting of 292 bytes like on my machine, you have bigger problems to worry about than virtual environment relocation &#x1F609;). Unfortunately, people tend to conflate environment creation with package installation, when they are in fact two separate things. What projects you choose to install with which installer is actually separate from environment creation and probably influences your "getting started" time the most.

How Python uses a virtual environment

During start-up, Python automatically calls the site.main() function (unless you specify the -S flag). That function calls site.venv() which handles setting up your Python executable to use the virtual environment appropriately. Specifically, the site module:

  1. Looks for pyvenv.cfg in either the same or parent directory as the running executable (which is not resolved, so the location of the symlink is used)
  2. Looks for include-system-site-packages in pyvenv.cfg to decide whether the system site-packages ends up on sys.path
  3. Sets sys._home if home is found in pyvenv.cfg (sys._home is used by sysconfig)

That&aposs it! It&aposs a surprisingly simple mechanism for what it accomplishes.

When thing to notice here about how all of this works is virtual environment activation is optional. Because the site module works off of the symlink to the executable in the virtual environment to resolve everything, activation is just a convenience. Honestly, all the activation scripts do are:

  1. Puts the bin/ (or Scripts/) directory at the front of your PATH environment variable
  2. Sets VIRTUAL_ENV to the directory containing your virtual environment
  3. Tweaks your shell prompt to let you know your PATH has been changed
  4. Registers a deactivate shell function which undoes the other steps

In the end, whether you type python after activation or .venv/bin/python makes no difference to Python. Some tooling like the Python extension for VS Code or the Python Launcher for Unix may check for VIRTUAL_ENV to pick up on your intent to use a virtual environment, but it doesn&apost influence Python itself.

Introducing microvenv

In the Python extension for VS Code, we have an issue where Python beginners end up on Debian or a Debian-based distro like Ubuntu and want to create a virtual environment. Due to Debian removing venv from the default Python install and beginners not realizing there was more to install than python3, they often end up failing at creating a virtual environment  (at least initially as you can install python3-venv separately; in the next version of Debian there will be a python3-full package you can install which will include venv and pip, but it will probably take a while for all the instructions online to be updated to suggest that over python3). We believe the lack of venv is a problem as beginners should be using environments, but asking them to install yet more software can be a barrier to getting started (I&aposm also ignoring the fact pip isn&apost installed by default on Debian either which also complicates the getting started experience for beginners).

But venv is not shipped as a separate part of Python&aposs stdlib, so we can&apost simply install it from PyPI somehow or easily ship it as part of the Python extension to work around this. Since venv is in the stdlib, it&aposs developed along with the version of Python it ships with, so there&aposs no single copy which is fully compatible with all maintained versions of Python (e.g. Python 3.11 added support to use sysconfig to get the directories to create for a virtual environment, various fields in pyvenv.cfg have been added over time, use new language features may be used, etc.). While we could ship a copy of venv for every maintained version of Python, we potentially would have to ship for every micro release to guarantee we always had a working copy, and that&aposs a lot of upstream tracking to do. And even if we only shipped copies from minor release of Python, we would still have to track every micro release in case a bug in venv was fixed.

Hence I have created microvenv. It is a project which provides a single .py file which you use to create a minimal virtual environment. You can either execute it as a script or call its create() function that is analogous to venv.create(). It&aposs also compatible with all maintained versions of Python. As I (hopefully) showed above, creating a virtual environment is actually straight-forward, so I was able to replicate the necessary bits in less than 100 lines of Python code (specifically 87 lines in the 2023.1.1 release). That actually makes it small enough to pass in via python -c, which means it could be embedded in a binary as a string constant and passed as an argument when executing a Python executable as a subprocess. Hopefully that means a tool could guarantee it can always construct a virtual environment somehow.

To keep it microvenv simple, small, and maintainable, it does not contain any activation scripts. I personally don&apost want to be a shell script expert for multiple shells, nor do I want to track the upstream activation scripts (and they do change in case you were thinking "it shouldn&apost be that hard to track"). Also, in VS Code we are actually working towards implicitly activating virtual environments by updating your environment variables directly instead of executing any activation shell scripts, so the shell scripts aren&apost needed for our use case (we are actively moving away from using any activation scripts where we can as we have run into race condition problems with them when sending the command to the shell; thank goodness of conda run, but we also know people still want an activated terminal).

I&aposm also skipping Windows support because we have found the lack of venv to be a unique problem for Linux in general, and Debian-based distros specifically.

I honestly don&apost expect anyone except tool providers to use microvenv, but since it could be useful to others beyond VS Code, I decided it was worth releasing on its own. I also expect anyone using the project to only use it as a fallback when venv is not available (which you can deduce by running py -c "from importlib.util import find_spec; print(find_spec(&aposvenv&apos) is not None)"). And before anyone asks why we don&apost just use virtualenv, its wheel is 8.7MB compared to microvenv at 3.9KB; 0.05% the size, or 2175x smaller. Granted, a good chunk of what makes up virtualen&aposs wheel is probably from shipping pip and setuptools in the wheel for fast installation of those projects after virtual environment creation, but we also acknowledge our need for a small, portable, single-file virtual environment creator is rather niche and something virtualenv currently doesn&apost support (for good reason).

Our plan for the Python extension for VS Code is to use microvenv as a fallback mechanism for our Python: Create Environment command (FYI we also plan to bootstrap pip via its pip.pyz file from bootstrap.pypa.io by downloading it on-demand, which is luckily less than 2MB). That way we can start suggesting to users in various UX flows to create and use an environment when one isn&apost already being used (as appropriate, of course). We want beginners to learn about environments if they don&apost already know about them and also remind experienced users when they may have accidentally forgotten to create an environment for their workspace. That way people get the benefit of (virtual) environments with as little friction as possible.

Categories: FLOSS Project Planets

Kushal Das: Everything Curl from Daniel

Sun, 2023-03-12 09:07

Everything Curl is a book about everything related to Curl. I read the book before online. But, now I am proud to have a physical copy signed by the author :)

It was a good evening, along with some amazing fish in dinner and wine and lots of chat about life in general.

Categories: FLOSS Project Planets

Pages