27 Nov 2025
Planet Python
Python GUIs: Getting Started With NiceGUI for Web UI Development in Python — Your First Steps With the NiceGUI Library for Web UI Development
NiceGUI is a Python library that allows developers to create interactive web applications with minimal effort. It's intuitive and easy to use. It provides a high-level interface to build modern web-based graphical user interfaces (GUIs) without requiring deep knowledge of web technologies like HTML, CSS, or JavaScript.
In this article, you'll learn how to use NiceGUI to develop web apps with Python. You'll begin with an introduction to NiceGUI and its capabilities. Then, you'll learn how to create a simple NiceGUI app in Python and explore the basics of the framework's components. Finally, you'll use NiceGUI to handle events and customize your app's appearance.
To get the most out of this tutorial, you should have a basic knowledge of Python. Familiarity with general GUI programming concepts, such as event handling, widgets, and layouts, will also be beneficial.
Installing NiceGUI
Before using any third-party library like NiceGUI, you must install it in your working environment. Installing NiceGUI is as quick as running the python -m pip install nicegui command in your terminal or command line. This command will install the library from the Python Package Index (PyPI).
It's a good practice to use a Python virtual environment to manage dependencies for your project. To create and activate a virtual environment, open a command line or terminal window and run the following commands in your working directory:
- Windows
- macOS
- Linux
PS> python -m venv .\venv
PS> .\venv\Scripts\activate
$ python -m venv venv/
$ source venv/bin/activate
$ python3 -m venv venv/
$ source venv/bin/activate
The first command will create a folder called venv/ containing a Python virtual environment. The Python version in this environment will match the version you have installed on your system.
Once your virtual environment is active, install NiceGUI by running:
(venv) $ python -m pip install nicegui
With this command, you've installed NiceGUI in your active Python virtual environment and are ready to start building applications.
Writing Your First NiceGUI App in Python
Let's create our first app with NiceGUI and Python. We'll display the traditional "Hello, World!" message in a web browser. To create a minimal NiceGUI app, follow these steps:
- Import the
niceguimodule. - Create a GUI element.
- Run the application using the
run()method.
Create a Python file named app.py and add the following code:
from nicegui import ui
ui.label('Hello, World!').classes('text-h1')
ui.run()
This code defines a web application whose UI consists of a label showing the Hello, World! message. To create the label, we use the ui.label element. The call to ui.run() starts the app.
Run the application by executing the following command in your terminal:
(venv) $ python app.py
This will open your default browser, showing a page like the one below:
First NiceGUI Application
Congratulations! You've just written your first NiceGUI web app using Python. The next step is to explore some features of NiceGUI that will allow you to create fully functional web applications.
If the above command doesn't open the app in your browser, then go ahead and navigate to http://localhost:8080.
Exploring NiceGUI Graphical Elements
NiceGUI elements are the building blocks that we'll arrange to create pages. They represent UI components like buttons, labels, text inputs, and more. The elements are classified into the following categories:
In the following sections, you'll code simple examples showcasing a sample of each category's graphical elements.
Text Elements
NiceGUI also has a rich set of text elements that allow you to display text in several ways. This set includes some of the following elements:
The following demo app shows how to create some of these text elements:
from nicegui import ui
# Text elements
ui.label("Label")
ui.link("PythonGUIs", "https://pythonguis.com")
ui.chat_message("Hello, World!", name="PythonGUIs Chatbot")
ui.markdown(
"""
# Markdown Heading 1
**bold text**
*italic text*
`code`
"""
)
ui.restructured_text(
"""
==========================
reStructuredText Heading 1
==========================
**bold text**
*italic text*
``code``
"""
)
ui.html("<strong>bold text using HTML tags</strong>")
ui.run(title="NiceGUI Text Elements")
In this example, we create a simple web interface showcasing various text elements. The page shows several text elements, including a basic label, a hyperlink, a chatbot message, and formatted text using the Markdown and reStructuredText markup languages. Finally, it shows some raw HTML.
Each text element allows us to present textual content on the page in a specific way or format, which gives us a lot of flexibility for designing modern web UIs.
Run it! Your browser will open with a page that looks like the following.
Text Elements Demo App in NiceGUI
Control Elements
When it comes to control elements, NiceGUI offers a variety of them. As their name suggests, these elements allow us to control how our web UI behaves. Here are some of the most common control elements available in NiceGUI:
- Buttons
- Dropdown lists
- Toggle buttons
- Radio buttons
- Checkboxes
- Sliders
- Switches
- Text inputs
- Text areas
- Date input
The demo app below showcases some of these control elements:
from nicegui import ui
# Control elements
ui.button("Button")
with ui.dropdown_button("Edit", icon="edit", auto_close=True):
ui.item("Copy")
ui.item("Paste")
ui.item("Cut")
ui.toggle(["ON", "OFF"], value="ON")
ui.radio(["NiceGUI", "PyQt6", "PySide6"], value="NiceGUI").props("inline")
ui.checkbox("Enable Feature")
ui.slider(min=0, max=100, value=50, step=5)
ui.switch("Dark Mode")
ui.input("Your Name")
ui.number("Age", min=0, max=120, value=25, step=1)
ui.date(value="2025-04-11")
ui.run(title="NiceGUI Control Elements")
In this app, we include several control elements: a button, a dropdown menu with editing options (Copy, Paste, Cut), and a toggle switch between ON and OFF states. We also have a radio button group to choose between GUI frameworks (NiceGUI, PyQt6, PySide6), a checkbox labeled Enable Feature, and a slider to select a numeric value within a range.
Further down, we have a switch to toggle Dark Mode, a text input field for entering a name, a number input for providing age, and a date picker. Each of these controls has its own properties and methods that you can tweak to customize your web interfaces using Python and NiceGUI.
Note that the elements on this app don't perform any action. Later in this tutorial, you'll learn about events and actions. For now, we're just showcasing some of the available graphical elements of NiceGUI.
Run it! You'll get a page that will look something like the following.
Text Elements Demo App in NiceGUI
Data Elements
If you're in the data science field, then you'll be thrilled with the variety of data elements that NiceGUI offers. You'll find elements for some of the following tasks:
- Representing data in a tabular format
- Creating plots and charts
- Building different types of progress charts
- Displaying 3D objects
- Using maps
- Creating tree and log views
- Presenting and editing text in different formats, including plain text, code, and JSON
Here's a quick NiceGUI app where we use a table and a plot to present temperature measurements against time:
from matplotlib import pyplot as plt
from nicegui import ui
# Data elements
time = [1, 2, 3, 4, 5, 6]
temperature = [30, 32, 34, 32, 33, 31]
columns = [
{
"name": "time",
"label": "Time (min)",
"field": "time",
"sortable": True,
"align": "right",
},
{
"name": "temperature",
"label": "Temperature (ºC)",
"field": "temperature",
"required": True,
"align": "right",
},
]
rows = [
{"temperature": temperature, "time": time}
for temperature, time in zip(temperature, time)
]
ui.table(columns=columns, rows=rows, row_key="name")
with ui.pyplot(figsize=(5, 4)):
plt.plot(time, temperature, "-o", color="blue", label="Temperature")
plt.title("Temperature vs Time")
plt.xlabel("Time (min)")
plt.ylabel("Temperature (ºC)")
plt.ylim(25, 40)
plt.legend()
ui.run(title="NiceGUI Data Elements")
In this example, we create a web interface that displays a table and a line plot. The data is stored in two lists: one for time (in minutes) and one for temperature (in degrees Celsius). These values are formatted into a table with columns for time and temperature. To render the table, we use the ui.table element.
Below the table, we create a Matplotlib plot of temperature versus time and embed it in the ui.pyplot element. The plot has a title, axis labels, and a legend.
Run it! You'll get a page that looks something like the following.
Data Elements Demo App in NiceGUI
Audiovisual Elements
NiceGUI also has some elements that allow us to display audiovisual content in our web UIs. The audiovisual content may include some of the following:
Below is a small demo app that shows how to add a local image to your NiceGUI-based web application:
from nicegui import ui
with ui.image("./otje.jpg"):
ui.label("Otje the cat!").classes(
"absolute-bottom text-subtitle2 text-center"
)
ui.run(title="NiceGUI Audiovisual Elements")
In this example, we use the ui.image element to display a local image on your NiceGUI app. The image will show a subtitle at the bottom.
NiceGUI elements provide the classes() method, which allows you to apply Tailwind CSS classes to the target element. To learn more about using CSS for styling your NiceGUI apps, check the Styling & Appearance section in the official documentation.
Run it! You'll get a page that looks something like the following.

Audiovisual Elements Demo App in NiceGUI
Laying Out Pages in NiceGUI
Laying out a GUI so that every graphical component is in the right place is a fundamental step in any GUI project. NiceGUI offers several elements that allow us to arrange graphical elements to build a nice-looking UI for our web apps.
Here are some of the most common layout elements:
- Cards wrap another element in a frame.
- Column arranges elements vertically.
- Row arranges elements horizontally.
- Grid organizes elements in a grid of rows and columns.
- List displays a list of elements.
- Tabs organize elements in dedicated tabs.
You'll find several other elements that allow you to tweak how your app's UI looks. Below is a demo app that combines a few of these elements to create a minimal but well-organized user profile form:
from nicegui import ui
with ui.card().classes("w-full max-w-3xl mx-auto shadow-lg"):
ui.label("Profile Page").classes("text-xl font-bold")
with ui.row().classes("w-full"):
with ui.card():
ui.image("./profile.png")
with ui.card_section():
ui.label("Profile Image").classes("text-center font-bold")
ui.button("Change Image", icon="photo_camera")
with ui.card().classes("flex-grow"):
with ui.column().classes("w-full"):
name_input = ui.input(
placeholder="Your Name",
).classes("w-full")
gender_select = ui.select(
["Male", "Female", "Other"],
).classes("w-full")
eye_color_input = ui.input(
placeholder="Eye Color",
).classes("w-full")
height_input = ui.number(
min=0,
max=250,
value=170,
step=1,
).classes("w-full")
weight_input = ui.number(
min=0,
max=500,
value=60,
step=0.1,
).classes("w-full")
with ui.row().classes("justify-end gap-2 q-mt-lg"):
ui.button("Reset", icon="refresh").props("outline")
ui.button("Save", icon="save").props("color=primary")
ui.run(title="NiceGUI Layout Elements")
In this app, we create a clean, responsive profile information page using a layout based on the ui.card element. We center the profile form and cap it at a maximum width for better readability on larger screens.
We organize the elements into two main sections:
-
A profile image card on the left and a form area on the right. The left section displays a profile picture using the
ui.imageelement with a Change Image button underneath. -
A series of input fields for personal information, including the name in a
ui.inputelement, the gender in aui.selectelement, the eye color in aui.inputelement, and the height and weight inui.numberelements. At the bottom of the form, we add two buttons: Reset and Save.
We use consistent CSS styling throughout the layout to guarantee proper spacing, shadows, and responsive controls. This ensures that the interface looks professional and works well across different screen sizes.
Run it! Here's how the form looks on the browser.
A Demo Profile Page Layout in NiceGUI
Handling Events and Actions in NiceGUI
In NiceGUI, you can handle events like mouse clicks, keystrokes, and similar ones as you can in other GUI frameworks. Elements typically have arguments like on_click and on_change that are the most direct and convenient way to bind events to actions.
Here's a quick app that shows how to make a NiceGUI app perform actions in response to events:
from nicegui import ui
def on_button_click():
ui.notify("Button was clicked!")
def on_checkbox_change(event):
state = "checked" if event.value else "unchecked"
ui.notify(f"Checkbox is {state}")
def on_slider_change(event):
ui.notify(f"Slider value: {event.value}")
def on_input_change(event):
ui.notify(f"Input changed to: {event.value}")
ui.label("Event Handling Demo")
ui.button("Click Me", on_click=on_button_click)
ui.checkbox("Check Me", on_change=on_checkbox_change)
ui.slider(min=0, max=10, value=5, on_change=on_slider_change)
ui.input("Type something", on_change=on_input_change)
ui.run(title="NiceGUI Events & Actions Demo")
In this app, we first define four functions we'll use as actions. When we create the control elements, we use the appropriate argument to bind an event to a function. For example, in the ui.button element, we use the on_click argument, which makes the button call the associated function when we click it.
We do something similar with the other elements, but use different arguments depending on the element's supported events.
You can check the documentation of elements to learn about the specific events they can handle.
Using the on_* type of arguments is not the only way to bind events to actions. You can also use the on() method, which allows you to attach event handlers manually. This approach is handy for less common events or when you want to attach multiple handlers.
Here's a quick example:
from nicegui import ui
def on_click(event):
ui.notify(f"Button was clicked!")
def on_hover(event):
ui.notify(f"Button was hovered!")
button = ui.button("Button")
button.on("click", on_click)
button.on("mouseover", on_hover)
ui.run()
In this example, we create a small web app with a single button that responds to two different events. When you click the button, the on_click() function triggers a notification. Similarly, when you hover the mouse over the button, the on_hover() function displays a notification.
To bind the events to the corresponding function, we use the on() method. The first argument is a string representing the name of the target event. The second argument is the function that we want to run when the event occurs.
Conclusion
In this tutorial, you've learned the basics of creating web applications with NiceGUI, a powerful Python library for web GUI development.
You've explored common elements, layouts, and event handling. This gives you the foundation to build modern and interactive web interfaces. For further exploration and advanced features, refer to the official NiceGUI documentation.
27 Nov 2025 6:00am GMT
26 Nov 2025
Planet Python
Real Python: How to Convert Bytes to Strings in Python
Converting bytes into readable strings in Python is an effective way to work with raw bytes fetched from files, databases, or APIs. You can do this in just three steps using the bytes.decode() method. This guide lets you convert byte data into clean text, giving you a result similar to what's shown in the following example:
>>> binary_data = bytes([100, 195, 169, 106, 195, 160, 32, 118, 117])
>>> binary_data.decode(encoding="utf-8")
'déjà vu'
By interpreting the bytes according to a specific character encoding, Python transforms numeric byte values into their corresponding characters. This allows you to seamlessly handle data loaded from files, network responses, or other binary sources and work with it as normal text.
A byte is a fundamental unit of digital storage and processing. Composed of eight bits (binary digits), it's a basic building block of data in computing. Bytes represent a vast range of data types and are used extensively in data storage and in networking. It's important to be able to manage and handle bytes where they come up. Sometimes they need to be converted into strings for further use or comprehensibility.
By the end of this guide, you'll be able to convert Python bytes to strings so that you can work with byte data in a human-readable format.
Get Your Code: Click here to download the free sample code that you'll use to convert bytes to strings in Python.
Step 1: Obtain the Byte Data
Before converting bytes to strings, you'll need some actual bytes to work with. In everyday programming, you may not have to deal with bytes directly at all, as Python often handles their encoding and decoding behind the scenes.
Binary data exchanged over the internet can be expressed in different formats, such as raw binary streams, Base64, or hexadecimal strings. When you browse a web page, download a file, or chat with a colleague, the data that emerges travels as numeric bytes before it is interpreted as text that you can read.
In this step, however, you'll obtain byte data using one of two approaches:
- Using the
bytesliteral (b"") - Using the
urllibpackage
You'll soon find that using the urllib package requires that you go online. You can, however, create bytes manually without reaching out to the internet at all. You do this by prefixing a string with b, which creates a bytes literal containing the text inside:
raw_bytes = b"These are some interesting bytes"
You may be wondering why you have to create a bytes object at all from strings that you can read. This isn't just a convenience. While bytes and strings share most of their methods, you can't mix them freely. If you pass string arguments to a bytes method, then you'll get an error:
>>> raw_bytes = b"These are some interesting bytes"
>>> raw_bytes.replace("y", "o")
Traceback (most recent call last):
...
TypeError: a bytes-like object is required, not 'str'
A bytes object only accepts other bytes-like objects as arguments. If you try to use a string like "y" with a bytes method, then Python raises a TypeError. To work with raw binary data, you must explicitly use bytes, not strings.
Note that you can represent the same information using alternative numeral formats, including binary, decimal, or hexadecimal. For instance, in the following code snippet, you convert the same bytes object from the above code example into hexadecimal and decimal formats:
Read the full article at https://realpython.com/convert-python-bytes-to-strings/ »
[ 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 ]
26 Nov 2025 2:00pm GMT
25 Nov 2025
Planet Python
PyCoder’s Weekly: Issue #710: FastAPI, Floodfill, 20 Years of Django, and More (Nov. 25, 2025)
#710 - NOVEMBER 25, 2025
View in Browser »
Serve a Website With FastAPI Using HTML and Jinja2
Use FastAPI to render Jinja2 templates and serve dynamic sites with HTML, CSS, and JavaScript, then add a color picker that copies hex codes.
REAL PYTHON
Quiz: Serve a Website With FastAPI Using HTML and Jinja2
Review how to build dynamic websites with FastAPI and Jinja2, and serve HTML, CSS, and JS with HTMLResponse and StaticFiles.
REAL PYTHON
Floodfill Algorithm in Python
The floodfill algorithm is used to fill a color in a bounded area. Learn how it works and how to implement it in Python.
RODRIGO GIRÃO SERRÃO
New guide: The Engineering Leader AI Imperative
Augment Code's new guide features real frameworks to lead your engineering team to systematic transformation: 30% faster PR velocity, 40% reduction in merge times, and 10x task speed-ups across teams. Learn from CTOs at Drata, Webflow, and Tilt who've scaled AI across 100+ developer teams →
AUGMENT CODE sponsor
Twenty Years of Django Releases
On November 16th, Django celebrated its 20th anniversary. This quick post highlights a few stats along the way.
DJANGO SOFTWARE FOUNDATION
Python Jobs
Python Video Course Instructor (Anywhere)
Python Tutorial Writer (Anywhere)
Articles & Tutorials
The Uselessness of "Fast" and "Slow" in Programming
"One of the unique aspects of software is how it spans such a large number of orders of magnitude." The huge difference makes the terms "fast" and "slow" arbitrary. Read on to discover how this effects our thinking as programmers and what mistakes it can cause.
JEREMY BOWERS
New Login Verification for TOTP-based Logins
Previously, when logging into PyPI with a Time-based One-Time Password (TOTP) authenticator, a successful response was sufficient. Now, if you log in from a new device, PyPI will send a verification email. Read all about how this protects PyPI users.
DUSTIN INGRAM
A Better Way to Watch Your Python Apps-Now with AI in the Loop
Scout's local MCP server lets your AI assistant query real Python telemetry. Call endpoints like get_app_error_groups or get_app_endpoint_traces to surface top errors, latency, and backtraces-no dashboards, no tab-switching, all from chat →
SCOUT APM sponsor
Manim: Create Mathematical Animations
Learn how to use Manim, the animation engine behind 3Blue1Brown, to create clear and compelling visual explanations with Python. This walkthrough shows how you can turn equations and concepts into smooth animations for data science storytelling.
CODECUT.AI • Shared by Khuyen Tran
The Varying Strictness of TypedDict
Brett came across an unexpected typing error when using Pyrefly on his code. He verified it with Pyright, and found the same problem. This post describes the issue and why ty let it pass.
BRETT CANNON
Exploring Class Attributes That Aren't Really Class Attributes
Syntax used for data classes and typing.NamedTuple confused Stephen when first learning it. Learn why, and how he cleared up his understanding.
STEPHEN GRUPPETTA
Unnecessary Parentheses in Python
Python's ability to use parentheses for grouping can often confuse new Python users into over-using parentheses in ways that they shouldn't be used.
TREY HUNNER
Build an MCP Client to Test Servers From Your Terminal
Follow this Python project to build an MCP client that discovers MCP server capabilities and feeds an AI-powered chat with tool calls.
REAL PYTHON
Quiz: Build an MCP Client to Test Servers From Your Terminal
Learn how to create a Python MCP client, start an AI-powered chat session, and run it from the command line. Check your understanding.
REAL PYTHON
Break Out of Loops With Python's break Keyword
Learn how Python's break lets you exit for and while loops early, with practical demos from simple games to everyday data tasks.
REAL PYTHON course
Cursor vs. Claude for Django Development
This article looks at how Cursor and Claude compare when developing a Django application.
ŠPELA GIACOMELLI
Projects & Code
Events
Weekly Real Python Office Hours Q&A (Virtual)
November 26, 2025
REALPYTHON.COM
PyDelhi User Group Meetup
November 29, 2025
MEETUP.COM
Melbourne Python Users Group, Australia
December 1, 2025
J.MP
PyBodensee Monthly Meetup
December 1, 2025
PYBODENSEE.COM
Happy Pythoning!
This was PyCoder's Weekly Issue #710.
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 ]
25 Nov 2025 7:30pm GMT
Real Python: Getting Started With Claude Code
Learn how to set up and start using Claude Code to boost your Python workflow. Learn how it differs from Claude Chat and how to use it effectively in your development setup.
You'll learn how to:
- Install and configure Claude Code
- Run it safely inside project directories
- Work with CLAUDE.md for task context
- Use Git integration for smoother coding workflows
- Apply Claude Code to automate real programming tasks
[ 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 ]
25 Nov 2025 2:00pm GMT
Python Software Foundation: PSF Code of Conduct Working Group Shares First Transparency Report
The PSF's Code of Conduct Working Group is a group of volunteers whose purpose is to foster a diverse and inclusive Python community by enforcing the PSF Code of Conduct, along with providing guidance and recommendations to the Python community on codes of conduct, that supports the PSF mission support and facilitate the growth of a diverse and international community of Python programmers.
The working group has recently committed to publishing annual transparency reports and we are pleased to share the first report with you today, for the 2024 calendar year. The initial transparency report took some time to produce, but we've improved our recording keeping practices to make future reports easier to prepare.
The Working Group spent time formalizing our record keeping this year, and going forward we plan to publish our transparency reports in the first quarter of each year. Each year's report will be added to the same place in the PSF's Code of Conduct documentation so that community members can easily access them. If you have thoughts or feedback on how to make these reports more useful, we welcome you to send us an email at conduct-wg@python.org.
25 Nov 2025 1:51pm GMT
Seth Michael Larson: WebKit browsers see telephone numbers everywhere
Just like Excel seeing everything as a date, WebKit mobile browsers automatically interpret many numbers as telephone numbers. When detected, mobile browsers replace the text in the HTML with a clickable <a href="tel:..."> value that when selected will call the number denoted. This can be helpful sometimes, but frustrating other times as random numbers in your HTML suddenly become useless hyperlinks.
Below I've included numbers that may be turned into phone numbers so you can see for yourself why this may be a problem and how many cases there are. Numbers that are detected as a phone number by your browser are highlighted blue by this CSS selector:
a[href^=tel] {
background-color: #00ccff;
}
None of the values below are denoted as telephone number links in the source HTML, they are all automatically created by the browser. If you're not using WebKit, enable this check-box to show WebKit's behavior:
- 2
- 22
- 222
- 2222
- 22222
- 222222
- 2222222
- 22222222
- 222222222
- 2222222222
- 22222222222
- 111111111111
- 222222222222
- 555555555555
- 1111111111111
- 2222222222222 (???)
- 5555555555555
- 11111111111111
- 22222222222222
- 55555555555555
- 111111111111111
- 222222222222222
- 555555555555555
- 2-2
- 2-2-2
- 22-2-2
- 22-22-2
- 22-22-22
- 22-22-222
- 22-222-222
- 222-222-222
- 222-222-2222
- 222-2222-2222
- 2222-2222-2222
- 2222-2222-22222
- 2222-22222-22222
- 22222-22222-22222
- 2 222-222-2222
- +1 222-222-2222
- +2 222-222-2222 (There is no +2 country code...)
- +28 222-222-2222 (Unassigned codes aren't used)
- +1222-222-2222
- +2222-222-2222
- (+1)222-222-2222
- (+2)222-222-2222
- (1)222-222-2222
- (2)222-222-2222
- (1222-222-2222
- (1 222-222-2222
- 1)222-222-2222
- 222-222-2222 (en-dashes)
- 222-222-2222 (em-dashes)
- [1]222-222-2222
- <1>222-222-2222
Are there any other combinations that get detected as telephone numbers that I missed? Send me a pull request or email.
How to prevent automatic telephone number detection?
So how can you prevent browsers from parsing telephone numbers automatically? Add this HTML to your <head> section:
<meta name="format-detection" content="telephone=no">
This will disable automatic telephone detection, and then you can be explicit about clickable telephone numbers by using the tel: URL scheme like so:
<a href="tel:+222-222-222-2222">(+222)222-222-2222</a>
Thanks for keeping RSS alive! ♥
25 Nov 2025 12:00am GMT
HoloViz: Rich parameters & reactive programming with Param: 2.3 release
25 Nov 2025 12:00am GMT
Brian Okken: PythonTest Black Friday Deals 2025
Save up to 50% on courses and the pytest book.
The Complete pytest Course bundle
Use BLACKFRIDAY to save 20% off through November.
This code is intended to match lots of 10-20% off deals around
Use SAVE50 to save 50% off through November
This code is intended to match the PragProg deal below
Why leave both in place?
No reason, really. I just thought it'd be fun to have a "choose your own discount" thing.
25 Nov 2025 12:00am GMT
24 Nov 2025
Planet Python
Rodrigo Girão Serrão: Generalising itertools.pairwise
![]()
In this article you will learn about itertools.pairwise, how to use it, and how to generalise it.
In this tutorial you will learn to use and generalise itertools.pairwise. You will understand what itertools.pairwise does, how to use it, and how to implement a generalised version for when itertools.pairwise isn't enough.
itertools.pairwise
itertools.pairwise is an iterable from the standard module itertools that lets you access overlapping pairs of consecutive elements of the input iterable. That's quite a mouthful, so let me translate:
You give
pairwisean iterable, like"ABCD", andpairwisegives you pairs back, like("A", "B"),("B", "C"), and("C", "D").
In loops, it is common to unpack the pairs directly to perform some operation on both values. The example below uses pairwise to determine how the balance of a bank account changed based on the balance history:
from itertools import pairwise # Python 3.10+
balance_history = [700, 1000, 800, 750]
for before, after in pairwise(balance_history):
change = after - before
print(f"Balance changed by {change:+}.")
Balance changed by +300.
Balance changed by -200.
Balance changed by -50.
How to implement pairwise
If you had to implement pairwise, you might think of something like the code below:
def my_pairwise(iterable):
for prev_, next_ in zip(iterable, iterable[1:]):
yield (prev_, next_)
Which directly translates to
def my_pairwise(iterable):
yield from zip(iterable, iterable[1:])
But there is a problem with this implementation, and that is the slicing operation. pairwise is supposed to work with any iterable and not all iterables are sliceable. For example, files are iterables but are not sliceable.
There are a couple of different ways to fix this but my favourite uses collections.deque with its parameter maxlen:
from collections import deque
from itertools import islice
def my_pairwise(data):
data = iter(data)
window = deque(islice(data, 1), maxlen=2)
for value in data:
window.append(value)
yield tuple(window)
Generalising itertools.pairwise
pairwise will always produce pairs of consecutive elements, but sometimes you might want tuples of different sizes. For example, you might want something like "triplewise", to get triples of consecutive elements, but pairwise can't be used for that. So, how do you implement that generalisation?
In the upcoming subsections I will present different ways of implementing the function nwise(iterable, n) that accepts an iterable and a positive integer n and produces overlapping tuples of n elements taken from the given iterable.
Some example applications:
nwise("ABCD", 2) -> ("A", "B"), ("B", "C"), ("C", "D")
nwise("ABCD", 3) -> ("A", "B", "C"), ("B", "C", "D")
nwise("ABCD", 4) -> ("A", "B", "C", "D")
Using deque
The implementation of pairwise that I showed above can be adapted for nwise:
from collections import deque
from itertools import islice
def nwise(iterable, n):
iterable = iter(iterable)
window = deque(islice(iterable, n - 1), maxlen=n)
for value in iterable:
window.append(value)
yield tuple(window)
Note that you have to change maxlen=2 to maxlen=n, but also islice(iterable, 1) to islice(iterable, n - 1).
Using tee
Another fundamentally different way of implement nwise is by using itertools.tee to split the input...
24 Nov 2025 6:36pm GMT
EuroPython Society: New EuroPython Society Fellow in 2025
A warm welcome to Martin Borus as the second elected EuroPython Society Fellow in 2025.
EuroPython Society Fellows
EuroPython Society Fellows have contributed significantly towards our mission, the EuroPython conference and the Society as an organisation. They are eligible for a lifetime free attendance of the EuroPython conference and will be listed on our EuroPython Society Fellow Grant page in recognition of their work.
Martin has been part of the Europython Conferences volunteers since 2017.
Some have "met" him the first time in a response on an issue sent to the helpdesk or during the organisation meetings of the Ops team.
Others interacted with him as a volunteer at reception, out in the halls, a tutor in a Humble Data tutorial, a session chair, or a room manager in a tutorial or talk.
While pretending to be just an "On-site volunteer" or a member of the "Operations team" - over time, he has taken over not only the organisation of the registration desk, but also developed and orchestrated the training of session chairs.
He also expanded the programme with the informal session for first time conference attendees, which have been well received by all attendees.
The EuroPython Society Board would like to congratulate and thank all Fellows for their tireless work towards our mission! If you want to send in your nomination, check out our Fellowship page and get in touch!
Many thanks,
EuroPython Society
https://www.europython-society.org/
24 Nov 2025 5:58pm GMT
Trey Hunner: Python Black Friday & Cyber Monday sales (2025)
It's time for some discounted Python-related skill-building. This is my eighth annual compilation of Python learning-related Black Friday & Cyber Monday deals. If you find a Python-related deal in the next week that isn't on this list, please contact me.
Python-related sales
It's not even Black Friday yet, but most of these Python-related Black Friday sales are already live:
- Python Morsels: I'm offering lifetime access for the second time ever (more details below)
- Data School: a new subscription to access all of Kevin's 7 courses plus all upcoming courses
- Talk Python: AI Python bundle, the Everything Bundle, and Michael's Talk Python in Production
- Reuven Lerner: get 20% off your first year of the LernerPython+data tier (code
BF2025) - Brian Okken: get 50% off his pytest books and courses (code
SAVE50) - Rodrigo: get 50% off all his books including his all books bundle (code
BF202550) - Mike Driscoll: get 50% off all his Python books and courses (code
BLACKISBACK) - The Python Coding Place: get 50% the all course bundle of 12 courses
- Sundeep Agarwal: ~55% off Sundeep's all books bundle with code
FestiveOffer - Nodeledge: get 20% off your first payment (code
BF2025) - O'Reilly Media: 40% off the first year with code
CYBERWEEK25($299 instead of $499) - Manning is offering 50% off from Nov 25 to Dec 1
- Wizard Zines: 50% all of Julia Evan's great zines on various tech topics that I personally find both fun and useful (not Python-related, but one of my favorite annual sales)… this is a one-day Black Friday exclusive sale
I will be keeping an idea on other potential sales and updating this post as I find them. If you've seen a sale that I haven't, please contact me or comment below.
Django-related sales
Adam Johnson has also published a list of Django-related deals for Black Friday (which he's been doing for a few years now).
More on my sale: Python Morsels Lifetime Access
Python Morsels is a hands-on, exercise-driven Python skill-building platform designed to help developers write cleaner, more idiomatic Python through real-world practice. This growing library of exercises, videos, and courses is aimed primarily at intermediate and professional developers. If you haven't used Python Morsels, you can read about it in my sale announcement post.
This Black Friday / Cyber Monday, I'm offering lifetime access: all current and future content for in a single payment. If you'd like to build confidence in your everyday Python skills, consider this sale. It's about 50% cheaper than paying annually for five years.
Get lifetime access to Python Morsels
24 Nov 2025 4:00pm GMT
Nicola Iarocci: Flask started as an April Fool's joke
The story that the Python micro web framework Flask started as an April Fool's joke is well known in Python circles, but it was nice to see it told by Armin Ronacher himself1.
I'm fond of Flask. It was a breath of fresh air when it came out, and most of my Python open-source work is based on it.
-
The video is produced by the people who also authored the remarkable Python: The Documentary. ↩︎
24 Nov 2025 2:29pm GMT
Real Python: How to Properly Indent Python Code
Knowing how to properly indent Python code is a key skill for becoming an accomplished Python developer. Beginning Python programmers know that indentation is required, but learning to indent code so it's readable, syntactically correct, and easy to maintain is a skill that takes practice.
By the end of this tutorial, you'll know:
- How to properly indent Python code in a variety of editors
- How your choice of editor can impact your Python code
- How to indent code using spaces in simple text editors
- How to use code formatters to properly indent Python code automatically
- Why indentation is required when writing Python code
With this knowledge, you'll be able to write Python code confidently in any environment.
Get Your Code: Click here to download the free sample code you'll use to learn how to properly indent Python code.
How to Indent Code in Python
Indenting your code means adding spaces to the beginning of a line, which shifts the start of the line to the right, as shown below:
number = 7
if number > 0:
print("It's a positive number")
In this example, the first two lines aren't indented, while the third line is indented.
How you indent your Python code depends on your coding environment. Most editors and integrated development environments (IDEs) can indent Python code correctly with little to no input from the user. You'll see examples of this in the sections that follow.
Python-Aware Editors
In most cases, you'll be working in a Python-aware environment. This might be a full Python IDE such as PyCharm, a code editor like Visual Studio Code, the Python REPL, IPython, IDLE, or even a Jupyter notebook. All these environments understand Python syntax and indent your code properly as you type.
Note: PEP 8 is the style guide for Python that was first introduced in 2001. Among other recommendations, it specifies that code indentation should be four spaces per indentation level. All environments discussed here follow that standard.
Here's a small example to show this automatic indentation. You'll use the following code to see how each environment automatically indents as you type:
lucky_number.py
1lucky_number = 7
2for number in range(10):
3 if number == lucky_number:
4 print("Found the lucky number!")
5 else:
6 print(f"{number} is not my lucky number.")
7
8print("Done.")
This example shows how indenting happens automatically and how to de-indent a line of code. De-indenting-also called dedenting-means removing spaces at the beginning of a line, which moves the start of the line to the left. The code on lines 5 and 8 needs to be de-indented relative to the previous lines to close the preceding code blocks. For a detailed explanation of the indentation in this code, expand the collapsible section below.
The code above shows different levels of indentation, combining a for loop with an if statement:
- Line 1 initializes the variable
lucky_numberto the integer value of7. - Line 2 starts a
forloop usingnumberas an iterator over a range of values from0to9. - Line 3 checks if
numberis equal tolucky_number. This line is indented to show it's part of theforloop's body. - Line 4 prints a message when the condition
number == lucky_numberisTrue. This line is indented to show it's part of the body of theifstatement. - Line 5 provides a way to act when the condition on line 3 is
False. This line is de-indented to show it's part of theifstatement on line 3. - Line 6 prints a message when the condition
number == lucky_numberisFalse. This line is indented to show it's part of the body of theelseclause. - Line 8 prints a final message. It's de-indented to show it's not part of the
forloop, but executes after theforloop is done.
If any part of this code is unfamiliar to you, you can learn more by exploring these resources:
- For more on
forloops, read PythonforLoops: The Pythonic Way. - For more on
range(), read Pythonrange(): Represent Numerical Ranges. - For more on
ifandelseclauses, read Conditional Statements in Python. - For more on the
print()function, read Your Guide to the Pythonprint()Function. - For more on f-string formatting, read Python's F-String for String Interpolation and Formatting.
Each line of the code above exists at a specific indentation level. All consecutive statements at the same indentation level are considered to be part of the same group or code block. The table below shows each line of code from the example, its indentation level, and what action is needed to achieve that level:
| Code | Indentation Level | Action |
|---|---|---|
lucky_number = 7 |
0 | - |
for number in range(10): |
0 | - |
if number == lucky_number: |
1 | Indent |
print("Found the lucky number!") |
2 | Indent |
else: |
1 | De-indent |
print(f"{number} is not my lucky number.") |
2 | Indent |
print("Done.") |
0 | De-indent |
First, here's what it looks like to enter this code in PyCharm, a full-featured Python IDE:
Notice that as you hit Enter on line 2, PyCharm immediately indents line 3 for you. The same thing happens on line 4. However, to de-indent line 5 and line 8, you have to either press Backspace or Shift+Tab to move the cursor back to the proper position for the next line.
Read the full article at https://realpython.com/how-to-indent-in-python/ »
[ 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 ]
24 Nov 2025 2:00pm GMT
Python Software Foundation: Python is for Everyone: Grab PyCharm Pro for 30% off—plus a special bonus!
So far this year's PSF fundraising campaign has been a truly inspiring demonstration of the Python community's generosity, strength, and solidarity. We set a special 🥧 themed goal of $314,159.26 (it's the year of Python 3.14!), and with your support, we are already at 93% of that goal-WOW!! Thank you to every single person who has already donated: you have our deep gratitude, and we are committed to making every dollar count.
🚨 New target alert: If we hit our goal of $100Kπ- we are going to release a nice video AND we are going to set a new goal, as well as an additional super stretch goal. Can you chip in to get us there? We're confident that with your contributions and support we can reach those new heights. Because Python is for everyone, thanks to you!
Today, we're excited to share another way for you to participate AND get awesome benefits from JetBrains! We have the opportunity to once again partner with JetBrains to deliver a special promotion: 30% off PyCharm Pro with ALL proceeds going to the PSF. New this year: Folks who take advantage of this offer will also receive a free tier of AI Assistant in PyCharm! Read on to learn more about the PyCharm promotion, how to grab it while it lasts, and other ways you can contribute to the PSF's 2025 end-of-year fundraiser. Huge thanks to JetBrains for stepping up to provide this awesome deal 🐍⚡️
LIMITED TIME! Grab PyCharm Pro at 30% off with a free tier of AI Assistant:
- Grab a discounted Python IDE: PyCharm! JetBrains is once again supporting the PSF by providing a 30% discount on PyCharm Pro and ALL proceeds will go to the PSF! Your subscription will include a free tier of AI Assistant in PyCharm. You can take advantage of this discount by clicking the button on the JetBrains promotion page, and the discount will be automatically applied when you check out. The promotion will only be available through December 12th, so go grab the deal today!
>>> Get PyCharm Pro! <<<
There are two ways to join our fundraiser through donate.python.org:
- Donate directly to the PSF! Your donation is a direct way to support and power the future of the Python programming language and community you love. Every donation makes a difference, and we work hard to make a little go a long way.
- Become a PSF Supporting Member! When you sign up as a Supporting Member of the PSF, you become a part of the PSF, are eligible to vote in PSF elections, and help us sustain our mission with your annual support. You can sign up as a Supporting Member at the usual annual rate ($99 USD), or you can take advantage of our sliding scale option (starting at $25 USD)!
>>> Donate or Become a Member Today! <<<
If you already donated, you're already a PSF member, AND you already grabbed PyCharm at 30% off (look at you, you exemplary supporter!🏆) you can:
- Share the fundraiser with your regional and project-based communities: Share this blog post in your Python-related Discords, Slacks, social media accounts- wherever your Python community is! Keep an eye on our social media accounts and repost to share the latest stories and news for the campaign.
- Share your Python story with a call to action: We invite you to share your personal Python, PyCon, or PSF story. What impact has it made in your life, in your community, in your career? Share your story in a blog post or on your social media platform of choice and add a link to donate.python.org.
- Ask your employer to sponsor: If your company is using Python to build its products and services, check to see if they already sponsor the PSF on our Sponsors page. If not, reach out to your organization's internal decision-makers and impress on them just how important it is for us to power the future of Python together, and send them our sponsor prospectus.
Your donations and support:
- Keep Python thriving
- Support CPython and PyPI progress
- Increase security across the Python ecosystem
- Bring the global Python community together
- Make our community more diverse and robust every year
24 Nov 2025 10:44am GMT
Python Bytes: #459 Inverted dependency trees
<strong>Topics covered in this episode:</strong><br> <ul> <li><strong><a href="https://discuss.python.org/t/pep-814-add-frozendict-built-in-type/104854?featured_on=pythonbytes">PEP 814 - Add frozendict built-in type</a></strong></li> <li><strong>From <a href="https://squidfunk.github.io/mkdocs-material/?featured_on=pythonbytes">Material for MkDocs</a> to <a href="https://zensical.org?featured_on=pythonbytes">Zensical</a></strong></li> <li><strong><a href="https://github.com/tach-org/tach?featured_on=pythonbytes">Tach</a></strong></li> <li><strong>Some Python Speedups in 3.15 and 3.16</strong></li> <li><strong>Extras</strong></li> <li><strong>Joke</strong></li> </ul><p><strong>About the show</strong></p> <p>Sponsored by us! Support our work through:</p> <ul> <li>Our <a href="https://training.talkpython.fm/?featured_on=pythonbytes"><strong>courses at Talk Python Training</strong></a></li> <li><a href="https://courses.pythontest.com/p/the-complete-pytest-course?featured_on=pythonbytes"><strong>The Complete pytest Course</strong></a></li> <li><a href="https://www.patreon.com/pythonbytes"><strong>Patreon Supporters</strong></a></li> </ul> <p><strong>Connect with the hosts</strong></p> <ul> <li>Michael: <a href="https://fosstodon.org/@mkennedy">@mkennedy@fosstodon.org</a> / <a href="https://bsky.app/profile/mkennedy.codes?featured_on=pythonbytes">@mkennedy.codes</a> (bsky)</li> <li>Brian: <a href="https://fosstodon.org/@brianokken">@brianokken@fosstodon.org</a> / <a href="https://bsky.app/profile/brianokken.bsky.social?featured_on=pythonbytes">@brianokken.bsky.social</a></li> <li>Show: <a href="https://fosstodon.org/@pythonbytes">@pythonbytes@fosstodon.org</a> / <a href="https://bsky.app/profile/pythonbytes.fm">@pythonbytes.fm</a> (bsky)</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 <strong>Monday</strong> at 10am PT. Older video versions available there too.</p> <p>Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to <a href="https://pythonbytes.fm/friends-of-the-show">our friends of the show list</a>, we'll never share it.</p> <p><strong>Michael #0</strong>: <a href="https://talkpython.fm/black-friday?featured_on=pythonbytes">Black Friday is on at Talk Python</a></p> <ul> <li>What's on offer: <ol> <li>An <a href="https://training.talkpython.fm/courses/bundle/black-friday-ai-2025?featured_on=pythonbytes">AI course mini bundle</a> (22% off)</li> <li>20% off our entire library via the <a href="https://training.talkpython.fm/courses/bundle/everything?featured_on=pythonbytes">Everything Bundle</a> (<a href="https://training.talkpython.fm/bundles-arent-subscriptions?featured_on=pythonbytes">what's that? ;)</a> )</li> <li>The new <a href="https://mikeckennedy.gumroad.com/l/talk-python-in-production-book/black-friday-2025?featured_on=pythonbytes">Talk Python in Production book</a> (25% off)</li> </ol></li> </ul> <p>Brian: This is peer pressure in action</p> <ul> <li>20% off <a href="https://courses.pythontest.com/the-complete-pytest-course-bundle?featured_on=pythonbytes">The Complete pytest Course bundle</a> (use code BLACKFRIDAY) through November <ul> <li>or use save50 for 50% off, your choice.</li> </ul></li> <li><a href="https://pragprog.com/titles/bopytest2/python-testing-with-pytest-second-edition/?featured_on=pythonbytes">Python Testing with pytest, 2nd edition</a>, eBook (50% off with code save50) also through November <ul> <li>I would have picked 20%, but it's a PragProg wide thing</li> </ul></li> </ul> <p><strong>Michael #1: <a href="https://discuss.python.org/t/pep-814-add-frozendict-built-in-type/104854?featured_on=pythonbytes">PEP 814 - Add frozendict built-in type</a></strong></p> <ul> <li>by Victor Stinner & Donghee Na</li> <li>A new public immutable type <code>frozendict</code> is added to the <code>builtins</code> module.</li> <li>We expect <code>frozendict</code> to be safe by design, as it prevents any unintended modifications. This addition benefits not only CPython's standard library, but also third-party maintainers who can take advantage of a reliable, immutable dictionary type.</li> <li>To add to <a href="https://blobs.pythonbytes.fm/existing-frozen-typesstructures-in-python.html?cache_id=5788ae">existing frozen types in Python</a>.</li> </ul> <p><strong>Brian #2: From <a href="https://squidfunk.github.io/mkdocs-material/?featured_on=pythonbytes">Material for MkDocs</a> to <a href="https://zensical.org?featured_on=pythonbytes">Zensical</a></strong></p> <ul> <li>Suggested by John Hagen</li> <li>A lot of people, me included, use Material for MkDocs as our MkDocs theme for both personal and professional projects, and in-house docs.</li> <li>This plugin for MkDocs is <a href="https://github.com/squidfunk/mkdocs-material/issues/8523?featured_on=pythonbytes">now in maintenance mode</a></li> <li>The development team is switching to working on Zensical, a static site generator to overcome some technical limitations with MkDocs. There's a series of posts about the transition and reasoning <ol> <li><a href="https://squidfunk.github.io/mkdocs-material/blog/2024/08/19/how-were-transforming-material-for-mkdocs/?featured_on=pythonbytes">Transforming Material for MkDocs</a></li> <li><a href="https://squidfunk.github.io/mkdocs-material/blog/2025/11/05/zensical/?featured_on=pythonbytes">Zensical - A modern static site generator built by the creators of Material for MkDocs</a></li> <li><a href="https://squidfunk.github.io/mkdocs-material/blog/2025/11/11/insiders-now-free-for-everyone/?featured_on=pythonbytes">Material for MkDocs Insiders - Now free for everyone</a></li> <li><a href="https://squidfunk.github.io/mkdocs-material/blog/2025/11/18/goodbye-github-discussions/?featured_on=pythonbytes">Goodbye, GitHub Discussions</a></li> </ol></li> <li>Material for MkDocs <ul> <li>still around, but in maintenance mode</li> <li>all insider features now available to everyone</li> </ul></li> <li>Zensical is / will be <ul> <li>compatible with Material for Mkdocs, can natively read mkdocs.yml, to assist with the transition</li> <li>Open Source, MIT license</li> <li>funded by an offering for professional users: Zensical Spark</li> </ul></li> </ul> <p><strong>Michael #3: <a href="https://github.com/tach-org/tach?featured_on=pythonbytes">Tach</a></strong></p> <ul> <li>Keep the streak: pip deps with uv + tach</li> <li>From Gerben Decker</li> <li>We needed some more control over linting our dependency structure, both internal and external.</li> <li>We use <code>tach</code> (which you covered before IIRC), but also some home built linting rules for our specific structure. These are extremely easy to build using an underused feature of <code>ruff</code>: "uv run ruff analyze graph --python python_exe_path .".</li> <li><a href="https://app.gauge.sh/show?uid=fee5a5ca-7f89-4f0a-bcf7-bca1f7c6cc8a&featured_on=pythonbytes">Example from an app</a> I'm working on (shhhhh not yet announced!)</li> </ul> <p><strong>Brian #4: Some Python Speedups in 3.15 and 3.16</strong></p> <ul> <li><a href="https://fidget-spinner.github.io/posts/faster-jit-plan.html?featured_on=pythonbytes"><strong>A Plan for 5-10%* Faster Free-Threaded JIT by Python 3.16</strong></a> <ul> <li>5% faster by 3.15 and 10% faster by 3.16</li> </ul></li> <li><a href="https://emmatyping.dev/decompression-is-up-to-30-faster-in-cpython-315.html?featured_on=pythonbytes">Decompression is up to 30% faster in CPython 3.15</a></li> </ul> <p><strong>Extras</strong></p> <p>Brian:</p> <ul> <li><a href="https://github.com/okken/lean-tdd-book?featured_on=pythonbytes">LeanTDD book issue tracker</a></li> </ul> <p>Michael:</p> <ul> <li>No. 4 for dependencies: <a href="https://www.linkedin.com/posts/bbelderbos_i-needed-to-see-which-packages-were-pulling-share-7398344644629016576-C-Q8?featured_on=pythonbytes">Inverted dep trees</a> from Bob Belderbos</li> </ul> <p><strong>Joke: <a href="https://x.com/creativedrewy/status/1990891118569869327?featured_on=pythonbytes">git pull inception</a></strong></p>
24 Nov 2025 8:00am GMT
Zato Blog: Automation in Python
Automation in Python
How to automate systems in Python and how the Zato Python integration platform differs from a network automation tool, how to start using it, along with a couple of examples of integrations with Office 365 and Jira, is what the latest article is about.
➤ Read it here: Systems Automation in Python.
More resources
➤ Python API integration tutorials
➤ What is an integration platform?
➤ Python Integration platform as a Service (iPaaS)
➤ What is an Enterprise Service Bus (ESB)? What is SOA?
➤ Open-source iPaaS in Python
24 Nov 2025 3:00am GMT

