Help: can't set attribute in 3.9.14

Hi. I’m writing code in a Python 3.9.9 environment and running it in a 3.9.14 environment. But I’m running into a problem.

I’m using pyparsing (3.0.9) and adding attributes to ParseResults objects, e.g.:
node.extra_info = something

It works fine under 3.9.9 but triggers an AttributeError: 'ParseResults' object has no attribute 'extra_info' under 3.9.14.

I assume something changed between 3.9.9 and 3.9.14. What would it be, and is there a way around it?

Thank you.

What version of pyparsing are you using in the 3.9.14 environment?

Can you demonstrate a minimal example of this code? E.g.

import pyparsing
print(pyparsing.__version__)
something = ...
something.extra = "something"
print("done")

Ouch! The 3.9.14 environment has pyparsing 3.0.9, but the Python 3.9.9 environment has pyparsing 2.4.7!

So would that suggest that pyparsing might’ve had its objects re-implemented in C, so that they no longer have a _dict_?

Is there any way to attach extra information to such an object, or do I simply need a different approach?

Or they got __slots__ added, which would have the same effect.

One option, assuming that a ParseResults is hashable, would be to maintain your own dictionary that maps the results object to your own attributes. Or subclass the ParseResults and do nothing:

class ParseResults(ParseResults): pass

This will re-add a dict to it. But then you might have to monkey-patch that back into the place that creates them, which may not be a good idea.

The source code suggests that it was converted to slots for performance reasons. You could possibly ask the devs to add a userdata slot, which would be the same as just adding attributes, but safe against future changes, and wouldn’t have as big a performance cost as keeping a __dict__ on everything (since it would only be added when YOU choose to add it). In your code, the only change would be:

# Was:
node.extra_info = something
# Now:
node.userdata = {"extra_info": something}

although it’s a little more complicated if you have multiple possible extra info fields and all of them are optional.

Pyparsing’s ParseResults class is somewhat split-personalitied. It is one of the classes that can be dict-like and namespace-like. (I was still somewhat new to Python when I wrote pyparsing, and I wasn’t sure which paradigm would be preferred. So I figured I would just support both.)

Here is a little parser that just gets a word and an integer, to get us a ParseResults object to work with:

import pyparsing as pp

a_word = pp.Word(pp.alphas)
a_number = pp.Word(pp.nums)
parser = a_word("some_word") + a_number("some_number")

result = parser.parse_string("Hi 1729")

(You can see this parser in railroad diagram form by setting names on the a_word, a_number, and parser expressions, and then calling parser.create_diagram with an output HTML file name.)

image

As you have already found, this won’t work:

result.some_data = "this won't work"

(I’m pleased to see that PyCharm will actually highlight this line, and indicate that result is read-only in this manner.)

However, you can make this assignment using the ParseResults dict-like access:

result["some_data"] = "this will work!"

And because ParseResults supports namespace-style access, you can then follow this with:

print(result.some_data)

A nice method on ParseResults is dump(), which will output a nested listing of the contents of a ParseResults including its named elements:

print(result.dump())

Giving:

['Hi', '1729']
- some_data: 'this will work!'
- some_number: '1729'
- some_word: 'Hi'
1 Like