Planet Python
Last update: February 04, 2025 07:43 AM UTC
February 04, 2025
Python Circle
Installing Python3.13 on Ubuntu 22.04
python3.13 installation, building python from source code, python 3.13, latest python installation, solving sqlite not found error, python stable version installation on linux, ubuntu python 3.13 installation, No module named '_sqlite3'
February 04, 2025 04:41 AM UTC
Armin Ronacher
Fat Rand: How Many Lines Do You Need To Generate A Random Number?
I recently wrote about dependencies in Rust. The feedback, both within and outside the Rust community, was very different. A lot of people, particularly some of those I greatly admire expressed support. The Rust community, on the other hand, was very dismissive on on Reddit and Lobsters.
Last time, I focused on the terminal_size crate, but I also want to show you a different one that I come across once more: rand. It has a similarly out-of-whack value-to-dependency ratio, but in a slightly different way. More than terminal_size, you are quite likely to use it. If for instance if you want to generate a random UUID, the uuid crate will depend on it. Due to its nature it also has a high security exposure.
I don't want to frame this as “rand is a bad crate”. It's not a bad crate at all! It is however a crate that does not appear very concerned about how many dependencies it has, and I want to put this in perspective: of all the dependencies and lines of codes it pulls in, how many does it actually use?
As the name implies, the rand crate is capable of calculating random numbers. The crate itself has seen a fair bit of churn: for instance 0.9 broke backwards compatibility with 0.8. So, as someone who used that crate, I did what a responsible developer is supposed to do, and upgraded the dependency. After all, I don't want to be the reason there are two versions of rand in the dependency tree. After the upgrade, I was surprised how fat that dependency tree has become over the last nine months.
Today, this is what the dependency tree looks like for the default feature set on macOS and Linux:
x v0.1.0 (/private/tmp/x) └── rand v0.9.0 ├── rand_chacha v0.9.0 │ ├── ppv-lite86 v0.2.20 │ │ └── zerocopy v0.7.35 │ │ ├── byteorder v1.5.0 │ │ └── zerocopy-derive v0.7.35 (proc-macro) │ │ ├── proc-macro2 v1.0.93 │ │ │ └── unicode-ident v1.0.16 │ │ ├── quote v1.0.38 │ │ │ └── proc-macro2 v1.0.93 (*) │ │ └── syn v2.0.98 │ │ ├── proc-macro2 v1.0.93 (*) │ │ ├── quote v1.0.38 (*) │ │ └── unicode-ident v1.0.16 │ └── rand_core v0.9.0 │ ├── getrandom v0.3.1 │ │ ├── cfg-if v1.0.0 │ │ └── libc v0.2.169 │ └── zerocopy v0.8.14 ├── rand_core v0.9.0 (*) └── zerocopy v0.8.14
About a year ago, it looked like this:
x v0.1.0 (/private/tmp/x) └── rand v0.8.5 ├── libc v0.2.169 ├── rand_chacha v0.3.1 │ ├── ppv-lite86 v0.2.17 │ └── rand_core v0.6.4 │ └── getrandom v0.2.10 │ ├── cfg-if v1.0.0 │ └── libc v0.2.169 └── rand_core v0.6.4 (*)
Not perfect, but better.
So, let's investigate what all these dependencies do. The current version pulls in quite a lot.
Platform Dependencies
First there is the question of getting access to the system RNG. On Linux and Mac it uses libc, for Windows it uses the pretty heavy Microsoft crates (windows-targets and windows-sys). The irony is that the Rust standard library already implements a way to get a good seed from the system, but it does not expose it. Well, not really at least. There is a crate called fastrand which does not have any dependencies which seeds itself by funneling out seeds from the stdlib via the hasher system. That looks a bit like this:
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
fn random_seed() -> u64 {
RandomState::new().build_hasher().finish()
}
Now obviously that's a hack, but it will work because the hashmap's hasher is randomly seeded from good sources. There is a single-dependency crate too which can read from the system's entropy source and that's getrandom. So there at least could be a world where rand only depends on that.
Dependency Chain
If you want to audit the entire dependency chain, you end up with maintainers that form eight distinct groups:
- libc: rust core + various externals
- cfg-if: rust core + Alex Crichton
- windows-*: Microsoft
- rand_* and getrandom: rust nursery + rust-random
- ppv-lite86: Kaz Wesley
- zerocopy and zerocopy-derive: Google (via two ICs there, Google does not publish)
- byteorder: Andrew Gallant
- syn, quote, proc-macro2, unicode-ident: David Tolnay
If I also cared about WASM targets, I'd have to consider even more dependencies.
Code Size
So let's vendor it. How much code is there? After removing all tests, we end up with 29 individual crates vendored taking up 62MB disk space. Tokei reports 209,150 lines of code.
Now this is a bit misleading, because like many times most of this is within windows-*. But how much of windows-* does getrandom need? A single function:
extern "system" fn ProcessPrng(pbdata: *mut u8, cbdata: usize) -> i32
For that single function (and the information which DLL it needs link into), we are compiling and downloading megabytes of windows-sys.
On Unix, it's harder to avoid libc because it tries multiple APIs. These are mostly single-function APIs, but some non-portable constants make libc difficult to avoid.
Beyond the platform dependencies, what else is there?
- ppv-lite86 (the rand's picked default randon number generator) alone comes to 3,587 lines of code including 168 unsafe blocks. If the goal of using zerocopy was to avoid unsafe, there is still a ton of unsafe remaining.
- The combination of proc-macro2, quote, syn, and unicode-ident comes to 49,114 lines of code.
- byteorder clocks in at 3,000 lines of code.
- The pair of zerocopy and zerocopy-derive together? 14,004 lines of code.
All of these are great crates, but do I need all of this just to generate a random number?
Compilation Times
Then there are compile times. How long does it take to compile? 4.3 seconds on my high-end M1 Max. A lot of dependencies block each other, particularly the part that waits for the derives to finish.
- rand depends on rand_chacha,
- which depends on ppv-lite86,
- which depends on zerocopy (with the derive feature),
- which depends on zerocopy-derive
- which pulls compiler plugins crate.
Only after all the code generation finished, the rest will make meaningful progress. In total a release build produces 36MB of compiler artifacts. 12 months ago, it took just under 2 seconds.
Final Thoughts
The Rust developer community on Reddit doesn't seem very concerned. The main sentiment is that rand now uses less unsafe so that's benefit enough. While the total amount of unsafe probably did not go down, that moved unsafe is is now in a common crate written by people that know how to use unsafe (zerocopy). There is also the sentiment that all of this doesn't matter anyways, because we will will all soon depend on zerocopy everywhere anyways, as more and more dependencies are switching over to it.
Maybe this points to Rust not having a large enough standard library. Perhaps features like terminal size detection and random number generation should be included. That at least is what people pointed out on Twitter.
We already treat crates like regex, rand, and serde as if they were part of the standard library. The difference is that I can trust the standard library as a whole—it comes from a single set of authors, making auditing easier. If these external, but almost standard crates were more cautious about dependencies and make it more of a goal to be auditable, we would all benefit.
Or maybe this is just how Rust works now. That would make me quite sad.
February 04, 2025 12:00 AM UTC
February 03, 2025
Eli Bendersky
Decorator JITs - Python as a DSL
Spend enough time looking at Python programs and packages for machine learning, and you'll notice that the "JIT decorator" pattern is pretty popular. For example, this JAX snippet:
import jax.numpy as jnp
import jax
@jax.jit
def add(a, b):
return jnp.add(a, b)
# Use "add" as a regular Python function
... = add(...)
Or the Triton language for writing GPU kernels directly in Python:
import triton
import triton.language as tl
@triton.jit
def add_kernel(x_ptr,
y_ptr,
output_ptr,
n_elements,
BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(axis=0)
block_start = pid * BLOCK_SIZE
offsets = block_start + tl.arange(0, BLOCK_SIZE)
mask = offsets < n_elements
x = tl.load(x_ptr + offsets, mask=mask)
y = tl.load(y_ptr + offsets, mask=mask)
output = x + y
tl.store(output_ptr + offsets, output, mask=mask)
In both cases, the function decorated with jit doesn't get executed by the Python interpreter in the normal sense. Instead, the code inside is more like a DSL (Domain Specific Language) processed by a special purpose compiler built into the library (JAX or Triton). Another way to think about it is that Python is used as a meta language to describe computations.
In this post I will describe some implementation strategies used by libraries to make this possible.
Preface - where we're going
The goal is to explain how different kinds of jit decorators work by using a simplified, educational example that implements several approaches from scratch. All the approaches featured in this post will be using this flow:
Expr IR --> LLVM IR --> Execution" /> Expr IR --> LLVM IR --> Execution" class="align-center" src="https://eli.thegreenplace.net/images/2025/decjit-python.png" />These are the steps that happen when a Python function wrapped with our educational jit decorator is called:
- The function is translated to an "expression IR" - Expr.
- This expression IR is converted to LLVM IR.
- Finally, the LLVM IR is JIT-executed.
Steps (2) and (3) use llvmlite; I've written about llvmlite before, see this post and also the pykaleidoscope project. For an introduction to JIT compilation, be sure to read this and maybe also the series of posts starting here.
First, let's look at the Expr IR. Here we'll make a big simplification - only supporting functions that define a single expression, e.g.:
def expr2(a, b, c, d):
return (a + d) * (10 - c) + b + d / c
Naturally, this can be easily generalized - after all, LLVM IR can be used to express fully general computations.
Here are the Expr data structures:
class Expr:
pass
@dataclass
class ConstantExpr(Expr):
value: float
@dataclass
class VarExpr(Expr):
name: str
arg_idx: int
class Op(Enum):
ADD = "+"
SUB = "-"
MUL = "*"
DIV = "/"
@dataclass
class BinOpExpr(Expr):
left: Expr
right: Expr
op: Op
To convert an Expr into LLVM IR and JIT-execute it, we'll use this function:
def llvm_jit_evaluate(expr: Expr, *args: float) -> float:
"""Use LLVM JIT to evaluate the given expression with *args.
expr is an instance of Expr. *args are the arguments to the expression, each
a float. The arguments must match the arguments the expression expects.
Returns the result of evaluating the expression.
"""
llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
llvm.initialize_native_asmparser()
cg = _LLVMCodeGenerator()
modref = llvm.parse_assembly(str(cg.codegen(expr, len(args))))
target = llvm.Target.from_default_triple()
target_machine = target.create_target_machine()
with llvm.create_mcjit_compiler(modref, target_machine) as ee:
ee.finalize_object()
cfptr = ee.get_function_address("func")
cfunc = CFUNCTYPE(c_double, *([c_double] * len(args)))(cfptr)
return cfunc(*args)
It uses the _LLVMCodeGenerator class to actually generate LLVM IR from Expr. This process is straightforward and covered extensively in the resources I linked to earlier; take a look at the full code here.
My goal with this architecture is to make things simple, but not too simple. On one hand - there are several simplifications: only single expressions are supported, very limited set of operators, etc. It's very easy to extend this! On the other hand, we could have just trivially evaluated the Expr without resorting to LLVM IR; I do want to show a more complete compilation pipeline, though, to demonstrate that an arbitrary amount of complexity can be hidden behind these simple interfaces.
With these building blocks in hand, we can review the strategies used by jit decorators to convert Python functions into Exprs.
AST-based JIT
Python comes with powerful code reflection and introspection capabilities out of the box. Here's the astjit decorator:
def astjit(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if kwargs:
raise ASTJITError("Keyword arguments are not supported")
source = inspect.getsource(func)
tree = ast.parse(source)
emitter = _ExprCodeEmitter()
emitter.visit(tree)
return llvm_jit_evaluate(emitter.return_expr, *args)
return wrapper
This is a standard Python decorator. It takes a function and returns another function that will be used in its place (functools.wraps ensures that function attributes like the name and docstring of the wrapper match the wrapped function).
Here's how it's used:
from astjit import astjit
@astjit
def some_expr(a, b, c):
return b / (a + 2) - c * (b - a)
print(some_expr(2, 16, 3))
After astjit is applied to some_expr, what some_expr holds is the wrapper. When some_expr(2, 16, 3) is called, the wrapper is invoked with *args = [2, 16, 3].
The wrapper obtains the AST of the wrapped function, and then uses _ExprCodeEmitter to convert this AST into an Expr:
class _ExprCodeEmitter(ast.NodeVisitor):
def __init__(self):
self.args = []
self.return_expr = None
self.op_map = {
ast.Add: Op.ADD,
ast.Sub: Op.SUB,
ast.Mult: Op.MUL,
ast.Div: Op.DIV,
}
def visit_FunctionDef(self, node):
self.args = [arg.arg for arg in node.args.args]
if len(node.body) != 1 or not isinstance(node.body[0], ast.Return):
raise ASTJITError("Function must consist of a single return statement")
self.visit(node.body[0])
def visit_Return(self, node):
self.return_expr = self.visit(node.value)
def visit_Name(self, node):
try:
idx = self.args.index(node.id)
except ValueError:
raise ASTJITError(f"Unknown variable {node.id}")
return VarExpr(node.id, idx)
def visit_Constant(self, node):
return ConstantExpr(node.value)
def visit_BinOp(self, node):
left = self.visit(node.left)
right = self.visit(node.right)
try:
op = self.op_map[type(node.op)]
return BinOpExpr(left, right, op)
except KeyError:
raise ASTJITError(f"Unsupported operator {node.op}")
When _ExprCodeEmitter finishes visiting the AST it's given, its return_expr field will contain the Expr representing the function's return value. The wrapper then invokes llvm_jit_evaluate with this Expr.
Note how our decorator interjects into the regular Python execution process. When some_expr is called, instead of the standard Python compilation and execution process (code is compiled into bytecode, which is then executed by the VM), we translate its code to our own representation and emit LLVM from it, and then JIT execute the LLVM IR. While it seems kinda pointless in this artificial example, in reality this means we can execute the function's code in any way we like.
AST JIT case study: Triton
This approach is almost exactly how the Triton language works. The body of a function decorated with @triton.jit gets parsed to a Python AST, which then - through a series of internal IRs - ends up in LLVM IR; this in turn is lowered to PTX by the NVPTX LLVM backend. Then, the code runs on a GPU using a standard CUDA pipeline.
Naturally, the subset of Python that can be compiled down to a GPU is limited; but it's sufficient to run performant kernels, in a language that's much friendlier than CUDA and - more importantly - lives in the same file with the "host" part written in regular Python. For example, if you want testing and debugging, you can run Triton in "interpreter mode" which will just run the same kernels locally on a CPU.
Note that Triton lets us import names from the triton.language package and use them inside kernels; these serve as the intrinsics for the language - special calls the compiler handles directly.
Bytecode-based JIT
Python is a fairly complicated language with a lot of features. Therefore, if our JIT has to support some large portion of Python semantics, it may make sense to leverage more of Python's own compiler. Concretely, we can have it compile the wrapped function all the way to bytecode, and start our translation from there.
Here's the bytecodejit decorator that does just this [1]:
def bytecodejit(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if kwargs:
raise BytecodeJITError("Keyword arguments are not supported")
expr = _emit_exprcode(func)
return llvm_jit_evaluate(expr, *args)
return wrapper
def _emit_exprcode(func):
bc = func.__code__
stack = []
for inst in dis.get_instructions(func):
match inst.opname:
case "LOAD_FAST":
idx = inst.arg
stack.append(VarExpr(bc.co_varnames[idx], idx))
case "LOAD_CONST":
stack.append(ConstantExpr(inst.argval))
case "BINARY_OP":
right = stack.pop()
left = stack.pop()
match inst.argrepr:
case "+":
stack.append(BinOpExpr(left, right, Op.ADD))
case "-":
stack.append(BinOpExpr(left, right, Op.SUB))
case "*":
stack.append(BinOpExpr(left, right, Op.MUL))
case "/":
stack.append(BinOpExpr(left, right, Op.DIV))
case _:
raise BytecodeJITError(f"Unsupported operator {inst.argval}")
case "RETURN_VALUE":
if len(stack) != 1:
raise BytecodeJITError("Invalid stack state")
return stack.pop()
case "RESUME" | "CACHE":
# Skip nops
pass
case _:
raise BytecodeJITError(f"Unsupported opcode {inst.opname}")
The Python VM is a stack machine; so we emulate a stack to convert the function's bytecode to Expr IR (a bit like an RPN evaluator). As before, we then use our llvm_jit_evaluate utility function to lower Expr to LLVM IR and JIT execute it.
Using this JIT is as simple as the previous one - just swap astjit for bytecodejit:
from bytecodejit import bytecodejit
@bytecodejit
def some_expr(a, b, c):
return b / (a + 2) - c * (b - a)
print(some_expr(2, 16, 3))
Bytecode JIT case study: Numba
Numba is a compiler for Python itself. The idea is that you can speed up specific functions in your code by slapping a numba.njit decorator on them. What happens next is similar in spirit to our simple bytecodejit, but of course much more complicated because it supports a very large portion of Python semantics.
Numba uses the Python compiler to emit bytecode, just as we did; it then converts it into its own IR, and then to LLVM using llvmlite [2].
By starting with the bytecode, Numba makes its life easier (no need to rewrite the entire Python compiler). On the other hand, it also makes some analyses harder, because by the time we're in bytecode, a lot of semantic information existing in higher-level representations is lost. For example, Numba has to sweat a bit to recover control flow information from the bytecode (by running it through a special interpreter first).
Tracing-based JIT
The two approaches we've seen so far are similar in many ways - both rely on Python's introspection capabilities to compile the source code of the JIT-ed function to some extent (one to AST, the other all the way to bytecode), and then work on this lowered representation.
The tracing strategy is very different. It doesn't analyze the source code of the wrapped function at all - instead, it traces its execution by means of specially-boxed arguments, leveraging overloaded operators and functions, and then works on the generated trace.
The code implementing this for our smile demo is surprisingly compact:
def tracejit(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if kwargs:
raise TraceJITError("Keyword arguments are not supported")
argspec = inspect.getfullargspec(func)
argboxes = []
for i, arg in enumerate(args):
if i >= len(argspec.args):
raise TraceJITError("Too many arguments")
argboxes.append(_Box(VarExpr(argspec.args[i], i)))
out_box = func(*argboxes)
return llvm_jit_evaluate(out_box.expr, *args)
return wrapper
Each runtime argument of the wrapped function is assigned a VarExpr, and that is placed in a _Box, a placeholder class which lets us do operator overloading:
@dataclass
class _Box:
expr: Expr
_Box.__add__ = _Box.__radd__ = _register_binary_op(Op.ADD)
_Box.__sub__ = _register_binary_op(Op.SUB)
_Box.__rsub__ = _register_binary_op(Op.SUB, reverse=True)
_Box.__mul__ = _Box.__rmul__ = _register_binary_op(Op.MUL)
_Box.__truediv__ = _register_binary_op(Op.DIV)
_Box.__rtruediv__ = _register_binary_op(Op.DIV, reverse=True)
The remaining key function is _register_binary_op:
def _register_binary_op(opcode, reverse=False):
"""Registers a binary opcode for Boxes.
If reverse is True, the operation is registered as arg2 <op> arg1,
instead of arg1 <op> arg2.
"""
def _op(arg1, arg2):
if reverse:
arg1, arg2 = arg2, arg1
box1 = arg1 if isinstance(arg1, _Box) else _Box(ConstantExpr(arg1))
box2 = arg2 if isinstance(arg2, _Box) else _Box(ConstantExpr(arg2))
return _Box(BinOpExpr(box1.expr, box2.expr, opcode))
return _op
To understand how this works, consider this trivial example:
@tracejit
def add(a, b):
return a + b
print(add(1, 2))
After the decorated function is defined, add holds the wrapper function defined inside tracejit. When add(1, 2) is called, the wrapper runs:
- For each argument of add itself (that is a and b), it creates a new _Box holding a VarExpr. This denotes a named variable in the Expr IR.
- It then calls the wrapped function, passing it the boxes as runtime parameters.
- When (the wrapped) add runs, it invokes a + b. This is caught by the overloaded __add__ operator of _Box, and it creates a new BinOpExpr with the VarExprs representing a and b as children. This BinOpExpr is then returned [3].
- The wrapper unboxes the returned Expr and passes it to llvm_jit_evaluate to emit LLVM IR from it and JIT execute it with the actual runtime arguments of the call: 1, 2.
This might be a little mind-bending at first, because there are two different executions that happen:
- The first is calling the wrapped add function itself, letting the Python interpreter run it as usual, but with special arguments that build up the IR instead of doing any computations. This is the tracing step.
- The second is lowering this IR our tracing step built into LLVM IR and then JIT executing it with the actual runtime argument values 1, 2; this is the execution step.
This tracing approach has some interesting characteristics. Since we don't have to analyze the source of the wrapped functions but only trace through the execution, we can "magically" support a much richer set of programs, e.g.:
@tracejit
def use_locals(a, b, c):
x = a + 2
y = b - a
z = c * x
return y / x - z
print(use_locals(2, 8, 11))
This just works with our basic tracejit. Since Python variables are placeholders (references) for values, our tracing step is oblivious to them - it follows the flow of values. Another example:
@tracejit
def use_loop(a, b, c):
result = 0
for i in range(1, 11):
result += i
return result + b * c
print(use_loop(10, 2, 3))
This also just works! The created Expr will be a long chain of BinExpr additions of i's runtime values through the loop, added to the BinExpr for b * c.
This last example also leads us to a limitation of the tracing approach; the loop cannot be data-dependent - it cannot depend on the function's arguments, because the tracing step has no concept of runtime values and wouldn't know how many iterations to run through; or at least, it doesn't know this unless we want to perform the tracing run for every runtime execution [4].
The tracing approach is useful in several domains, most notably automatic differentiation (AD). For a slightly deeper taste, check out my radgrad project.
Tracing JIT case study: JAX
The JAX ML framework uses a tracing approach very similar to the one described here. The first code sample in this post shows the JAX notation. JAX cleverly wraps Numpy with its own version which is traced (similar to our _Box, but JAX calls these boxes "tracers"), letting you write regular-feeling Numpy code that can be JIT optimized and executed on accelerators like GPUs and TPUs via XLA. JAX's tracer builds up an underlying IR (called jaxpr) which can then be emitted to XLA ops and passed to XLA for further lowering and execution.
For a fairly deep overview of how JAX works, I recommend reading the autodidax doc.
As mentioned earlier, JAX has some limitations with things like data-dependent control flow in native Python. This won't work, because there's control flow that depends on a runtime value (count):
import jax
@jax.jit
def sum_datadep(a, b, count):
total = a
for i in range(count):
total += b
return total
print(sum_datadep(10, 3, 3))
When sum_datadep is executed, JAX will throw an exception, saying something like:
This concrete value was not available in Python because it depends on the value of the argument count.
As a remedy, JAX has its own built-in intrinsics from the jax.lax package. Here's the example rewritten in a way that actually works:
import jax
from jax import lax
@jax.jit
def sum_datadep_fori(a, b, count):
def body(i, total):
return total + b
return lax.fori_loop(0, count, body, a)
fori_loop (and many other built-ins in the lax package) is something JAX can trace through, generating a corresponding XLA operation (XLA has support for While loops, to which this lax.fori_loop can be lowered).
The tracing approach has clear benefits for JAX as well; because it only cares about the flow of values, it can handle arbitrarily complicated Python code, as long as the flow of values can be traced. Just like the local variables and data-independent loops shown earlier, but also things like closures. This makes meta-programming and templating easy.
Code
The full code for this post is available on GitHub.
[1] | Once again, this is a very simplified example. A more realistic translator would have to support many, many more Python bytecode instructions. |
[2] | In fact, llvmlite itself is a Numba sub-project and is maintained by the Numba team, for which I'm grateful! |
[3] | For a fun exercise, try adding constant folding to the wrapped _op: when both its arguments are constants (not boxes), instead placing each in a _Box(ConstantExpr(...)), it could perform the mathematical operation on them and return a single constant box. This is a common optimization in compilers! |
[4] | In all the JIT approaches showed in this post, the expectation is that compilation happens once, but the compiled function can be executed many times (perhaps in a loop). This means that the compilation step cannot depend on the runtime values of the function's arguments, because it has no access to them. You could say that it does, but that's just for the very first time the function is run (in the tracing approach); it has no way of knowing their values the next times the function will run. JAX has some provisions for cases where a function is invoked with a small set of runtime values and we want to separately JIT each of them. |
February 03, 2025 10:22 PM UTC
PyBites
The Mutable Trap: Avoiding Unintended Side Effects in Python
Ever had a Python function behave strangely, remembering values between calls when it shouldn’t? You’re not alone! This is one of Python’s sneakiest pitfalls—mutable default parameters.
Recently someone asked for help in our Pybites Circle Community with a Bite exercise that seemed to be behaving unexpectedly.
It turned out that this was a result of modifying a mutable parameter passed to a function.
For folks new to programming it is not obvious why modifying a variable inside a function might cause a change outside of that function. Let’s have a closer look at the underlying issue.
What is a Python Variable
When considering variables in Python it is a good idea to differentiate between a variable’s name and the object that it represents.
Think of a variable like a name tag on an object. An object can have more than one name tag, and modifying the object affects everyone holding a reference to it.
# The inspiration variable is pointing to the Singleton None
>>> inspiration = None
>>> id(inspiration)
140713396607856
# The inspiration variable is now pointing to a string
>>> inspiration = "Read Atomic Habits"
>>> id(inspiration)
3034242491760
# The inspiration variable is now pointing to a different string,
# and as strings are immutable, the id has changed.
# It's a different object.
>>> inspiration = "Bob and Julian"
>>> id(inspiration)
3034242497712
>>> nums = list(range(10))
# The nums variable is pointing to a list
>>> nums
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> id(nums)
3034242497984
>>> nums.append(10)
# The list that nums is pointing to has been modified,
# but the id is the same because lists are mutable.
>>> nums
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> id(nums)
3034242497984
>>> me = "Tarzan"
>>> me_too = me
>>> id(me)
2546636178128
>>> id(me_too)
2546636178128
# Variables me and me_too are both pointing to the same string.
>>> me is me_too
True
Python passes parameters by Reference
Python passes parameters to functions by Reference – also referred to as call by sharing. This results in multiple names bound to the same object.
Consider this simple case where a global variable is passed into a function:
# Our global variable
FAB_FOUR = ["John", "Paul", "George", "Ringo"]
# Our positional (mutable) parameter
def meet_the_beatles(members) -> None:
# Sorting our local variable
members.sort()
print(f" ... {members}")
def main():
print(f"Before: {FAB_FOUR}")
meet_the_beatles(FAB_FOUR)
print(f" After: {FAB_FOUR}")
if __name__ == "__main__":
main()
Running the above code results in the following output:
Before: ['John', 'Paul', 'George', 'Ringo']
... ['George', 'John', 'Paul', 'Ringo']
After: ['George', 'John', 'Paul', 'Ringo']
Which shows that our global variable FAB_FOUR
has indeed been modified. This is because our function variable members
is really just an alias for the global variable FAB_FOUR
– they both point to the same object.
The excellent site Python Tutor can be used to provide a nice visualisation:
Take Care when programming with Mutable Parameters
Functions that mutate their input values or modify state in other parts of the program behind the scenes are said to have side effects and as a general rule this is best avoided. However, it is not uncommon to encounter such behaviour in real-world applications and it something we need to be aware of.
At the very least, you should consider carefully whether the caller expects the argument to be changed.
If you want to protect your code from such side effects, consider using immutable types where possible/practical.
If it is not clear to you whether it is safe to modify a passed mutable parameter – create a copy of the parameter and modify that instead. Comprehensions provide a nice pythonic way to create new objects as does the copy module with its copy
and deepcopy
functions.
Mutable Types as Parameter Defaults
Python allows us to provide default values for function parameters – making them optional.
For example, when we call members.sort()
in our code above, the sort
method has an optional keyword argument reverse
which defaults to False
. We can pass it with the value True
to override the default behaviour:
>>> FAB_FOUR = ["John", "Paul", "George", "Ringo"]
>>> FAB_FOUR.sort()
>>> FAB_FOUR
['George', 'John', 'Paul', 'Ringo']
>>> FAB_FOUR.sort(reverse=True)
>>> FAB_FOUR
['Ringo', 'Paul', 'John', 'George']
The default values are evaluated once, at the point of function definition in the defining scope.
Using a Mutable Type as a paramater default should be avoided if possible because it can lead to unexpected and inconsistent behaviour
Consider the following code:
def enroll_student(name, students=[]):
students.append(name)
return students
def main():
print(enroll_student("Biffa"))
print(enroll_student("Moose"))
print(enroll_student("Cheeseman"))
if __name__ == "__main__":
main()
Running this code produces the following output:
['Biffa']
['Biffa', 'Moose']
['Biffa', 'Moose', 'Cheeseman']
Our function seems to be retaining information from previous calls.
This is because default values are stored in function attribute __defaults__
and if mutable, can be changed by the function code.
Lets modify our code to show this:
def enroll_student(name, students=[]):
# id of function local variable students
print(f"ID of students: {id(students)}")
students.append(name)
return students
def main():
enroll_student("Biffa")
# Function enroll_student parameter default values
print(f"Function Default Values: {enroll_student.__defaults__}")
# id of students default value
print(f"ID of students default value: {id(enroll_student.__defaults__[0])}")
if __name__ == "__main__":
main()
Running this code produces the following output:
ID of students: 2667038141888
Function Default Values: (['Biffa'],)
ID of students default value: 2667038141888
This shows that our local students
variable and its default value both point to the same object. Modifying the local variable will cause the default value to be updated on the function object!
To prevent this behaviour, we can specify None
as the default value, and handle that case inside our code. None
is immutable. Here is our new version of the function:
def enroll_student(name, students=None):
if students is None:
students = []
students.append(name)
return students
Looking at the output, we can see that the immutable None
is stored as the function default for students
and is no longer coupled to our local students
variable:
ID of students: 1545913462208
Function Default Values: (None,)
ID of students default value: 140713967338128
And our code works consistently:
def main():
print(enroll_student("Biffa"))
print(enroll_student("Moose"))
print(enroll_student("Cheeseman"))
Which produces:
['Biffa']
['Moose']
['Cheeseman']
Key Takeaways
- Python passes objects by reference, meaning variables can point to the same object.
- Mutable parameters can cause side effects by modifying variables in enclosing scopes.
- Mutable default parameters persist across function calls, which can cause unexpected behaviour.
- To avoid issues, use
None
as a default and create a new object inside the function.
February 03, 2025 06:26 PM UTC
Real Python
Python for Loops: The Pythonic Way
Python’s for
loop allows you to iterate over the items in a collection, such as lists, tuples, strings, and dictionaries. The for
loop syntax declares a loop variable that takes each item from the collection in each iteration. This loop is ideal for repeatedly executing a block of code on each item in the collection. You can also tweak for
loops further with features like break
, continue
, and else
.
By the end of this tutorial, you’ll understand that:
- Python’s
for
loop iterates over items in a data collection, allowing you to execute code for each item. - To iterate from
0
to10
, you use thefor index in range(11):
construct. - To repeat code a number of times without processing the data of an iterable, use the
for _ in range(times):
construct. - To do index-based iteration, you can use
for index, value in enumerate(iterable):
to access both index and item.
In this tutorial, you’ll gain practical knowledge of using for
loops to traverse various collections and learn Pythonic looping techniques. Additionally, you’ll learn how to handle exceptions and how to use asynchronous iterations to make your Python code more robust and efficient.
Get Your Code: Click here to download the free sample code that shows you how to use for loops in Python.
Take the Quiz: Test your knowledge with our interactive “The Python for Loop” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
The Python for LoopIn this quiz, you'll test your understanding of Python's for loop and the concepts of definite iteration, iterables, and iterators. With this knowledge, you'll be able to perform repetitive tasks in Python more efficiently.
Getting Started With the Python for
Loop
In programming, loops are control flow statements that allow you to repeat a given set of operations a number of times. In practice, you’ll find two main types of loops:
for
loops are mostly used to iterate a known number of times, which is common when you’re processing data collections with a specific number of data items.while
loops are commonly used to iterate an unknown number of times, which is useful when the number of iterations depends on a given condition.
Python has both of these loops and in this tutorial, you’ll learn about for
loops. In Python, you’ll generally use for
loops when you need to iterate over the items in a data collection. This type of loop lets you traverse different data collections and run a specific group of statements on or with each item in the input collection.
In Python, for
loops are compound statements with a header and a code block that runs a predefined number of times. The basic syntax of a for
loop is shown below:
for variable in iterable:
<body>
In this syntax, variable
is the loop variable. In each iteration, this variable takes the value of the current item in iterable
, which represents the data collection you need to iterate over. The loop body can consist of one or more statements that must be indented properly.
Here’s a more detailed breakdown of this syntax:
for
is the keyword that initiates the loop header.variable
is a variable that holds the current item in the input iterable.in
is a keyword that connects the loop variable with the iterable.iterable
is a data collection that can be iterated over.<body>
consists of one or more statements to execute in each iteration.
Here’s a quick example of how you can use a for
loop to iterate over a list:
>>> colors = ["red", "green", "blue", "yellow"]
>>> for color in colors:
... print(color)
...
red
green
blue
yellow
In this example, color
is the loop variable, while the colors
list is the target collection. Each time through the loop, color
takes on a successive item from colors
. In this loop, the body consists of a call to print()
that displays the value on the screen. This loop runs once for each item in the target iterable. The way the code above is written is the Pythonic way to write it.
However, what’s an iterable anyway? In Python, an iterable is an object—often a data collection—that can be iterated over. Common examples of iterables in Python include lists, tuples, strings, dictionaries, and sets, which are all built-in data types. You can also have custom classes that support iteration.
Note: Python has both iterables and iterators. Iterables support the iterable protocol consisting of the .__iter__()
special method. Similarly, iterators support the iterator protocol that’s based on the .__iter__()
and .__next__()
special methods.
Both iterables and iterators can be iterated over. All iterators are iterables, but not all iterables are iterators. Python iterators play a fundamental role in for
loops because they drive the iteration process.
A deeper discussion on iterables and iterators is beyond the scope of this tutorial. However, to learn more about them, check out the Iterators and Iterables in Python: Run Efficient Iterations tutorial.
You can also have a loop with multiple loop variables:
>>> points = [(1, 4), (3, 6), (7, 3)]
>>> for x, y in points:
... print(f"{x = } and {y = }")
...
x = 1 and y = 4
x = 3 and y = 6
x = 7 and y = 3
In this loop, you have two loop variables, x
and y
. Note that to use this syntax, you just need to provide a tuple of loop variables. Also, you can have as many loop variables as you need as long as you have the correct number of items to unpack into them. You’ll also find this pattern useful when iterating over dictionary items or when you need to do parallel iteration.
Sometimes, the input iterable may be empty. In that case, the loop will run its header once but won’t execute its body:
>>> for item in []:
... print(item)
...
Read the full article at https://realpython.com/python-for-loop/ »
[ 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 ]
February 03, 2025 02:00 PM UTC
Zato Blog
LDAP and Active Directory as Python API Services
LDAP and Active Directory as Python API Services
LDAP and Active Directory often play key a role in the management of a company's network resources yet it is not always very convenient to query a directory directly using the LDAP syntax and protocol that few people truly specialize in. This is why in this article we are using Zato to offer a REST API on top of directory services so that API clients can use REST and JSON instead.
Installing Zato
Start off by installing Zato - if you are not sure what to choose, pick the Docker Quickstart option and this will set up a working environment in a few minutes.
Creating connections
Once Zato is running, connections can be easily created in its Dashboard (by default, http://127.0.0.1:8183). Navigate to Connections -> Outgoing -> LDAP ..
.. and then click Create a new connection which will open a form as below:
The same form works for both regular LDAP and Active Directory - in the latter case, make sure that Auth type is set to NTLM.
The most important information is:
- User credentials
- Authentication type
- Server or servers to connect to
Note that if authentication type is not NTLM, user credentials can be provided using the LDAP syntax, e.g. uid=MyUser,ou=users,o=MyOrganization,dc=example,dc=com.
Right after creating a connection be sure to set its password too - the password asigned by default is a randomly generated one.
Pinging
It is always prudent to ping a newly created connection to ensure that all the information entered was correct.
Note that if you have more than one server in a pool then the first available one of them will be pinged - it is the whole pool that is pinged, not a particular part of it.
Active Directory as a REST service
As the first usage example, let's create a service that will translate JSON queries into LDAP lookups - given username or email the service will basic information about the person's account, such as first and last name.
Note that the conn object returned by client.get() below is capable of running any commands that its underlying Python library offers - in this case we are only using searches but any other operation can also be used, e.g. add or modify as well.
# -*- coding: utf-8 -*-
# stdlib
from json import loads
# Bunch
from bunch import bunchify
# Zato
from zato.server.service import Service
# Where in the directory we expect to find the user
search_base = 'cn=users, dc=example, dc=com'
# On input, we are looking users up by either username or email
search_filter = '(&(|(uid={user_info})(mail={user_info})))'
# On output, we are interested in username, first name, last name and the person's email
query_attributes = ['uid', 'givenName', 'sn', 'mail']
class ADService(Service):
""" Looks up users in AD by their username or email.
"""
class SimpleIO:
input_required = 'user_info'
output_optional = 'message', 'username', 'first_name', 'last_name', 'email'
response_elem = None
skip_empty_keys = True
def handle(self):
# Connection name to use
conn_name = 'My AD Connection'
# Get a handle to the connection pool
with self.out.ldap[conn_name].conn.client() as client:
# Get a handle to a particular connection
with client.get() as conn:
# Build a filter to find a user by
user_info = self.request.input['user_info']
user_filter = search_filter.format(user_info=user_info)
# Returns True if query succeeds and has any information on output
if conn.search(search_base, user_filter, attributes=query_attributes):
# This is where the actual response can be found
response = conn.entries
# In this case, we expect at most one user matching input criteria
entry = response[0]
# Convert it to JSON for easier handling ..
entry = entry.entry_to_json()
# .. and load it from JSON to a Python dict
entry = loads(entry)
# Convert to a Bunch instance to get dot access to dictionary keys
entry = bunchify(entry['attributes'])
# Now, actually produce a JSON response. For simplicity's sake,
# assume that users have only one of email or other attributes.
self.response.payload.message = 'User found'
self.response.payload.username = entry.uid[0]
self.response.payload.first_name = entry.givenName[0]
self.response.payload.last_name = entry.sn[0]
self.response.payload.email = entry.mail[0]
else:
# No business response = no such user found
self.response.payload.message = 'No such user'
After creating a REST channel, we can invoke the service from command line, thus confirming that we can offer the directory as a REST service:
$ curl "localhost:11223/api/get-user?user_info=MyOrganization\\MyUser" ; echo
{
"message": "User found",
"username": "MyOrganization\\MyUser",
"first_name": "First",
"last_name": "Last",
"email": "address@example.com"
}
$
More resources
➤ Python API integration tutorial
➤ What is a Network Packet Broker? How to automate networks in Python?
➤ 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
February 03, 2025 08:00 AM UTC
Seth Michael Larson
Connection without Connectivity (#1: Space)
This is the first article in a 7-part series about software for connection.
Feeling connected to others is a basic human need, so it is no surprise we want software to enable human connection. The surprise is that despite computing device ownership and internet usage being at an all-time high, feelings of loneliness have also never been higher.
The shape of today's “software for connection” uses centralized servers in the cloud, algorithmic curation, and incentives optimized for users to connect with the platform (aka “engagement” and “parasociality”), not necessarily with other humans. Software for connection has followed in the same rut created by big tech, as can be seen in protocols, browsers, and infrastructure.
The software we have today feels like a tiny fraction of what should be possible in a world full of people with personal computing devices in their pockets. There's no problem with a well-traveled road (it's the road that led you here, after all ❤️), but maybe you'll join me for a walk down a less-traveled path to explore how software can connect people outside the common paradigms and what assumptions and restrictions we can drop to enable different methods of connection.
Dōbutsu no Mori+ cover art (archive.org)
This 7-part series explores the feeling of “connection without connectivity” through the design of an offline video-game: Animal Crossing
Animal Crossing, known in Japan as “Dōbutsu no Mori+” (どうぶつの森+), was released on the GameCube in December 2001. According to director Katsuya Eguchi (江口 勝也), Animal Crossing features three themes:
“family, friendship, and community”
Animal Crossing is able to fulfill these themes without internet-connectivity, LAN-play, or even concurrent local multiplayer.
Sharing spaces, not concurrently
This first article is about space, a place where people can convene and feel togetherness. Spaces can be large or small, public or private, or somewhere in between.
Sharing a space with others, not necessarily at the same time, can evoke feelings of connection by experiencing changes to the space that others have made. By making your own changes to a shared space, you are connecting to others in the future.
Animal Crossing didn't support any kind of concurrent multiplayer, but that didn't stop the game from feeling like a multiplayer experience. In the game, players would collect bugs and fish, decorate their house, and plant trees and flowers. The many animal villagers that lived in the town would “remember” past conversations with other players, making the world feel more alive.
Because each player shared the same space with other players, everyone can see the changes made to the town over time. Katsuya Eguchi remarked on Animal Crossing being a shared space for his family to connect across time:
“[My family is] playing games, and I'm playing games, but we're not really doing it together. It'd be nice to have a play experience where even though we're not playing at the same time, we're still sharing things together. ...[I wanted] to create a space where my family and I could interact more, even if we weren't playing together.”
What does this mean for modern software? From this we learn that concurrency is not needed to feel connected. Today's internet-connected software typically requires a persistent connection, often because data doesn't exist on the same hardware that's running the software.
Requiring a persistent connection presents accessibility problems: internet access isn't distributed evenly throughout the world. Even in places where internet connectivity is common, a persistent connection isn't always possible (airplanes, subways, tunnels, inside a building, infrastructure outage).
Removing the need for real-time synchronization and data access means that internet-connectivity becomes optional. Users can then engage and create with the software at any time, regardless of connectivity. This empowers the user to weave the software into their own life and schedule at an engagement level that works for them.
Local-First Software is a software design paradigm that brings the benefits of colocated data and user interface to software. More details about the benefits of Local-First Software are outlined by Ink and Switch.
When adopting this paradigm, the onus is on the software to create a connected experience with the data that the software has on hand. Compare this to software demanding users be online and using the software as often as possible to feel connected to a space.
Local-First Software logo. The SVG is
impressively compact.
Future articles will discuss more implications for software for connection where connectivity is optional. If you've enjoyed this article it's likely you'll enjoy the others, I hope you'll follow along for more. Thanks for reading!
February 03, 2025 12:00 AM UTC
Quansight Labs Blog
From napari to the world: how we generalized the `conda/constructor` stack for distributing Python applications
Our work for the napari project resulted in multiple beneficial side effects for the conda packaging ecosystem.
February 03, 2025 12:00 AM UTC
February 02, 2025
Real Python
Build a Quiz Application With Python
In this tutorial, you’ll build a Python quiz application for the terminal. You’ll start by developing a basic app capable of asking questions, collecting answers, and checking correctness. As you progress, you’ll enhance the app by adding features like user-friendly interfaces, storing questions in external files, handling multiple correct answers, and providing hints and explanations.
By the end of this tutorial, you’ll understand that:
- A quiz application is a nice beginner project as it involves basic Python concepts like data structures and control flow.
- The
input()
function helps you to interact with the user in the terminal. - You can structure questions and answers in data files.
- TOML or JSON files are suitable for storing data in small games due to their readability and ease of integration with Python.
The word quiz was first used in 1781 to mean eccentric person. Nowadays, it’s mostly used to describe short tests of trivia or expert knowledge with questions like the following:
When was the first known use of the word quiz?
By following along in this step-by-step project, you’ll build an application that can test a person’s expertise on a range of topics. You can use this project to strengthen your own knowledge or to challenge your friends to a fun battle of wits.
The quiz application is a comprehensive project for anyone comfortable with the basics of Python. Throughout the tutorial, you’ll get all the code you need in separate, bite-size steps. You can also find the full source code of the application by clicking on the link below:
Get Source Code: Click here to get access to the source code that you’ll use to build your quiz application.
Whether you’re an eccentric person or not, read on to learn how to create your own quiz.
Demo: Your Python Quiz Application
In this step-by-step project, you’ll build a terminal application that can quiz you and your friends on a range of topics:
You first choose a topic for your questions. Then, for each question, you’ll choose an answer from a set of alternatives. Some questions may have multiple correct answers. You can access a hint to help you along the way. After answering a question, you’ll read an explanation that can provide more context for the answer.
Project Overview
You’ll start by creating a basic Python quiz application that’s only capable of asking a question, collecting an answer, and checking whether the answer is correct. From there, you’ll add more and more features in order to make your app more interesting, user-friendly, and fun.
You’ll build the quiz application iteratively by going through the following steps:
- Create a basic application that can ask multiple-choice questions.
- Make the app more user-friendly by improving how it looks and how it handles user errors.
- Refactor the code to use functions.
- Separate question data from source code by storing questions in a dedicated data file.
- Expand the app to handle multiple correct answers, give hints, and provide explanations.
- Add interest by supporting different quiz topics to choose from.
As you follow along, you’ll gain experience in starting with a small script and expanding it. This is an important skill in and of itself. Your favorite program, app, or game probably started as a small proof of concept that later grew into what it is today.
Prerequisites
In this tutorial, you’ll build a quiz application using Python’s basic building blocks. While working through the steps, it’s helpful if you’re comfortable with the following concepts:
- Reading input from the user at the terminal
- Organizing data in structures like lists, tuples, and dictionaries
- Using
if
statements to check different conditions - Repeating actions with
for
andwhile
loops - Encapsulating code with functions
If you’re not confident in your knowledge of these prerequisites, then that’s okay too! In fact, going through this tutorial will help you practice these concepts. You can always stop and review the resources linked above if you get stuck.
Step 1: Ask Questions
In this step, you’ll learn how to create a program that can ask questions and check answers. This will be the foundation of your quiz application, which you’ll improve upon in the rest of the tutorial. At the end of this step, your program will look like this:
Read the full article at https://realpython.com/python-quiz-application/ »
[ 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 ]
February 02, 2025 02:00 PM UTC
Top Python Game Engines
You can use several Python game engines for crafting video games using your existing Python skills. Popular Python game engines are Pygame, Arcade, and Ren’Py, each offering unique features. In this tutorial, you’ll learn how to install and use them, and how these engines differ from traditional stand-alone game engines.
By the end of this tutorial, you’ll understand that:
- Pygame, Arcade, adventurelib, and Ren’Py are some of the top Python game engines, each with unique features.
- Python game engines are straightforward to use for Python developers but may require more effort for cross-platform support compared to other engines.
- Pygame Zero is designed for beginners, offering simplified game development requiring less manual code than Pygame.
- Creating mobile games with Python game engines isn’t straightforward and requires additional effort for optimization and compatibility.
- You can use Python to create 3D games using frameworks like Panda 3D.
Using Python, and a host of great Python game engines, makes crafting great computer games much easier than in the past. In this tutorial, you’ll explore several of these game engines, learning what you need to start crafting your own Python video games!
To get the most out of this tutorial, you should be well-versed in Python programming, including object-oriented programming. An understanding of basic game concepts is helpful, but not necessary.
Ready to dive in? Click the link below to download the source code for all the games that you’ll be creating:
Get Source Code: Click here to get the source code you’ll use to try out Python game engines.
Python Game Engines Overview
Game engines for Python most often take the form of Python libraries, which can be installed in a variety of ways. Most are available on PyPI and can be installed with pip
. However, a few are available only on GitHub, GitLab, or other code sharing locations, and they may require other installation steps. This article will cover installation methods for all the engines discussed.
Python is a general purpose programming language, and it’s used for a variety of tasks other than writing computer games. In contrast, there are many different stand-alone game engines that are tailored specifically to writing games. Some of these include:
These stand-alone game engines differ from Python game engines in several key aspects:
- Language support: Languages like C++, C#, and JavaScript are popular for games written in stand-alone game engines, as the engines themselves are often written in these languages. Very few stand-alone engines support Python.
- Proprietary scripting support: In addition, many stand-alone game engines maintain and support their own scripting languages, which may not resemble Python. For example, Unity uses C# natively, while Unreal works best with C++.
- Platform support: Many modern stand-alone game engines can produce games for a variety of platforms, including mobile and dedicated game systems, with very little effort. In contrast, porting a Python game across various platforms, especially mobile platforms, can be a major undertaking.
- Licensing options: Games written using a stand-alone game engine may have different licensing options and restrictions, based on the engine used.
So why use Python to write games at all? In a word, Python. Using a stand-alone game engine often requires you to learn a new programming or scripting language. Python game engines leverage your existing knowledge of Python, reducing the learning curve and getting you moving forward quickly.
There are many game engines available for the Python environment. The engines that you’ll learn about here all share the following criteria:
- They’re relatively popular engines, or they cover aspects of gaming that aren’t usually covered.
- They’re currently maintained.
- They have good documentation available.
For each engine, you’ll learn about:
- Installation methods
- Basic concepts, as well as assumptions that the engine makes
- Major features and capabilities
- Two game implementations, to allow for comparison
Where appropriate, you should install these game engines in a virtual environment. Full source code for the games in this tutorial is available for download at the link below and will be referenced throughout the article:
Get Source Code: Click here to get the source code you’ll use to try out Python game engines.
With the source code downloaded, you’re ready to begin.
Pygame
When people think of Python game engines, the first thought many have is Pygame. In fact, there’s already a great primer on Pygame available at Real Python.
Written as a replacement for the stalled PySDL library, Pygame wraps and extends the SDL library, which stands for Simple DirectMedia Layer. SDL provides cross-platform access to your system’s underlying multimedia hardware components, such as sound, video, mouse, keyboard, and joystick. The cross-platform nature of both SDL and Pygame means that you can write games and rich multimedia Python programs for every platform that supports them!
Pygame Installation
Read the full article at https://realpython.com/top-python-game-engines/ »
[ 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 ]
February 02, 2025 02:00 PM UTC
Build a Dice-Rolling Application With Python
In this tutorial, you’ll learn how to create a Python dice roll simulator. The tutorial guides you through building a text-based user interface (TUI) application that simulates rolling dice using Python’s random
module. You’ll learn to gather and validate user input, use random.randint()
for dice rolling, and display results with ASCII art.
By the end of this tutorial, you’ll understand that:
- To simulate dice-rolling events, you can use
random.randint()
. - To get the user’s input, you use the built-in
input()
function. - To display dice in Python, you generate ASCII art representations of dice faces and use
print()
. - To manipulate strings, you use methods such as
.center()
and.join()
.
Building small projects, like a text-based user interface (TUI) dice-rolling application, will help you level up your Python programming skills. You’ll learn how to gather and validate the user’s input, import code from modules and packages, write functions, use for
loops and conditionals, and neatly display output by using strings and the print()
function.
Click the link below to download the entire code for this dice-rolling application and follow along while you build the project yourself:
Get Source Code: Click here to get the source code you’ll use to build your Python dice-rolling app.
Demo
In this step-by-step project, you’ll build an application that runs dice-rolling simulations. The app will be able to roll up to six dice, with each die having six faces. After every roll, the application will generate an ASCII diagram of dice faces and display it on the screen. The following video demonstrates how the app works:
When you run your dice-rolling simulator app, you get a prompt asking for the number of dice you want to roll. Once you provide a valid integer from 1 to 6, inclusive, then the application simulates the rolling event and displays a diagram of dice faces on the screen.
Project Overview
Your dice-rolling simulator app will have a minimal yet user-friendly text-based user interface (TUI), which will allow you to specify the number of six-sided dice that you’d like to roll. You’ll use this TUI to roll the dice at home without having to fly to Las Vegas.
Here’s a description of how the app will work internally:
Tasks to Run | Tools to Use | Code to Write |
---|---|---|
Prompt the user to choose how many six-sided dice to roll, then read the user’s input | Python’s built-in input() function |
A call to input() with appropriate arguments |
Parse and validate the user’s input | String methods, comparison operators, and conditional statements | A user-defined function called parse_input() |
Run the dice-rolling simulation | Python’s random module, specifically the randint() function |
A user-defined function called roll_dice() |
Generate an ASCII diagram with the resulting dice faces | Loops, list.append() , and str.join() |
A user-defined function called generate_dice_faces_diagram() |
Display the diagram of dice faces on the screen | Python’s built-in print() function |
A call to print() with appropriate arguments |
Keeping these internal workings in mind, you’ll code three custom functions to provide the app’s main features and functionalities. These functions will define your code’s public API, which you’ll call to bring the app to life.
To organize the code of your dice-rolling simulator project, you’ll create a single file called dice.py
in a directory of your choice in your file system. Go ahead and create the file to get started!
Prerequisites
You should be comfortable with the following concepts and skills before you start building this dice-rolling simulation project:
- Ways to run scripts in Python
- Python’s
import
mechanism - The basics of Python data types, mainly strings and integer numbers
- Basic data structures, especially lists
- Python variables and constants
- Python comparison operators
- Boolean values and logical expressions
- Conditional statements
- Python
for
loops - The basics of input, output, and string formatting in Python
If you don’t have all of the prerequisite knowledge before starting this coding adventure, then that’s okay! You might learn more by going ahead and getting started! You can always stop and review the resources linked here if you get stuck.
Step 1: Code the TUI of Your Python Dice-Rolling App
In this step, you’ll write the required code to ask for the user’s input of how many dice they want to roll in the simulation. You’ll also code a Python function that takes the user’s input, validates it, and returns it as an integer number if the validation was successful. Otherwise, the function will ask for the user’s input again.
To download the code for this step, click the following link and navigate to the source_code_step_1/
folder:
Get Source Code: Click here to get the source code you’ll use to build your Python dice-rolling app.
Take the User’s Input at the Command Line
Read the full article at https://realpython.com/python-dice-roll/ »
[ 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 ]
February 02, 2025 02:00 PM UTC
Develop Data Visualization Interfaces in Python With Dash
Dash is a popular Python framework for creating interactive data visualization interfaces. With Dash, you build web applications using only Python, without needing advanced web development skills. It integrates seamlessly with technologies like Flask, React.js, and Plotly.js to render user interfaces and generate charts.
By the end of this tutorial, you’ll understand that:
- Dash is an open-source framework for building data visualization interfaces using Python.
- Good use cases for Dash include interactive dashboards for data analysis and visualization tasks.
- You can customize the style of a Dash app using CSS, either inline or with external files.
- You can deploy Dash applications on PythonAnywhere, a platform offering free hosting for Python web apps.
Dash gives data scientists the ability to showcase their results in interactive web applications. You don’t need to be an expert in web development. In this tutorial, you’ll explore how to create, style, and deploy a Dash application, transforming a basic dashboard into a fully interactive tool.
You can download the source code, data, and resources for the sample application that you’ll make in this tutorial by clicking the link below:
Get the Source Code: Click here to get the source code you’ll use to learn about creating data visualization interfaces in Python with Dash in this tutorial.
What Is Dash?
Dash is an open-source framework for building data visualization interfaces. Released in 2017 as a Python library, it’s grown to include implementations for R, Julia, and F#. Dash helps data scientists build analytical web applications without requiring advanced web development knowledge.
Three technologies constitute the core of Dash:
- Flask supplies the web server functionality.
- React.js renders the user interface of the web page.
- Plotly.js generates the charts used in your application.
But you don’t have to worry about making all these technologies work together. Dash will do that for you. You just need to write Python, R, Julia, or F# and sprinkle in a bit of CSS.
Plotly, a Canada-based company, built Dash and supports its development. You may know the company from the popular graphing libraries that share its name. The company released Dash as open source under an MIT license, so you can use Dash at no cost.
Plotly also offers a commercial companion to Dash called Dash Enterprise. This paid service provides companies with support services such as hosting, deploying, and handling authentication on Dash applications. But these features live outside of Dash’s open-source ecosystem.
Dash will help you build dashboards quickly. If you’re used to analyzing data or building data visualizations using Python, then Dash will be a useful addition to your toolbox. Here are a few examples of what you can make with Dash:
- A dashboard showing object detection for self-driving cars
- A visualization of millions of Uber rides
- An interactive tool for analyzing soccer match data
This is just a tiny sample. If you’d like to see other interesting use cases, then go check out the Dash App Gallery.
Note: You don’t need advanced knowledge of web development to follow this tutorial, but some familiarity with HTML and CSS won’t hurt.
You should know the basics of the following topics, though:
- Python graphing libraries such as Plotly, Bokeh, and Matplotlib
- HTML and the structure of an HTML file
- CSS and style sheets
If you feel comfortable with the requirements and want to learn how to use Dash in your next project, then continue to the following section!
Get Started With Dash in Python
In this tutorial, you’ll go through the end-to-end process of building a dashboard using Dash. If you follow along with the examples, then you’ll go from a bare-bones dashboard on your local machine to a styled dashboard deployed on PythonAnywhere.
To build the dashboard, you’ll use a dataset of sales and prices of avocados in the United States between 2015 and 2018. Justin Kiggins compiled this dataset using data from the Hass Avocado Board.
How to Set Up Your Local Environment
To develop your app, you’ll need a new directory to store your code and data. You’ll also need a clean Python virtual environment. To create those, execute the commands below, choosing the version that matches your operating system:
Read the full article at https://realpython.com/python-dash/ »
[ 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 ]
February 02, 2025 02:00 PM UTC
February 01, 2025
Real Python
A Guide to Modern Python String Formatting Tools
In modern Python, you have f-strings and the .format()
method to approach the tasks of interpolating and formatting strings.
These tools help you embed variables and expressions directly into strings, control text alignment, and use custom format specifiers to modify how values appear. You can apply these techniques to create well-structured and readable Python code.
By the end of this tutorial, you’ll understand that:
- String interpolation in Python involves embedding variables and expressions into strings.
- You create an f-string in Python by prepending a string literal with an
f
orF
and using curly braces to include variables or expressions. - You can use variables in Python’s
.format()
method by placing them inside curly braces and passing them as arguments. - Format specifiers in Python control how values appear when formatted, using components like fill, align, sign, width, and type.
- You align text in Python string formatting using the align component, which can justify text to the left, right, or center within a specified width.
When working with strings in Python, you can leverage these formatting techniques to create dynamic and readable output. To get the most out of this tutorial, you should know the basics of Python programming and the string data type.
Get Your Code: Click here to download the free sample code that shows you how to use modern string formatting tools in Python.
Take the Quiz: Test your knowledge with our interactive “A Guide to Modern Python String Formatting Tools” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
A Guide to Modern Python String Formatting ToolsYou can take this quiz to test your understanding of modern tools for string formatting in Python. These tools include f-strings and the .format() method.
Getting to Know String Interpolation and Formatting in Python
Python has developed different string interpolation and formatting tools over the years. If you’re getting started with Python and looking for a quick way to format your strings, then you should use Python’s f-strings.
Note: To learn more about string interpolation, check out the String Interpolation in Python: Exploring Available Tools tutorial.
If you need to work with older versions of Python or legacy code, then it’s a good idea to learn about the other formatting tools, such as the .format()
method.
In this tutorial, you’ll learn how to format your strings using f-strings and the .format()
method. You’ll start with f-strings to kick things off, which are quite popular in modern Python code.
Using F-Strings for String Interpolation
Python has a string formatting tool called f-strings, which stands for formatted string literals. F-strings are string literals that you can create by prepending an f
or F
to the literal. They allow you to do string interpolation and formatting by inserting variables or expressions directly into the literal.
Creating F-String Literals
Here you’ll take a look at how you can create an f-string by prepending the string literal with an f
or F
:
👇
>>> f"Hello, Pythonista!"
'Hello, Pythonista!'
👇
>>> F"Hello, Pythonista!"
'Hello, Pythonista!'
Using either f
or F
has the same effect. However, it’s a more common practice to use a lowercase f
to create f-strings.
Just like with regular string literals, you can use single, double, or triple quotes to define an f-string:
👇
>>> f'Single-line f-string with single quotes'
'Single-line f-string with single quotes'
👇
>>> f"Single-line f-string with double quotes"
'Single-line f-string with single quotes'
👇
>>> f'''Multiline triple-quoted f-string
... with single quotes'''
'Multiline triple-quoted f-string\nwith single quotes'
👇
>>> f"""Multiline triple-quoted f-string
... with double quotes"""
'Multiline triple-quoted f-string\nwith double quotes'
Up to this point, your f-strings look pretty much the same as regular strings. However, if you create f-strings like those in the examples above, you’ll get complaints from your code linter if you have one.
The remarkable feature of f-strings is that you can embed Python variables or expressions directly inside them. To insert the variable or expression, you must use a replacement field, which you create using a pair of curly braces.
Interpolating Variables Into F-Strings
The variable that you insert in a replacement field is evaluated and converted to its string representation. The result is interpolated into the original string at the replacement field’s location:
>>> site = "Real Python"
👇
>>> f"Welcome to {site}!"
'Welcome to Real Python!'
In this example, you’ve interpolated the site
variable into your string. Note that Python treats anything outside the curly braces as a regular string.
Read the full article at https://realpython.com/python-formatted-output/ »
[ 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 ]
February 01, 2025 02:00 PM UTC
Natural Language Processing With spaCy in Python
spaCy is a robust open-source library for Python, ideal for natural language processing (NLP) tasks. It offers built-in capabilities for tokenization, dependency parsing, and named-entity recognition, making it a popular choice for processing and analyzing text. With spaCy, you can efficiently represent unstructured text in a computer-readable format, enabling automation of text analysis and extraction of meaningful insights.
By the end of this tutorial, you’ll understand that:
- You can use spaCy for natural language processing tasks such as part-of-speech tagging, and named-entity recognition.
- spaCy is often preferred over NLTK for production environments due to its performance and modern design.
- spaCy provides integration with transformer models, such as BERT.
- You handle tokenization in spaCy by breaking text into tokens using its efficient built-in tokenizer.
- Dependency parsing in spaCy helps you understand grammatical structures by identifying relationships between headwords and dependents.
Unstructured text is produced by companies, governments, and the general population at an incredible scale. It’s often important to automate the processing and analysis of text that would be impossible for humans to process. To automate the processing and analysis of text, you need to represent the text in a format that can be understood by computers. spaCy can help you do that.
If you’re new to NLP, don’t worry! Before you start using spaCy, you’ll first learn about the foundational terms and concepts in NLP. You should be familiar with the basics in Python, though. The code in this tutorial contains dictionaries, lists, tuples, for
loops, comprehensions, object oriented programming, and lambda functions, among other fundamental Python concepts.
Free Source Code: Click here to download the free source code that you’ll use for natural language processing (NLP) in spaCy.
Introduction to NLP and spaCy
NLP is a subfield of artificial intelligence, and it’s all about allowing computers to comprehend human language. NLP involves analyzing, quantifying, understanding, and deriving meaning from natural languages.
Note: Currently, the most powerful NLP models are transformer based. BERT from Google and the GPT family from OpenAI are examples of such models.
Since the release of version 3.0, spaCy supports transformer based models. The examples in this tutorial are done with a smaller, CPU-optimized model. However, you can run the examples with a transformer model instead. All Hugging Face transformer models can be used with spaCy.
NLP helps you extract insights from unstructured text and has many use cases, such as:
spaCy is a free, open-source library for NLP in Python written in Cython. It’s a modern, production-focused NLP library that emphasizes speed, streamlined workflows, and robust pretrained models. spaCy is designed to make it easy to build systems for information extraction or general-purpose natural language processing.
Another popular Python NLP library that you may have heard of is Python’s Natural Language Toolkit (NLTK). NLTK is a classic toolkit that’s widely used in research and education. It offers an extensive range of algorithms and corpora but with less emphasis on high-throughput performance than spaCy.
In this tutorial, you’ll learn how to work with spaCy, which is a great choice for building production-ready NLP applications.
Installation of spaCy
In this section, you’ll install spaCy into a virtual environment and then download data and models for the English language.
You can install spaCy using pip
, a Python package manager. It’s a good idea to use a virtual environment to avoid depending on system-wide packages. To learn more about virtual environments and pip
, check out Using Python’s pip to Manage Your Projects’ Dependencies and Python Virtual Environments: A Primer.
First, you’ll create a new virtual environment, activate it, and install spaCy. Select your operating system below to learn how:
With spaCy installed in your virtual environment, you’re almost ready to get started with NLP. But there’s one more thing you’ll have to install:
(venv) $ python -m spacy download en_core_web_sm
There are various spaCy models for different languages. The default model for the English language is designated as en_core_web_sm
. Since the models are quite large, it’s best to install them separately—including all languages in one package would make the download too massive.
Once the en_core_web_sm
model has finished downloading, open up a Python REPL and verify that the installation has been successful:
>>> import spacy
>>> nlp = spacy.load("en_core_web_sm")
Read the full article at https://realpython.com/natural-language-processing-spacy-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 ]
February 01, 2025 02:00 PM UTC
Python & APIs: A Winning Combo for Reading Public Data
Python is an excellent choice for working with Application Programming Interfaces (APIs), allowing you to efficiently consume and interact with them. By using the Requests library, you can easily fetch data from APIs that communicate using HTTP, such as REST, SOAP, or GraphQL APIs. This tutorial covers the essentials of consuming REST APIs with Python, including authentication and handling responses.
By the end of this tutorial, you’ll understand that:
- An API is an interface that allows different systems to communicate, typically through requests and responses.
- Python is a versatile language for consuming APIs, offering libraries like Requests to simplify the process.
- REST and GraphQL are two common types of APIs, with REST being more widely used for public APIs.
- To handle API authentication in Python, you can use API keys or more complex methods like OAuth to access protected resources.
Knowing how to consume an API is one of those magical skills that, once mastered, will crack open a whole new world of possibilities, and consuming APIs using Python is a great way to learn such a skill.
By the end of this tutorial, you’ll be able to use Python to consume most of the APIs that you come across. If you’re a developer, then knowing how to consume APIs with Python will empower you to integrate data from various online sources into your applications
Note: In this tutorial, you’ll focus on how to consume APIs using Python, not how to build them. For information on building an API with Python, check out Python REST APIs With Flask, Connexion, and SQLAlchemy.
You can download the source code for the examples that you’ll see in this tutorial by clicking the link below:
Get the Source Code: Click here to get the source code you’ll use to learn about consuming APIs with Python in this tutorial.
Getting to Know APIs
API stands for application programming interface. In essence, an API acts as a communication layer, or interface, that allows different systems to talk to each other without having to understand exactly what the others do.
APIs can come in many forms or shapes. They can be operating system APIs, used for actions like turning on your camera and audio when joining a Zoom call. Or they can be web APIs, used for web-focused actions, such as liking images on your Instagram or fetching the latest tweets.
No matter the type, all APIs function mostly the same way. You usually make a request for information or data, and the API returns a response with what you requested. For example, every time you open Twitter or scroll down your Instagram feed, you’re basically making a request to the API behind that app and getting a response in return. This is also known as calling an API.
In this tutorial, you’ll focus more on the high-level APIs that communicate across networks, also called web APIs.
SOAP vs REST vs GraphQL
Even though some of the examples above are geared toward newer platforms or apps, web APIs have been around for quite a long time. In the late 1990s and early 2000s, two different design models became the norm in exposing data publicly:
- SOAP (Simple Object Access Protocol) is typically associated with the enterprise world, has a stricter contract-based usage, and is mostly designed around actions.
- REST (Representational State Transfer) is typically used for public APIs and is ideal for fetching data from the Web. It’s much lighter and closer to the HTTP specification than SOAP.
Nowadays, there’s a new kid in town: GraphQL. Created by Facebook, GraphQL is a very flexible query language for APIs, where the clients decide exactly what they want to fetch from the server instead of letting the server decide what to send.
If you want to learn more about the differences between these three design models, then here are a few good resources:
- What is SOAP?
- What is REST?
- API 101: SOAP vs. REST
- Introduction to GraphQL
- Comparing API Architectural Styles: SOAP vs REST vs GraphQL vs RPC
Even though GraphQL is on the rise and is being adopted by bigger and bigger companies, including GitHub and Shopify, the truth is that the majority of public APIs are still REST APIs. Therefore, for the purpose of this tutorial, you’ll learn only about REST APIs and how to consume them using Python.
APIs and requests
: A Match Made in Heaven
When consuming APIs with Python, there’s only one library you need: requests
. With it, you should be able to do most, if not all, of the actions required to consume any public API.
You can install requests
by running the following command in your console:
$ python -m pip install requests
The examples in this tutorial have been tested with Python 3.13.1 and requests
2.32.3. However, any supported version of Python and requests
should yield similar results.
Read the full article at https://realpython.com/python-api/ »
[ 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 ]
February 01, 2025 02:00 PM UTC
Providing Multiple Constructors in Your Python Classes
Python doesn’t support constructor overloading in the same way that Java or C++ do. However, you can simulate multiple constructors by defining default arguments in .__init__()
and use @classmethod
to define alternative constructors. Another option is to employ the @singledispatchmethod
decorator for method overloading based on argument types. These techniques provide flexible ways to construct objects in Python.
By the end of this tutorial, you’ll understand that:
- You can create multiple constructors in Python by using optional arguments and branching logic in the
.__init__()
method. - The built-in
@classmethod
decorator allows you to define alternative constructors, using the class itself as the first argument. @singledispatchmethod
can simulate overloaded constructors based on the type of the first argument.
Having the tools to provide multiple constructors will help you write flexible classes that can adapt to changing needs.
You’ll also get a peek under the hood at how Python internally constructs instances of a regular class and how some standard-library classes provide multiple constructors.
To get the most out of this tutorial, you should have basic knowledge of object-oriented programming and understand how to define class methods with @classmethod
. You should also have experience working with decorators in Python.
Free Bonus: Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.
Instantiating Classes in Python
Python supports object-oriented programming with classes that are straightforward to create and use. Python classes offer powerful features that can help you write better software. Classes are like blueprints for objects, also known as instances. In the same way that you can build several houses from a single blueprint, you can build several instances from a class.
To define a class in Python, you need to use the class
keyword followed by the class name:
>>> # Define a Person class
>>> class Person:
... def __init__(self, name):
... self.name = name
...
Python has a rich set of special methods that you can use in your classes. Python implicitly calls special methods to automatically execute a wide variety of operations on instances. There are special methods to make your objects iterable, provide a suitable string representation for your objects, initialize instance attributes, and a lot more.
A pretty common special method is .__init__()
. This method provides what’s known as the instance initializer in Python. This method’s job is to initialize instance attributes with appropriate values when you instantiate a given class.
In Person
, the .__init__()
method’s first argument is called self
. This argument holds the current object or instance, which is passed implicitly in the method call. This argument is common to every instance method in Python. The second argument to .__init__()
is called name
and will hold the person’s name as a string.
Note: Using self
to name the current object is a pretty strong convention in Python but not a requirement. However, using another name will raise some eyebrows among your fellow Python developers.
Once you’ve defined a class, you can start instantiating it. In other words, you can start creating objects of that class. To do this, you’ll use a familiar syntax. Just call the class using a pair of parentheses (()
), which is the same syntax that you use to call any Python function:
>>> # Instantiating Person
>>> john = Person("John Doe")
>>> john.name
'John Doe'
In Python, the class name provides what other languages, such as C++ and Java, call the class constructor. Calling a class, like you did with Person
, triggers Python’s class instantiation process, which internally runs in two steps:
- Create a new instance of the target class.
- Initialize the instance with suitable instance attribute values.
To continue with the above example, the value that you pass as an argument to Person
is internally passed to .__init__()
and then assigned to the instance attribute .name
. This way, you initialize your person instance, john
, with valid data, which you can confirm by accessing .name
. Success! John Doe
is indeed his name.
Note: When you call the class to create a new instance, you need to provide as many arguments as .__init__()
requires so that this method can initialize all the instance attributes that demand an initial value.
Now that you understand the object initialization mechanism, you’re ready to learn what Python does before it gets to this point in the instantiation process. It’s time to dig into another special method, called .__new__()
. This method takes care of creating new instances in Python.
Note: The .__new__()
special method is often called a class constructor in Python. However, its job is actually to create new objects from the class blueprint, so you can more accurately call it an instance creator or object creator.
The .__new__()
special method takes the underlying class as its first argument and returns a new object. This object is typically an instance of the input class, but in some cases, it can be an instance of a different class.
If the object that .__new__()
returns is an instance of the current class, then this instance is immediately passed to .__init__()
for initialization purposes. These two steps run when you call the class.
Read the full article at https://realpython.com/python-multiple-constructors/ »
[ 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 ]
February 01, 2025 02:00 PM UTC
Build a Tic-Tac-Toe Game With Python and Tkinter
Developing a tic-tac-toe game in Python using Tkinter combines programming logic with graphical user interface design (GUI). This tutorial guides you through creating the game logic and a Tkinter-based GUI to produce a fully functional tic-tac-toe game. You’ll learn how to manage player moves, detect winning combinations, and build an interactive interface with Tkinter widgets.
By the end of this tutorial, you’ll understand that:
- Python can be used to implement the logic for a tic-tac-toe game.
- Tkinter provides tools to build a game GUI using labels and buttons.
- You can connect the game logic and GUI to create a fully interactive application.
Playing computer games is a great way to unwind or challenge yourself, and it’s also fun and educational to build your own. In this project, you’ll use the Tkinter GUI framework from Python’s standard library to create a game interface, while applying the model-view-controller pattern and an object-oriented approach to organize your code. For more on these concepts, check out the links in the prerequisites.
To download the entire source code for this project, click the link in the box below:
Get Source Code: Click here to get access to the source code that you’ll use to build your tic-tac-toe game.
Demo: A Tic-Tac-Toe Game in Python
In this step-by-step project, you’ll build a tic-tac-toe game in Python. You’ll use the Tkinter tool kit from the Python standard library to create the game’s GUI. In the following demo video, you’ll get a sense of how your game will work once you’ve completed this tutorial:
Your tic-tac-toe game will have an interface that reproduces the classic three-by-three game board. The players will take turns making their moves on a shared device. The game display at the top of the window will show the name of the player who gets to go next.
If a player wins, then the game display will show a winning message with the player’s name or mark (X or O). At the same time, the winning combination of cells will be highlighted on the board.
Finally, the game’s File menu will have options to reset the game if you want to play again or to exit the game when you’re done playing.
If this sounds like a fun project to you, then read on to get started!
Project Overview
Your goal with this project is to create a tic-tac-toe game in Python. For the game interface, you’ll use the Tkinter GUI tool kit, which comes in the standard Python installation as an included battery.
The tic-tac-toe game is for two players. One player plays X and the other plays O. The players take turns placing their marks on a grid of three-by-three cells. If a given player gets three marks in a row horizontally, vertically, or diagonally, then that player wins the game. The game will be tied if no one gets three in a row by the time all the cells are marked.
With these rules in mind, you’ll need to put together the following game components:
- The game’s board, which you’ll build with a class called
TicTacToeBoard
- The game’s logic, which you’ll manage using a class called
TicTacToeGame
The game board will work as a mix between view and controller in a model-view-controller design. To build the board, you’ll use a Tkinter window, which you can create by instantiating the tkinter.Tk
class. This window will have two main components:
- Top display: Shows information about the game’s status
- Grid of cells: Represents previous moves and available spaces or cells
You’ll create the game display using a tkinter.Label
widget, which allows you to display text and images.
For the grid of cells, you’ll use a series of tkinter.Button
widgets arranged in a grid. When a player clicks one of these buttons, the game logic will run to process the player’s move and determine whether there’s a winner. In this case, the game logic will work as the model, which will manage the data, logic, and rules of your game.
Now that you have a general idea of how to build your tic-tac-toe game, you should check out a few knowledge prerequisites that’ll allow you to get the most out of this tutorial.
Prerequisites
To complete this tic-tac-toe game project, you should be comfortable or at least familiar with the concepts and topics covered in the following resources:
Read the full article at https://realpython.com/tic-tac-toe-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 ]
February 01, 2025 02:00 PM UTC
Zero to Mastery
Python Monthly Newsletter 💻🐍
62nd issue of Andrei Neagoie's must-read monthly Python Newsletter: which AI tool to use in 2025, Django vs. FastAPI, and much more. Read the full newsletter to get up-to-date with everything you need to know from last month.
February 01, 2025 10:00 AM UTC
Tryton News
Newsletter February 2025
In the last month we focused on fixing bugs, improving the behaviour of things, speeding-up performance issues - building on the changes from our last release. We also added some new features which we would like to introduce to you in this newsletter.
For an in depth overview of the Tryton issues please take a look at our issue tracker or see the issues and merge requests filtered by label.
Changes for the User
Accounting, Invoicing and Payments
In order to unify our terminology, we now use a reference field instead of the former description field for payments. The idea of the field is more about storing external references than describing the payments. As a reference, it also becomes part of the record name. This helps when using payments with bank checks, as the number of those checks is usually set in the reference field. It is implemented in the following modules:
- account_payment
- account_payment_braintree
- account_payment_sepa
- account_payment_stripe
User Interface
Now we implemented a generic sidebar to display attachment previews and in preparation of the coming chat functionality.
We now add the same bottom padding on the panel body as the top padding.
New Documentation
We re-worked the documentation of the product_classification module.
New Releases
We released bug fixes for the currently maintained long term support series
7.0 and 6.0, and for the penultimate series 7.4 and 7.2.
Changes for Implementers and Developers
To make the customization of accounting move lines from receivable rules we now return the move and lines unsaved. When now applying a rule, the move and the lines are no longer saved before being returned by _reconcile
. So it’s now possible to customize the content.
1 post - 1 participant
February 01, 2025 07:00 AM UTC
Python GUIs
Kivy's UX Widgets: A Quick Exploration — Learn the Basics of UX Widgets in Kivy
Widgets are elements of the graphical user interface (GUI) that provide an application functionality. From buttons and labels to more complex elements like checkboxes, sliders and canvases, widgets receive input and display output. They are the building blocks we use to build user interfaces.
In this tutorial we'll get ourselves up to speed with Kivy's system of widgets, going through the most commonly used widget classes and their basic usage. Before you start, you'll want to be familiar with the basics of how to create apps in Kivy.
- Getting to Know Kivy's UX Widgets
- Displaying a Logo With an Image Widget
- Displaying the Texts With the Label Widget
- Collecting Text Input Data with the TextInput Widget
- Adding a Save Progress? Option
- Creating a Button Widget With the Button Class
- Adding a Progress Bar With ProgressBar
- Exploring Other UI Widgets
- Conclusion
Getting to Know Kivy's UX Widgets
Widgets are an integral part of modern graphical user interfaces. We can define a widget as a graphical user interface component that displays information or provides specific functionality. They provide different ways for your users to interact with your app's interface, whether for input or output. Buttons, text labels, text fields, drop-down lists, scrollbars, and progress bars are common examples of widgets.
In Kivy, everything visible in an application window is a widget, including the window itself.
Kivy includes a rich library of widgets which you can use in your own applications. Each widget is implemented as a Python class
that implements the look and functionality of the widget they create. For example, you can use the Button
class to create a Kivy button. We refer to these as widget classes.
Kivy defines widget classes in dedicated modules that we can find in the kivy.uix
package. Each dedicated module is named after the widget that it defines. For example, the kivy.uix.button
module and the kivy.uix.label
define the Button
and Label
widgets, respectively.
Kivy groups its widgets into five categories five categories:
- UX widgets
- Layouts
- Complex UX widgets
- Behavior widgets
- Screen manager
In this section, we will go through some of the UX widgets, their features, and how to use them in your application code.
At the end of this tutorial, we will have created a Kivy application that uses most of the UX widgets. The app will look something like this:
Kivy demo application with multiple UX widgets
We'll start with a simple application outline and start adding widgets from there. Add the following code to a new file named main.py
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout()
return layout
ApplicationFormApp().run()
Run this from the command line, as follows.
$ python main.py
You will see a window, in a blue green color (as defined by Window.clearcolor
) which contains no other widgets.
Kivy demo app's window
Now, let's start adding widgets to our Kivy demo app. To kick things off, we'll start with an image to display the Kivy logo.
Displaying a Logo With an Image
Widget
You can use the Image
widget to display images in your UI. It requires a source
argument, which should be a Python string representing the file path to your image.
The Image
class can be used for various things in your Kivy applications. For example, you could:
- Display images in an image gallery app
- Add a background image or scene for an app or game
- Display an app's logo on a welcome screen
In our sample app, we'll use the Image
class to display Kivy's logo at the top of the form:
from pathlib import Path
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout(orientation="vertical", padding=[20, 30])
logo_path = (
Path(kivy.__file__).parent / "data" / "logo" / "kivy-icon-512.png"
)
layout.add_widget(Image(source=str(logo_path)))
return layout
ApplicationFormApp().run()
In this code, we create a Kivy application by inheriting the App
class. In the build()
method, we create a `BoxLayout
to organize the widget in your Kivy window. Then, we create a pathlib.Path
object to represent the path to the Kiby logo. Next, we add an Image
instance with the logo as its source
argument. Finally, we return the layout, which will be the app's root.
Here's how our application will look:
Kivy demo app with the Kivy logo
At this point, the image takes up the entire window because there are no other widgets in the layout. As we add other widgets, the BoxLayout
automatically resizes each, stacking the widgets vertically. The orientation is determined by the orientation
parameter we passed when creating the BoxLayout
.
Displaying the Texts With the Label
Widget
The Label
class creates a widget to display text in a Kivy GUI. We're going to add a Label
to our UI to display the title "Application Form"
at the top of our window. We'll also add additional Label
widgets for the labels on the form.
from pathlib import Path
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.uix.label import Label # <-- update
YELLOW = (1, 1, 0, 1)
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout(orientation="vertical", padding=[20, 30])
logo_path = (
Path(kivy.__file__).parent / "data" / "logo" / "kivy-icon-512.png"
)
# updated -->
title_label = Label(
text="Application Form", color=YELLOW, font_size=24
)
fullname_label = Label(text="Full Name", color=YELLOW, font_size=20)
about_label = Label(text="About Yourself", color=YELLOW, font_size=20)
status_label = Label(
text="[size=18][i]Progress: Page 1/2[/i][/size]",
color=YELLOW,
markup=True,
)
# <-- updated
layout.add_widget(Image(source=str(logo_path)))
# updated -->
layout.add_widget(title_label)
layout.add_widget(fullname_label)
layout.add_widget(about_label)
layout.add_widget(status_label)
# <-- updated
return layout
ApplicationFormApp().run()
In this update, we add four labels to our app. These labels will display the "Application Form"
, the "Full Name"
, the "About Yourself"
and the "Progress: Page 1/2"
text. To define each label, we use the text
, color
, and font_size
arguments.
When creating the object you can pass various arguments to to alter the label's appearance. For example:
text
font_size
color
markup
In Kivy, the properties of a widget class are also arguments to the class constructor. So, in this tutorial, we call them either properties or arguments.
The text
argument is a Python string whose value will be displayed on your screen. The color
argument determines the color of your text. Its value must be a tuple or list of three or four numbers in (red, green, blue, alpha)
or (red, green, blue)
format, with each value in the range of 0 to 1. By default, the display color is white or (1, 1, 1, 1)
.
The font_size
argument controls how large your text will be and accepts a string, float, or integer value. If you use a string value, then it must have a unit, say font_size="14sp"
. Otherwise, a default unit (px
for pixels) is used. Other units available include pt
, mm
, cm
, in
, and dp
.
The markup
argument in the status label introduces HTML-like tags for styling a label's appearance. You can use this feature to achieve the same effect as you would have gotten using other individual properties. In this example, we have used the markup property to render its text in italics and also to set its font size to 18.
Run the updated code. You'll see a window like the following:
The Kivy demo app with four additional labels
Now, your sample app has the logo at the top and the labels all the way to the bottom. Note that all the objects are spaced uniformly and the logo we added has now shrunk to take the same amount of space as the other elements. This is the default behavior of Kivy's BoxLayout
.
Collecting Text Input Data with the TextInput
Widget
For an application form to work, users need to be able to enter their information. Next we'll add some input fields, which we'll define using the TextInput
class. As the name suggests, this widget provides a way for users to input text.
Let's modify our code to add the fields for "Full Name"
and "About Yourself"
so we can now accept information from an applicant:
from pathlib import Path
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput # <-- update
YELLOW = (1, 1, 0, 1)
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout(orientation="vertical", padding=[20, 30])
logo_path = (
Path(kivy.__file__).parent / "data" / "logo" / "kivy-icon-512.png"
)
title_label = Label(
text="Application Form", color=YELLOW, font_size=24
)
fullname_label = Label(text="Full Name", color=YELLOW, font_size=20)
about_label = Label(text="About Yourself", color=YELLOW, font_size=20)
status_label = Label(
text="[size=18][i]Progress: Page 1/2[/i][/size]",
color=YELLOW,
markup=True,
)
# update -->
fullname = TextInput(
hint_text="Full name", padding=[5, 5], multiline=False
)
about = TextInput(hint_text="About yourself", padding=[5, 5])
# <-- update
layout.add_widget(Image(source=str(logo_path)))
layout.add_widget(title_label)
layout.add_widget(fullname_label)
layout.add_widget(fullname) # <-- update
layout.add_widget(about_label)
layout.add_widget(about) # <-- update
layout.add_widget(status_label)
return layout
ApplicationFormApp().run()
In this update, we've created the fullname
and about
text inputs, passing the following arguments to TextInput
:
hint_text
padding
multiline
focused
The hint_text
argument accepts string values and gives the user an idea of what the text input is meant for. The padding
argument holds a list of values that define the padding around the text.
The multiline
argument accepts only values of True
or False
and enables or disables the use of multiple lines of text in a text input widget. Its default value is True
, and we have set this to False
for the full name field to accept only a single line of input.
If you run the application now, then you'll see the following:
The Kivy demo app with two additional text inputs
While the full name text input allows only a single line of input, you'll notice that it still appears the same as a multi-line input. This can be confusing for users, so we could adjust the size of the widget. To do this, you can set the size_hint_y
argument to None
and the height
argument to an appropriate value in the call to TextInput()
.
Adding a Save Progress? Option
Now we'll add a Save Progress? option to our app. This is a Boolean option, which we can provide with a few different widgets. In the following sections, we'll add three different UX widgets that allow us to provide Boolean options. We will add a CheckBox
, ToggleButton
, and Switch
widget as a demonstration.
Using a CheckBox
Widget
Checkboxes are widgets that give the user the ability to select multiple options from a list of options. You can use these widgets to give the user a choice of disabling/enabling a feature on an interface.
To create checkboxes, we can use the CheckBox
widget and instantiate it with a few arguments, including the following:
active
group
As with have done so far with other widgets, let's update our GUI by adding a checkbox. We'll also add a label displaying the "Save Progress?"
text:
from pathlib import Path
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.checkbox import CheckBox # <-- update
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
YELLOW = (1, 1, 0, 1)
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout(orientation="vertical", padding=[20, 30])
logo_path = (
Path(kivy.__file__).parent / "data" / "logo" / "kivy-icon-512.png"
)
title_label = Label(
text="Application Form", color=YELLOW, font_size=24
)
fullname_label = Label(text="Full Name", color=YELLOW, font_size=20)
about_label = Label(text="About Yourself", color=YELLOW, font_size=20)
status_label = Label(
text="[size=18][i]Progress: Page 1/2[/i][/size]",
color=YELLOW,
markup=True,
)
fullname = TextInput(
hint_text="Full name", padding=[5, 5], multiline=False
)
about = TextInput(hint_text="About yourself", padding=[5, 5])
# update -->
save_progress = Label(
text="Save progress?", font_size=18, color=YELLOW
)
save_checkbox = CheckBox(active=False)
h_layout = BoxLayout(padding=[0, 5])
h_layout.add_widget(save_progress)
h_layout.add_widget(save_checkbox)
# <-- update
layout.add_widget(Image(source=str(logo_path)))
layout.add_widget(title_label)
layout.add_widget(fullname_label)
layout.add_widget(fullname)
layout.add_widget(about_label)
layout.add_widget(about)
layout.add_widget(h_layout) # <-- update
layout.add_widget(status_label)
return layout
ApplicationFormApp().run()
In this updated version of our app, we add the save_progress
label and the save_checkbox
check box. Note that we've used a horizontal layout to arrange these widgets in a row under the About Yourself text input.
Here's how the app looks now:
The Kivy demo app with an additional checkbox
The CheckBox
class accepts an active
argument, which we can use to set its state. The state can be either checked or unchecked. However, active
can only take values of True
or False
.
Using a ToggleButton
Widget
A ToggleButton
is like a regular button, but it stays either ON or OFF when we click it and remain in the corresponding state until we click it again. We can use this widget to provide a toggle option ON/OFF, much like a CheckBox
.
Among others, ToggleButton
accepts the state
argument, which accepts a string value. The allowed values can be either "down"
or "normal"
.
Once more, let's add to our code to include the toggle button:
from pathlib import Path
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.checkbox import CheckBox
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.togglebutton import ToggleButton # <-- update
YELLOW = (1, 1, 0, 1)
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout(orientation="vertical", padding=[20, 30])
logo_path = (
Path(kivy.__file__).parent / "data" / "logo" / "kivy-icon-512.png"
)
title_label = Label(
text="Application Form", color=YELLOW, font_size=24
)
fullname_label = Label(text="Full Name", color=YELLOW, font_size=20)
about_label = Label(text="About Yourself", color=YELLOW, font_size=20)
status_label = Label(
text="[size=18][i]Progress: Page 1/2[/i][/size]",
color=YELLOW,
markup=True,
)
fullname = TextInput(
hint_text="Full name", padding=[5, 5], multiline=False
)
about = TextInput(hint_text="About yourself", padding=[5, 5])
save_progress = Label(
text="Save progress?", font_size=18, color=YELLOW
)
save_checkbox = CheckBox(active=False)
h_layout = BoxLayout(padding=[0, 5])
h_layout.add_widget(save_progress)
h_layout.add_widget(save_checkbox)
# update -->
toggle = ToggleButton(text="Yes")
h_layout.add_widget(toggle)
# <-- update
layout.add_widget(Image(source=str(logo_path)))
layout.add_widget(title_label)
layout.add_widget(fullname_label)
layout.add_widget(fullname)
layout.add_widget(about_label)
layout.add_widget(about)
layout.add_widget(h_layout)
layout.add_widget(status_label)
return layout
ApplicationFormApp().run()
In this update, we add a toggle button with the text "Yes"
. Again, we use the horizontal layout under the About Yourself text input to arrange the widget in our GUI. Here's how the app looks after this update:
The Kivy demo app with an additional toggle button
Now, when you click the Yes toggle buttons once, you see that it changes to its checked state. You'll know that because the button changes its color to a light blue. If you click the button again, then it gets back to its unchecked or normal state.
Using a Switch
Widget to our Form
Just like the CheckBox
and ToggleButton
, we want to add another way of offering the user an option to save the form's progress. We can do that with a Switch
widget.
The key property of a Switch
is the active
property. This property can have a value of True
or False
corresponding to an ON/OFF position like a light switch.
So, let's add our switch now:
from pathlib import Path
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.checkbox import CheckBox
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.switch import Switch # <-- update
from kivy.uix.textinput import TextInput
from kivy.uix.togglebutton import ToggleButton
YELLOW = (1, 1, 0, 1)
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout(orientation="vertical", padding=[20, 30])
logo_path = (
Path(kivy.__file__).parent / "data" / "logo" / "kivy-icon-512.png"
)
title_label = Label(
text="Application Form", color=YELLOW, font_size=24
)
fullname_label = Label(text="Full Name", color=YELLOW, font_size=20)
about_label = Label(text="About Yourself", color=YELLOW, font_size=20)
status_label = Label(
text="[size=18][i]Progress: Page 1/2[/i][/size]",
color=YELLOW,
markup=True,
)
fullname = TextInput(
hint_text="Full name", padding=[5, 5], multiline=False
)
about = TextInput(hint_text="About yourself", padding=[5, 5])
save_progress = Label(
text="Save progress?", font_size=18, color=YELLOW
)
save_checkbox = CheckBox(active=False)
h_layout = BoxLayout(padding=[0, 5])
h_layout.add_widget(save_progress)
h_layout.add_widget(save_checkbox)
toggle = ToggleButton(text="Yes")
h_layout.add_widget(toggle)
# update -->
switch = Switch(active=True)
h_layout.add_widget(switch)
# <-- update
layout.add_widget(Image(source=str(logo_path)))
layout.add_widget(title_label)
layout.add_widget(fullname_label)
layout.add_widget(fullname)
layout.add_widget(about_label)
layout.add_widget(about)
layout.add_widget(h_layout)
layout.add_widget(status_label)
return layout
ApplicationFormApp().run()
In this new update, we've added a Switch
widget. The active
argument allows you to define whether the switch is ON or OFF. In this example, we set it to True
so the switch is ON. Here's how the application looks at this point:
The Kivy demo app with an additional switch
So now we have three different ways of selecting options. In a regular application, we definitely would need only one of these, preferably the CheckBox
widget.
Creating a Button Widget With the Button
Class
The Button
class comes next. It is one of the most common components of any user interface. It inherits from the Label
class directly, which means it possesses all the properties of a label. Of course, together with some button-specific properties.
We can use the Button
widget to call methods and functions when the user presses or releases the button itself. Around this specific capability, the Button
class has two properties to handle that, which are:
on_press
on_release
On our form, we will create a Submit button to attach a method that will close the app when we click the button. Here's the updated code:
from pathlib import Path
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button # <-- update
from kivy.uix.checkbox import CheckBox
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.switch import Switch
from kivy.uix.textinput import TextInput
from kivy.uix.togglebutton import ToggleButton
YELLOW = (1, 1, 0, 1)
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout(orientation="vertical", padding=[20, 30])
logo_path = (
Path(kivy.__file__).parent / "data" / "logo" / "kivy-icon-512.png"
)
layout.add_widget(Image(source=str(logo_path)))
title_label = Label(
text="Application Form", color=YELLOW, font_size=24
)
fullname_label = Label(text="Full Name", color=YELLOW, font_size=20)
about_label = Label(text="About Yourself", color=YELLOW, font_size=20)
status_label = Label(
text="[size=18][i]Progress: Page 1/2[/i][/size]",
color=YELLOW,
markup=True,
)
fullname = TextInput(
hint_text="Full name", padding=[5, 5], multiline=False
)
about = TextInput(hint_text="About yourself", padding=[5, 5])
save_progress = Label(
text="Save progress?", font_size=18, color=YELLOW
)
save_checkbox = CheckBox(active=False)
h_layout = BoxLayout(padding=[0, 5])
h_layout.add_widget(save_progress)
h_layout.add_widget(save_checkbox)
toggle = ToggleButton(text="Yes")
h_layout.add_widget(toggle)
switch = Switch(active=True)
h_layout.add_widget(switch)
# update -->
submit_button = Button(text="Submit")
submit_button.bind(on_press=self.stop)
# <-- update
layout.add_widget(title_label)
layout.add_widget(fullname_label)
layout.add_widget(fullname)
layout.add_widget(about_label)
layout.add_widget(about)
layout.add_widget(h_layout)
layout.add_widget(submit_button) # <-- update
layout.add_widget(status_label)
return layout
ApplicationFormApp().run()
Here, we've created a button with the text Submit. Then, we use the .bind()
method to connect the on_press
argument to the stop()
method. This connection makes it possible to call the method when we click the button. This method terminates the app's execution.
Now your app looks like in the following screenshot:
The Kivy demo app with an additional button
To close a Kivy app, we use the .stop()
method. This method will gracefully shut down the application and exit the program. It stops all scheduled events, closes the windows, and cleans up any allocated resources.
Adding a Progress Bar With ProgressBar
The ProgressBar
widget is a horizontal bar that displays the progress of an ongoing process, as the name suggests. According to the official documentation, only horizontal progress bars are currently officially supported.
The following arguments are useful when creating a ProgressBar
widget:
value
value_normalized
max
We can use the value_normalized
and value
arguments to set the current progress of the progress bar to a numerical value between 0
and max
. The difference between these two arguments is that value
allows us to set the value directly, while value_normalized
normalizes value
inside the range between 0
and 1
.
The max
argument allows us to set the maximum value of a progress bar. For example, let's say that we have two pages of forms to fill out in our sample app. In this case, we can set max
to 2
. If we're working with percentage, then the max
argument can be 100
, which is the default.
Adding a progress bar to our previous code, we now have the final code:
from pathlib import Path
import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.checkbox import CheckBox
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar # <-- update
from kivy.uix.switch import Switch
from kivy.uix.textinput import TextInput
from kivy.uix.togglebutton import ToggleButton
YELLOW = (1, 1, 0, 1)
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class ApplicationFormApp(App):
def build(self):
layout = BoxLayout(orientation="vertical", padding=[20, 30])
logo_path = (
Path(kivy.__file__).parent / "data" / "logo" / "kivy-icon-512.png"
)
title_label = Label(
text="Application Form", color=YELLOW, font_size=24
)
fullname_label = Label(text="Full Name", color=YELLOW, font_size=20)
about_label = Label(text="About Yourself", color=YELLOW, font_size=20)
status_label = Label(
text="[size=18][i]Progress: Page 1/2[/i][/size]",
color=YELLOW,
markup=True,
)
fullname = TextInput(
hint_text="Full name", padding=[5, 5], multiline=False
)
about = TextInput(hint_text="About yourself", padding=[5, 5])
save_progress = Label(
text="Save progress?", font_size=18, color=YELLOW
)
save_checkbox = CheckBox(active=False)
h_layout = BoxLayout(padding=[0, 5])
h_layout.add_widget(save_progress)
h_layout.add_widget(save_checkbox)
toggle = ToggleButton(text="Yes")
h_layout.add_widget(toggle)
switch = Switch(active=True)
h_layout.add_widget(switch)
submit_button = Button(text="Submit")
submit_button.bind(on_press=self.stop)
# update -->
status_progress = ProgressBar(value=1, max=2)
# <-- update
layout.add_widget(Image(source=str(logo_path)))
layout.add_widget(title_label)
layout.add_widget(fullname_label)
layout.add_widget(fullname)
layout.add_widget(about_label)
layout.add_widget(about)
layout.add_widget(h_layout)
layout.add_widget(submit_button)
layout.add_widget(status_label)
layout.add_widget(status_progress) # <-- update
return layout
ApplicationFormApp().run()
In the added code, we create a progress bar to track the completion of our hypothetic form. Here's how the app looks after this addition:
The Kivy demo app with an additional progress bar
Because the form is supposed to have two pages, we set max
to 2
and value
to 1
. So, the progress bar shows that we're on the first page.
Exploring Other UI Widgets
So far we've looked at some of the most common GUI widgets which are useful for building standard forms. However, Kivy also has a few more specialized widgets which you may find useful in your own applications. Next, we'll look at the Video
widget for playing videos and the Slider
widget, which allows you to select from a range of numerical values.
The Video
Widget
The Video
widget can be used to play video files. It supports a wide range of video formats. Much like the Image
class, the Video
widget class accepts a source
argument that points to the location of the video file we want to play. We can also pass in a value of "play"
to the state
argument to make the video start playing once loaded.
The following app shows a window playing the specified video file:
from kivy.app import App
from kivy.uix.video import Video
class MainApp(App):
def build(self):
player = Video(source="sample-video.mp4", state="play")
return player
MainApp().run()
Make sure to change the file name to the file path of a video in your local drive, or move an MP4 video into the same folder as your script and rename it to sample-video.mp4
.
Run the above code and you'll see a window playing the video passed in the source
argument.
The Video
widget comes may come in handy when you want to build a video player app or display an intro video in a game before it starts.
The Slider
Widget
We can use the Slider
widget to allow users to select from a range of numerical values by moving a thumb along a track. Sliders are commonly used to select numerical values where there are known upper or lower bounds, or for representing progress through a media file.
The Slider
class has the following basic arguments:
value
value_normalized
min
max
step
orientation
The value
, value_normalized
, and max
arguments work similarly to those in the ProgressBar
widget. The min
argument allows us to set the minimum numerical value of the slider.
We can use the step
argument to make the slider's progression in discrete steps, rather than being continuous. For example, a step value of 10
makes the slider go from minimum to maximum values in steps of 10
.
Then, we have the orientation
argument, which accepts either "horizontal"
or "vertical"
as a value. You can use this argument to create either a horizontal or vertical slider. Its default value is "horizontal"
.
The example below creates two sliders of values ranging from 0
to 100
, set at a current value of halfway in this range:
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
Window.clearcolor = (0, 0.31, 0.31, 1.0)
class MainApp(App):
def build(self):
vertical_slider = Slider(
min=0,
max=100,
value=50,
step=10,
orientation="vertical",
)
horizontal_slider = Slider(
min=0,
max=100,
value=50,
)
layout = BoxLayout()
layout.add_widget(vertical_slider)
layout.add_widget(horizontal_slider)
return layout
MainApp().run()
The first slider is vertically oriented, and it progresses in steps of 10
. You can try it out by holding the click on the slider indicator and dragging it up and down. The second slider is horizontally oriented, which is the default orientation. In this case, you have a continuous slider.
Go ahead and run the app. You'll get the following window on your screen:
A Kivy app showing a vertical and a horizontal slider
We can use sliders for a variety of purposes. For example, you can use it to provide volume controls in a media player app. You can also use it in image editing apps for tweaking an image's properties like brightness and contrast.
Conclusion
In this tutorial, we've learned about some of the widgets that Kivy provides. From buttons and labels to images and videos, these widgets provide the basic building block for creating GUI applications with Kivy.
With these few widgets you will be able to start building your own applications with Kivy!
Remember to refer to the widgets documentation as you continue to create apps with Kivy. Each widget discussed here has many useful properties that give you wide control over the appearance and functionality to offer in your GUIs.
February 01, 2025 06:00 AM UTC
January 31, 2025
Test and Code
pytest-mock : Mocking in pytest
pytest-mock is currently the #3 pytest plugin.
pytest-mock is a wrapper around unittest.mock.
In this episode:
- Why the pytest-mock plugin is awesome
- What is mocking, patching, and monkey patching
- What, if any, is the difference between mock, fake, spy, stub.
- Why we might need these in testing
- Some history of mock in Python and how mock became unittest.mock
- From unittest.mock
- patch.object
- patch.object with autospec
- using these as context managers
- pytest-mock:
- The mocker fixture
- Cleanup in teardown
- Using mocker.patch, mocker.spy, and mocker.stub
- Why it's awesome and why you might want to use it over straight unittest.mock
Links:
- top pytest plugins list
- pytest-mock documentation
- unittest.mock
- Podcast episode discussing unittest.mock with Michael Foord
- monkeypatch
Learn pytest
- pytest is the number one test framework for Python.
- Learn the basics super fast with Hello, pytest!
- Then later you can become a pytest expert with The Complete pytest Course
- Both courses are at courses.pythontest.com
Real Python
The Real Python Podcast – Episode #237: Testing Your Python Code Base: Unit vs. Integration
What goes into creating automated tests for your Python code? Should you focus on testing the individual code sections or on how the entire system runs? Christopher Trudeau is back on the show this week, bringing another batch of PyCoder's Weekly articles and projects.
[ 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 ]
Kushal Das
Pixelfed on Docker
I am running a Pixelfed instance for some time now at https://pixel.kushaldas.photography/kushal. This post contains quick setup instruction using docker/containers for the same.
Copy over .env.docker file
We will need .env.docker file and modify it as required, specially the following, you will have to write the values for each one of them.
APP_NAME=
APP_DOMAIN=
OPEN_REGISTRATION="false" # because personal site
ENFORCE_EMAIL_VERIFICATION="false" # because personal site
DB_PASSWORD=
# Extra values to db itself
MYSQL_DATABASE=
MYSQL_PASSWORD=
MYSQL_USER=
CACHE_DRIVER="redis"
BROADCAST_DRIVER="redis"
QUEUE_DRIVER="redis"
SESSION_DRIVER="redis"
REDIS_HOST="redis"
ACITIVITY_PUB="true"
LOG_CHANNEL="stderr"
The actual docker compose file:
---
services:
app:
image: zknt/pixelfed:2025-01-18
restart: unless-stopped
env_file:
- ./.env
volumes:
- "/data/app-storage:/var/www/storage"
- "./.env:/var/www/.env"
depends_on:
- db
- redis
# The port statement makes Pixelfed run on Port 8080, no SSL.
# For a real instance you need a frontend proxy instead!
ports:
- "8080:80"
worker:
image: zknt/pixelfed:2025-01-18
restart: unless-stopped
env_file:
- ./.env
volumes:
- "/data/app-storage:/var/www/storage"
- "./.env:/var/www/.env"
entrypoint: /worker-entrypoint.sh
depends_on:
- db
- redis
- app
healthcheck:
test: php artisan horizon:status | grep running
interval: 60s
timeout: 5s
retries: 1
db:
image: mariadb:11.2
restart: unless-stopped
env_file:
- ./.env
environment:
- MYSQL_ROOT_PASSWORD=CHANGE_ME
volumes:
- "/data/db-data:/var/lib/mysql"
redis:
image: zknt/redis
restart: unless-stopped
volumes:
- "redis-data:/data"
volumes:
redis-data:
I am using nginx
as the reverse proxy. Only thing to remember there is to
pass .well-known/acme-challenge
to the correct directory for letsencrypt
,
the rest should point to the contianer.
Seth Michael Larson
Significant whitespace
There is power in what you refuse to type; creating significant whitespace.
Matt Layman
Finishing Simple Signup - Building SaaS #213
In this episode, I completed the simplified sign up process for my JourneyInbox app. I finished off the final features that add account verification and initial engagement features to make sign up and nice and functional experience.