Skip to content

aiorepl: Can't assign top-level object attributes, but lower level objects work #681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sandyscott opened this issue Jun 14, 2023 · 2 comments

Comments

@sandyscott
Copy link

Apologies if the title isn't clear, hopefully this example will explain my issue:

So I'm using aiorepl to help make certain bits of my code interactive, and I pass object.__dict__ to aiorepl.task().

What's confused me is which bits of the object I can actually modify - basically I only seem to be able to assign values to properties of sub-objects - assigning a value to a property of the top level object just results in a TypeError. In this example the only assignment that works is to inner.a

import aiorepl
import uasyncio as asyncio

class innerClass:
    a = 1
    
class outerClass:
    b = 2
    c = innerClass()
    def __init__(self):
        self.d = 3
        self.inner = innerClass()

def main():
    obj = outerClass()
    asyncio.create_task(aiorepl.task(obj.__dict__))
    while True:
        await asyncio.sleep(0)

asyncio.run(main())

Now to run it and try to access the properties of the outerClass and innerClass objects:

Starting asyncio REPL...
--> b
NameError: name 'b' isn't defined
--> c
NameError: name 'c' isn't defined
--> d
3
--> inner
<innerClass object at 2000ba00>
--> inner.a
10
--> d = 10
TypeError: 
--> inner.a = 11
--> inner.a
11

I might not be understanding __dict__ properly, but I think it would help if there was a way of making this behave a bit more intutively - ie. I'd like to pass an object (maybe wrapped in some function to pull out the relevant bits) and I can then access the object like you would at a normal python terminal - read/write access to all the properties, and can call methods.

@jimmo
Copy link
Member

jimmo commented Jun 19, 2023

hi @sandyscott -- Interesting question! The ability to set the globals dict was mostly intended to be passed a module's dict.

In MicroPython the dictionary returned by __dict__ on a class instance is read-only. (This is different to CPython). It's not a copy, it references the underlying members dict, but it's marked as read-only.

b and c aren't available because they aren't part of the instance's __dict__ (this is the same in CPython).

Setting nested members works because it's a regular read-only lookup of the first level, then eval() is taking care of the assignment.

In theory you could construct a dict-like object (that implements __setitem__ etc) that wraps an instance type, but unfortunately in MicroPython, the dict passed to eval (used internally in aiorepl) needs to be an actual dict.

I suspect the best workaround for your use case is to make a very simple wrapper dict, i.e. aiorepl.task({"obj": obj }) which would allow you to do --> obj.d = 10 etc. Not quite so convenient but I haven't been able to think of a better workaround.

@sandyscott
Copy link
Author

Hi @jimmo.

Thanks for the detailed response - after a bit more digging I realise __dict__ is somewhat more of a minefield than I'd realised. I also found a roughly equivalent workaround (wrapping it in a minimal object, then using __dict__) but your way is neater.

I'm satisfied that my problem is solved, if you or another maintainer thinks this is worth adding to the documentation, I created PR 687 which adds an extra paragraph to cover this use case.

@jimmo jimmo closed this as completed Jul 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants