Migrating from github to codeberg: existing readthedocs projects
Recently, I made the decision to migrate select projects of mine from github to codeberg.
Today I started migrating wwvbpy, which has an existing readthedocs configuration. In readthedocs "edit project" page, the "repository URL" field was greyed out and uneditable.
It took me a few minutes but I eventually realized that before I could set the project URL I had to clear the "connected repository" and save the settings.
After that, I was able to manually edit the URL and then manually configure the outgoing webhooks, so that pushes to codeberg would trigger doc builds.
Variations on 'if TYPE_CHECKING'
Suggested by mypy documentation:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import …
Works in mypy, pyright, pyrefly. Found in setuptool_scm generated __version__.py
:
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import …
Works in mypy, pyright, pyrefly. Best variant for CircuitPython?
def const(x): return x
TYPE_CHECKING = const(0)
if TYPE_CHECKING:
from typing import …
Works in mypy only. Does not work in pyright, pyrefly:
if False:
from typing import …
Dear Julian
Julian,
I just found the message you wrote in 2018.
I miss you and I wish you were in my life.
That is all, that's the message.
Mutual Tail Recursion in Python, fully mypy-strict type checked
As one does, I was thinking about how Python is criticized for lacking tail recursion optimization.
I came up with an idea of how to implement this without new language features, by using a decorator around the tail recursive function that catches a special Recur
exception and then turns around and calls the same function with the new arguments:
class Recur(BaseException, Generic[P]):
args: P.args
kwargs: P.kwargs
def __init__(self, *args: P.args, **kwargs: P.kwargs):
super().__init__(*args)
self.kwargs = kwargs
def recurrent(f: Callable[P, T]) -> Callable[P, T]:
@functools.wraps(f)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
while True:
try:
return f(*args, **kwargs)
except Recur as r:
args = r.args
kwargs = r.kwargs
return wrapper
It can be used like so:
@recurrent
def gcd(a: int, b: int) -> int:
print(f"gcd({a}, {b})")
if b == 0:
return a
raise Recur(b, a % b)
print(gcd(1071, 462))
This is not an original idea. It has been documented before e.g., by Chris Penner. This code all properly type-checks under mypy --strict
(some context not shown). However, it doesn't allow mutual tail recursion.
I'll be honest: I didn't find any well-motivated examples for mutual tail recursion! Everyone uses the same awful poorly-motivated example of is-odd
and is-even
. But, because it was a challenge to placate the mypy type checker, I wanted to implement it anyway.
The problem lies in the implementation of the wrapper
function: args
and kwargs
have the types given in the initial recurrent call, and the types can't change just because f
changes.
The solution, which I realized a few weeks later, was to move the responsibility to actually dispatch the recurrent call into the Recur
instance. There can be many Recur
instances, but there inside the wrapper function they all simply have the same type: Recur
!
Here's the full implementation, which type checks clean with mypy --strict
(1.11.2) and runs in python 3.11.2:
from __future__ import annotations
import functools
from typing import Callable, ParamSpec, TypeVar, Generic, NoReturn
P = ParamSpec("P")
T = TypeVar("T")
class Recur(BaseException, Generic[P, T]):
f: Callable[P, T]
args: P.args
kwargs: P.kwargs
def __init__(self, f: Callable[P, T], args: P.args, kwargs: P.kwargs):
super().__init__()
self.f = f.f if isinstance(f, Recurrent) else f
self.args = args
self.kwargs = kwargs
def __call__(self) -> T:
return self.f(*self.args, **self.kwargs)
def __repr__(self) -> str:
if self.kwargs:
return f"<Recur {self.f.__name__}(*{self.args}, **{self.kwargs})>"
return f"<Recur {self.f.__name__}{self.args})>"
__str__ = __repr__
class Recurrent(Generic[P, T]):
f: Callable[P, T]
def __init__(self, f: Callable[P, T]) -> None:
self.f = f
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
r = Recur(self.f, args, kwargs)
while True:
try:
return r()
except Recur as exc:
r = exc
def recur(self, *args: P.args, **kwargs: P.kwargs) -> NoReturn:
raise Recur(self.f, args, kwargs)
def __repr__(self) -> str:
return f"<Recurrent {self.f.__name__}>"
And here's an example use:
import sys
from recur import Recurrent
@Recurrent
def gcd(a: int, b: int) -> int:
print(f"gcd({a}, {b})")
if b == 0:
return a
gcd.recur(b, a % b)
@Recurrent
def is_even(a: int) -> bool:
assert a >= 0
if a == 0:
return True
is_sum_odd.recur(a, -1)
@Recurrent
def is_sum_odd(a: int, b: int) -> bool:
c = a + b
assert c >= 0
if c == 0:
return False
is_even.recur(c - 1)
print(gcd)
print(gcd(1071, 462))
print(is_even(sys.getrecursionlimit() * 2))
print(is_even(sys.getrecursionlimit() * 2 + 1))
Enabling markdown
This blog is based on its own weird markup language.
Now in 2024 everyone's used to markdown instead.
So I made a markdown tag for this blog.
You won't notice much as a reader, but for me this is a big convenience.
One thing I noticed is that code rendered inside backticks
looks a bit
different.
Talking directly to in-process tcl/tk
I recently saw a post on a blog about using wish as a subprocess of Python, as a way to access tk without the complexity of tkinter.
To be clear the original post also calls out the situation where the tkinter part of python is not installed by default, and my technique would not be applicable there.
So what do you do if you like Tk but don't care for the high level abstraction provided by Tkinter? Well, you can import _tkinter and create a tkapp object with _tkinter.create().
The _tkinter module and the tkapp object is largely undocumented, but in Python 3.11 here are some useful methods:
- tkapp.createcommand: Create a callback into Python code. Takes a string and a callable. Creates a Tcl command with that name, that calls back into Python code: app.createcomand("cb", lambda *args: print("cb", args))
- tkapp.eval: Takes a command and evaluates it. Call it with a single string: app.eval("button .b -text HI -command {cb arg1 arg2}")
- tkapp.call: Takes a series of arguments, does proper Tcl quoting, and evaluates it: app.call("pack", ".b")
Python 3.11.2 (main, Aug 26 2024, 07:20:54) [GCC 12.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import _tkinter >>> app = _tkinter.create() >>> app.createcommand("cb", lambda *args: print("cb", args)) >>> app.eval("button .b -text HI -command {cb arg1 arg2}") '.b' >>> app.call("pack", ".b") '' >>> # Now I click the button several times ... >>> cb ('arg1', 'arg2') cb ('arg1', 'arg2') cb ('arg1', 'arg2')
Vintage X Bitmap Font "VT100 graphics characters"
In an old "5x9" bitmap font a friend gave me I was puzzled about the chacters in positions 1..31, normally used as control characters.
It turns out these are the "VT100 graphics characters", and the xfonts-utils
program ucs2any can optionally put specific Unicode code points in these spots:
/* DEC VT100 graphics characters in the range 1-31 (as expected by
some old xterm versions and a few other applications) */
#define decmap_size 31
static int decmap[decmap_size] = {
0x25C6, /* BLACK DIAMOND */
0x2592, /* MEDIUM SHADE */
0x2409, /* SYMBOL FOR HORIZONTAL TABULATION */
0x240C, /* SYMBOL FOR FORM FEED */
0x240D, /* SYMBOL FOR CARRIAGE RETURN */
0x240A, /* SYMBOL FOR LINE FEED */
0x00B0, /* DEGREE SIGN */
0x00B1, /* PLUS-MINUS SIGN */
0x2424, /* SYMBOL FOR NEWLINE */
0x240B, /* SYMBOL FOR VERTICAL TABULATION */
0x2518, /* BOX DRAWINGS LIGHT UP AND LEFT */
0x2510, /* BOX DRAWINGS LIGHT DOWN AND LEFT */
0x250C, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */
0x2514, /* BOX DRAWINGS LIGHT UP AND RIGHT */
0x253C, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
0x23BA, /* HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */
0x23BB, /* HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */
0x2500, /* BOX DRAWINGS LIGHT HORIZONTAL */
0x23BC, /* HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */
0x23BD, /* HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */
0x251C, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
0x2524, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */
0x2534, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */
0x252C, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
0x2502, /* BOX DRAWINGS LIGHT VERTICAL */
0x2264, /* LESS-THAN OR EQUAL TO */
0x2265, /* GREATER-THAN OR EQUAL TO */
0x03C0, /* GREEK SMALL LETTER PI */
0x2260, /* NOT EQUAL TO */
0x00A3, /* POUND SIGN */
0x00B7 /* MIDDLE DOT */
Apparently on a real VT100 these are printed by sending various escape codes that cause them to be mapped at character values 95..126. various escape sequences.
datetime in local time zone
ruff and flake8 consider that it is a mistake to ever use "naive" datetime objects.
However, this leaves no "obvious" way to do something like convert between local and UTC time using the datetime module!
I was not able to find a way in standard Python, but I was able to find it in dateutil (based on some stackoverflow where the top answer was a wrong answer, purposely not linked)
So: Use dateutil.tz.gettz() to get a timezone object that reflects the system's local time zone. This will pass ruff's checks.
>>> localtime = dateutil.tz.gettz() >>> localnow datetime.datetime(2024, 7, 8, 21, 33, 51, 897086, tzinfo=tzfile('/etc/localtime')) >>> localnow.astimezone(datetime.timezone.utc) datetime.datetime(2024, 7, 9, 2, 33, 51, 897086, tzinfo=datetime.timezone.utc)
You can also use a piece of code from the documentation called LocalTimeZone that makes a best effort to implement the local system time zone based on the items available in the "time" module. This has caveats, such as not working properly when a zone historically had different offsets than it does today, and you can't pip install it, you have to copy & paste it.
Sunflower Butter aka Sun Butter or Sunbutter (plus Chocolate Sun Butter)
An efficient pair of polynomials for approximating sincos
Leaving my roles in LinuxCNC
Linux ThinkPad T16 Microphone "Muted" Indicator
Notes on using skyui with Skyrim Anniversary Edition from GOG
"Letter Boxed" puzzle statistics
NYT "Letter Boxed" solver in C++
Conservation of Experience
All older entries
Website Copyright © 2004-2024 Jeff Epler