ptmcg's Avatar

ptmcg

@ptmcg-pm

Author/maintainer of pyparsing, littletable, plusminus, logmerger; co-author of Python in a Nutshell 4th edition

15
Followers
41
Following
31
Posts
26.09.2025
Joined
Posts Following

Latest posts by ptmcg @ptmcg-pm

And these:
- command-line access to stdlib modules (python -m <name>)
- uuid (3.12)
- json (3.14)
- random (3.13)
- sqlite3 (3.12)
- http .server (3.4)

07.03.2026 23:50 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

Skim the "What's New in Python x.x" notes for the past few releases, there have been some handy things added:
- itertools.batched (3.12)
- access re.Match groups using [] notation (faster than group()) (3.6)
- 1_000_000 is a legal syntax for 1000000 (3.6)
- contextlib.chdir (3.11)

07.03.2026 23:47 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0
Preview
Search: 109 results found for "paul mcguire" Personalize and protect your iPhone, iPad, MacBook, Dell, Microsoft and Samsung devices with artist-designed and custom skins and cases.

There's no link in this article, but if you like these pieces, many are available as phone cases and laptop skins at www.gelaskins.com/search?q=pau...

31.01.2026 14:36 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0
Preview
Featured Artist Paul McGuire | Artsy Shark Blending math and imagination, Paul McGuire creates fractal-inspired images that reveal unexpected beauty in algorithmic form. View more on his

I'm excited to share this article published this week in the Artsy Shark blog, on my artistic journey with generative art (which I've been doing since the mid-1980's, so NOT AI). www.artsyshark.com/2026/01/26/f... #art #digitalart #abstractart #generativeart #fractals

31.01.2026 14:18 ๐Ÿ‘ 4 ๐Ÿ” 0 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0
Preview
Featured Artist Paul McGuire | Artsy Shark Blending math and imagination, Paul McGuire creates fractal-inspired images that reveal unexpected beauty in algorithmic form. View more on his

Featured this week on the Artsy Shark website! Includes some of my favorite pieces, and some backstory on how they all came about.

Featured Artist Paul McGuire www.artsyshark.com/2026/01/26/f...

27.01.2026 17:13 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0
Post image

If you're a Python developer using Claude Code, or something similar, we just published a blog that covers 10 rules we add to our prompt for drastically improved code gen.

And we include the full prompt at the end of the post!

dagster.io/blog/dignifi...

09.01.2026 17:26 ๐Ÿ‘ 4 ๐Ÿ” 2 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

Even though they have refactored pyparsing out of packaging, I'm proud of its prior inclusion in this core Python component.

10.01.2026 06:58 ๐Ÿ‘ 2 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

Add `time.sleep((366 if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0) else 365)*24*60*60)` to your loop, and you can just leave it running (barring future leap seconds).

31.12.2025 09:15 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0
Snippet of AI instructions, available by calling `pyparsing.show_best_practices()` from Python

Snippet of AI instructions, available by calling `pyparsing.show_best_practices()` from Python

Example showing DeprecationWarning raised when calling pyparsing `parseString` instead of `parse_string`

Example showing DeprecationWarning raised when calling pyparsing `parseString` instead of `parse_string`

TINY code example of a leap year determination function.

Complete code:
int is_divisible(int a, int b) {
    float quo := a / b;
    int iquo := quo;
    return quo = iquo;
}

int is_leap_year(int yr) {
    int div_by_400 := is_divisible(yr, 400);
    if div_by_400 then return 1; end

    int div_by_100 := is_divisible(yr, 100);
    if div_by_100 then return 0; end

    int div_by_4 := is_divisible(yr, 4);
    return div_by_4;
}

int main() {
    string s;
    int year;
    int this_year;

    read this_year;
    repeat
        read yr;
        if yr = "q" then return 0; end
        year := yr;
        is_leap := is_leap_year(year);

        write year; write " ";
        if is_leap then
            if year > this_year then
                write "will be";
            elseif year < this_year then
                write "was";
            else
                write "is";
            end
        else
            if year > this_year then
                write "will not be";
            elseif year < this_year then
                write "was not";
            else
                write "is not";
            end
        end
        write " a leap year"; write endl;
    until 0
}

TINY code example of a leap year determination function. Complete code: int is_divisible(int a, int b) { float quo := a / b; int iquo := quo; return quo = iquo; } int is_leap_year(int yr) { int div_by_400 := is_divisible(yr, 400); if div_by_400 then return 1; end int div_by_100 := is_divisible(yr, 100); if div_by_100 then return 0; end int div_by_4 := is_divisible(yr, 4); return div_by_4; } int main() { string s; int year; int this_year; read this_year; repeat read yr; if yr = "q" then return 0; end year := yr; is_leap := is_leap_year(year); write year; write " "; if is_leap then if year > this_year then write "will be"; elseif year < this_year then write "was"; else write "is"; end else if year > this_year then write "will not be"; elseif year < this_year then write "was not"; else write "is not"; end end write " a leap year"; write endl; until 0 }

This past weekend I pushed version 3.3.1 of pyparsing to PyPI (3.3.0 had a small but CI-stopping packaging blip). Includes AI instructions of pyparsing best practices. Deprecated camelCase names now throw DeprecatedWarnings. TINY teaching language parser+REPL
#PythonDevelopment #python #pyparsing

25.12.2025 04:46 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0
int is_divisible(int a, int b) {
    int quo := a / b;
    return quo * b = a;
}

int is_prime(int n) {
    if i = 2 || i = 3 || i = 5 || i = 7 then return 1; end

    if is_divisible(n, 2) then return 0; end
    if is_divisible(n, 3) then return 0; end
    if is_divisible(n, 5) then return 0; end
    if is_divisible(n, 7) then return 0; end

    /* n is prime, assuming it is not > 120 */
    return 1;
}

int main() {
    int i := 2;
    repeat
        if is_prime(i) then write i; write endl; end
        i := i + 1;
    until i > 120
}

int is_divisible(int a, int b) { int quo := a / b; return quo * b = a; } int is_prime(int n) { if i = 2 || i = 3 || i = 5 || i = 7 then return 1; end if is_divisible(n, 2) then return 0; end if is_divisible(n, 3) then return 0; end if is_divisible(n, 5) then return 0; end if is_divisible(n, 7) then return 0; end /* n is prime, assuming it is not > 120 */ return 1; } int main() { int i := 2; repeat if is_prime(i) then write i; write endl; end i := i + 1; until i > 120 }

A TINY script for finding prime numbers

26.11.2025 05:23 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

The TINY interpreter was written with AI assistance (Junie in PyCharm), using the AI-targeted best practices that ship with this release of pyparsing. The interpreter examples include a transcript of the AI session, including prompts and the corresponding AI plans and actions taken.

26.11.2025 03:52 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0
Preview
Release Pyparsing 3.3.0b1 ยท pyparsing/pyparsing (added in 3.3.0b1) Implemented a TINY language parser/interpreter using pyparsing, in the examples/tiny directory. This is a little tutorial language that I used to demonstrate how to use pyparsi...

I just published pyparsing version 3.3.0b1, with some significant additions:
- example implementation of the TINY language
- performance tests with scripts to run and tabulate results using pyparsing 3.1-3.3 and Python 3.9-3.14

Github link: github.com/pyparsing/py...

#pyparsing #python #parsing

26.11.2025 03:18 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0
Screenshot of the logmerger utility showing a time-based merge of two large log files, with a progress bar in the lower left corner.

Screenshot of the logmerger utility showing a time-based merge of two large log files, with a progress bar in the lower left corner.

I just released version 0.13.0, with some significant performance speedups, and visibility to loading time while building the display DataTable. Changelog here: github.com/ptmcg/logmer...

15.11.2025 17:07 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

But do you pronounce it "toople" or "tupple"?

09.10.2025 07:16 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

And even a "<<=" operator!

01.10.2025 12:14 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0
12 prints of colorful abstract art by Paul McGuire, printed on business cards with simple white and black frames. Arranged on my kitchen table, quarter show for scale.

12 prints of colorful abstract art by Paul McGuire, printed on business cards with simple white and black frames. Arranged on my kitchen table, quarter show for scale.

Mini-gallery of business card-sized prints of my artwork. Submitting these to the Bee Cave Arts Foundation's Christmas Fair. Prints by moo.com #art #digitalart #beecaveartsfoundation

01.10.2025 05:42 ๐Ÿ‘ 2 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

With the upcoming release of #pyparsing 3.3.0, this code would emit DeprecationWarnings: nestedExpr is deprecated in favor of nested_expr, and parseString in favor of parse_string. A conversion utility ships with pyparsing to auto-update all of these pre-PEP8 names to PEP-8 compliant snake case.

29.09.2025 19:26 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

`operator.attrgetter("first")` and `operator.attrgetter("last")` would work here too. `attrgetter` can also drill down into the structure of an object, like `operator.attrgetter("address.postal_code")`. Best of all, attrgetter runs at C speed, when lambdas are the Pokey Little Puppy of Python.

29.09.2025 16:38 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

Get a post-it note and write CUT ME on it. Attach it to the jacket's coat tail, but do so surreptitiously to avoid embarrassment.

29.09.2025 15:17 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0
Pyparsing 3.3.0a1 ยท pyparsing pyparsing ยท Discussion #620 Supports Python 3.14 and free-threaded 3.14t. (Note: this does not mean that pyparsing is thread-safe, simply that pyparsing can safely run using the free-threaded build of Python.) The version 3.3...

I pushed out version 3.3.0a1 late yesterday, so that folks with existing apps can upgrade their code from the deprecated pre-PEP8 API, or to try out the AI instructions to generate a new parser app. There is a discussion link here: github.com/pyparsing/py...

29.09.2025 14:05 ๐Ÿ‘ 0 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

This pigment is a plot device in the book "Sacre Bleu", a fantasy/light horror with a heavy dose of art history, set in Paris during the Impressionist period.

29.09.2025 07:31 ๐Ÿ‘ 3 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0
Snippet extracted from the AI instructions that will be bundled with the upcoming 3.3.0 release of pyparsing:

$ python -m pyparsing.ai.show_best_practices
<!-- 
This file contains instructions for best practices for developing parsers with pyparsing, and can be used by AI agents
when generating Python code using pyparsing.
-->

## Planning
- If not provided or if target language definition is ambiguous, ask for examples of valid strings to be parsed
- Before developing the pyparsing expressions, define a Backus-Naur Form definition and save this in docs/grammar.md. Update this document as changes are made in the parser.

## Implementing
- Import pyparsing using "import pyparsing as pp", and use that for all pyparsing references.
- When writing parsers that contain recursive elements (using Forward() or infix_notation()), immediately enable packrat parsing for performance: `pp.ParserElement.enable_packrat()` (call this right after importing pyparsing). See https://pyparsing-docs.readthedocs.io/en/latest/HowToUsePyparsing.html.
  - For recursive grammars, define placeholders with `pp.Forward()` and assign later using the `<<=` operator; give Forwards meaningful names with `set_name()` to improve errors.
- Use PEP8 method and argument names in the pyparsing API ("parse_string", not "parseString").
- Do not include expressions for matching whitespace in the grammar. Pyparsing skips whitespace by default.
...

Snippet extracted from the AI instructions that will be bundled with the upcoming 3.3.0 release of pyparsing: $ python -m pyparsing.ai.show_best_practices <!-- This file contains instructions for best practices for developing parsers with pyparsing, and can be used by AI agents when generating Python code using pyparsing. --> ## Planning - If not provided or if target language definition is ambiguous, ask for examples of valid strings to be parsed - Before developing the pyparsing expressions, define a Backus-Naur Form definition and save this in docs/grammar.md. Update this document as changes are made in the parser. ## Implementing - Import pyparsing using "import pyparsing as pp", and use that for all pyparsing references. - When writing parsers that contain recursive elements (using Forward() or infix_notation()), immediately enable packrat parsing for performance: `pp.ParserElement.enable_packrat()` (call this right after importing pyparsing). See https://pyparsing-docs.readthedocs.io/en/latest/HowToUsePyparsing.html. - For recursive grammars, define placeholders with `pp.Forward()` and assign later using the `<<=` operator; give Forwards meaningful names with `set_name()` to improve errors. - Use PEP8 method and argument names in the pyparsing API ("parse_string", not "parseString"). - Do not include expressions for matching whitespace in the grammar. Pyparsing skips whitespace by default. ...

The upcoming 3.3.0 release of pyparsing will include AI instructions for best practices when using this package. They can be accessed from the command-line using `python -m pyparsing.ai.show_best_practices` (They work for human developers too.) #python #pythondev #pyparsing #ai

29.09.2025 05:51 ๐Ÿ‘ 3 ๐Ÿ” 1 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0
Preview
PyPI ยท The Python Package Index The Python Package Index (PyPI) is a repository of software for the Python programming language.

#art link: paulmcguireart.com
#python #pyparsing link: pypi.org/project/pypa...
Python #littletable link: pypi.org/project/litt...

28.09.2025 18:47 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

I should have added the #Art and #DigitalArt hashtags (always forgetting those hashtags...) :shrug-emoji:

28.09.2025 16:43 ๐Ÿ‘ 2 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0
A digital art abstract image of blues and greens, with a ragged white bottom. The title of this piece is "wave"

A digital art abstract image of blues and greens, with a ragged white bottom. The title of this piece is "wave"

A digital art abstract image, mostly white, but with a field of pale colors in the lower left corner, with a ragged diagonal border running from upper left to lower right. In the middle of this border is an irregular clump of color, as if it were a stain from multiple colors of ink, washed over the side of a snowy ridge. The title of this piece is "water music II".

A digital art abstract image, mostly white, but with a field of pale colors in the lower left corner, with a ragged diagonal border running from upper left to lower right. In the middle of this border is an irregular clump of color, as if it were a stain from multiple colors of ink, washed over the side of a snowy ridge. The title of this piece is "water music II".

Greetings, BlueSky-ers! I'm new here so I wanted to do a quick introduction. My name is Paul McGuire, I've been using Python for about 24 years. I maintain the #pyparsing package, which is about to have its release 3.3.0 next month. I also make digital art. #PythonDev #Python #Introduction

28.09.2025 16:33 ๐Ÿ‘ 5 ๐Ÿ” 0 ๐Ÿ’ฌ 2 ๐Ÿ“Œ 0

Oh, and you can type hint the hh variable as:

hh: Heapq[tuple[str, int]] = Heapq()

28.09.2025 13:11 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0
import heapq

class Heapq[T]:
    def __init__(self, data: list[T] = None):
        self._data = data or []
        heapq.heapify(self._data)

    def push(self, item: T):
        heapq.heappush(self._data, item)

    def pop(self) -> T:
        return heapq.heappop(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __repr__(self) -> str:
        return repr(self._data)


hh = Heapq()
hh.push(('b', 1000))
hh.push(('a', 3))
hh.push(('a', 1))
hh.push(('a', 2))

print(len(hh))
print(bool(hh))

while hh:
    print(hh.pop())

import heapq class Heapq[T]: def __init__(self, data: list[T] = None): self._data = data or [] heapq.heapify(self._data) def push(self, item: T): heapq.heappush(self._data, item) def pop(self) -> T: return heapq.heappop(self._data) def __len__(self) -> int: return len(self._data) def __repr__(self) -> str: return repr(self._data) hh = Heapq() hh.push(('b', 1000)) hh.push(('a', 3)) hh.push(('a', 1)) hh.push(('a', 2)) print(len(hh)) print(bool(hh)) while hh: print(hh.pop())

Output of the Heapq script:
4
True
('a', 1)
('a', 2)
('a', 3)
('b', 1000)

Output of the Heapq script: 4 True ('a', 1) ('a', 2) ('a', 3) ('b', 1000)

Here is a Heapq class wrapper around those heapq functions, if you really want to en-class-ify them.

Code is in the ALT text.

28.09.2025 13:09 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0

And math.hypot is not limited to 2 dimensions (or even 3).

27.09.2025 21:39 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 1 ๐Ÿ“Œ 0

And thanks a ton for putting this package together, it's great for documentation (and parser design review too - I cleaned up a number of the example parsers after seeing weirdness in the generated diagram), and it turns pyparsing into a much more powerful teaching tool.

27.09.2025 20:59 ๐Ÿ‘ 1 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0

The latest update is to add HREF URLs to the diagram elements that refer to their actual definitions elsewhere in the diagram so that you can interact with them, a huge help when a diagram gets big. Pull the pyparsing repo, you'll see a number of diagrams already generated in the examples directory.

27.09.2025 20:58 ๐Ÿ‘ 2 ๐Ÿ” 0 ๐Ÿ’ฌ 0 ๐Ÿ“Œ 0