8000 RFC: py/stream: Add user-streams adaptor. by dpgeorge · Pull Request #2824 · micropython/micropython · GitHub
[go: up one dir, main page]

Skip to content

RFC: py/stream: Add user-streams adaptor. #2824

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
wants to merge 1 commit into from

Conversation

dpgeorge
Copy link
Member

This allows one to write classes that act (via duck-typing) like an internal stream object. For example, one can now do the following:

    class S:
        def write(self, buf):
            print('write:', repr(buf))
            return(len(buf))
    s = S()
    print('abc', file=s)

The above code almost works the same on CPython, except there the argument buf to the write method is a bytes object, rather than a bytearray.

See #2747 (comment) for previous discussion leading to this.

For example, one can now do the following:

    class S:
        def write(self, n):
            print('write:', repr(n))
            return(len(n))
    s = S()
    print('abc', file=s)
@dpgeorge dpgeorge changed the title py/stream: Add user-streams adaptor. RFC: py/stream: Add user-streams adaptor. Jan 30, 2017
@pfalcon
Copy link
Contributor
pfalcon commented Jan 30, 2017

Well, that's what I meant by "No if's and stuff" in the discussion you link to. How PinBase works, you're guaranteed to have o->type->protocol filled in, if you inherited from it. This can be checked once whenever possible, for example Signal() constructor can check it once and throw exception, and no further checks are necessary when actually executing operations. Where check is unavoidable, it's simple null value check.

Your patch adds requirement for (another) check. Where there were no checks at all, that adds expensive conditional jump, which may potentially stall pipelines and flush caches. To avoid that, you'd need to cache the result of

 +        if (mp_obj_is_instance_type(type)) {
 +            return &user_stream_p;
 +        }

But that takes extra space, and may be even not always possible (like in this patch). And the main point - o->type->protocol is where it was carefully cached from the beginning with PinBase-style design.

@pfalcon
Copy link
Contributor
pfalcon commented Jan 30, 2017

The above code almost works the same on CPython

On CPython, for a realistic cases, you'd actually need to inherit from one of io.*Base classes to get useful behavior (for example, you provide read()/write(), and readline() is implemented by the base class).

@dpgeorge
8000 Copy link
Member Author

Your patch adds requirement for (another) check. Where there were no checks at all, t

No it doesn't. Notice that I removed a check (call to mp_get_stream_raise) so that it balances out.

As I said, you need a check somewhere to see if the object has the protocol, so that check can return the adaptor if needed.

This can be checked once whenever possible, for example Signal() constructor can check it once and throw exception, and no further checks are necessary when actually executing operations.

Yes, exactly, you need a check, and the check can return the protocol structure so you don't need to access it again. And it can be a user-adapted protocol if the object doesn't have a native protocol.

@pfalcon
Copy link
Contributor
pfalcon commented Jan 31, 2017

Well, let's trace back to reconsider the facts which should be obvious, but may be they aren't. What you propose is an obvious, ground-level solution to a problem. When the problem of allowing Pins to be implemented in Python was faced, there was idea to seek for smarter solution, specifically:

  1. Avoid the need to patch each and every (including each newly introduced) protocol for support of Python objects.
  2. Minimize number of extra operations, down to mere checks, especially recurring ones.

This solution was achieved in the form of PinBase, which undergo review, so all facts above should have been known, and it was (and is) argued that such solution lies within Python paradigm. And duck typing isn't the only paradigm for Python, normal class inheritance plays important role too. As mentioned above, a class implementing the stream interface would realistically subclass one of io.* base classes, and there's a whole concept of ABCs. MicroPython didn't have ABCs, and PinBase is effectively how we introduce them in (unbloated uPy'esque manner).

So, if you say we can do it differently - of course we can, we could have long ago. Even if "different" solution would be equivalent (and it's not), what would be the reason for change. You quoted need to poll a pin - no problem, poll is a universal operation, we can make it a part of every protocol. You quoted an (abstract) need to support multiple protocols on one object - I previously explained how that would work, there would be "GET_PROTOCOL(S)" predefined operation. If there's a need to support multiple protocol, apparently it would return not just ID, but a bitmask of supported protocols. You would say that gets complicated, and I would remind that the original goal is to have efficient C-based backbone, and then the next requirement was to try to implement Python-level interfacing with it without the need to patch this C-based infrastructure, and PinBase implements such solution, while you get back to need to patch it.

const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) {
mp_obj_type_t *type = mp_obj_get_type(self_in);
const mp_stream_p_t *stream_p = type->protocol;
if (stream_p == NULL
|| ((flags & MP_STREAM_OP_READ) && stream_p->read == NULL)
|| ((flags & MP_STREAM_OP_WRITE) && stream_p->write == NULL)
|| ((flags & MP_STREAM_OP_IOCTL) && stream_p->ioctl == NULL)) {
// if the object is a user instance then return a protocol adaptor
if (mp_obj_is_instance_type(type)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your patch adds requirement for (another) check. Where there were no checks at all, t

No it doesn't. Notice that I removed a check (call to mp_get_stream_raise) so that it balances out.

Here's the check that you added, bingo. Yes, you removed a check somewhere else, but counting it as a "balance" is a way of cheating ;-).

@pfalcon
Copy link
Contributor
pfalcon commented Nov 4, 2017

I think we should move on with this, as this definitely should be implemented (not necessarily enabled) to make the core "complete".

So, I have the final argument for doing it as it was intended with the protocols framework, not apply opportunistic practical hacks like this patch does. So, mp_get_stream_raise() was introduced as a convenience function, it doesn't have to be used. Of course, practically, it's used, because not using it and doing adhoc checks would violate "minimal code size" requirement. BUT, one of its (always-was, always-is) legitimate uses is to pre-validate an object to have the required stream operations. Once validated, the object (and only the object) can be stored, and on the following invocations, protocol structure members can be accessed directly (that was always the idea how protocol structures should be used - they are made the way they are to minimize the runtime overhead). As this patch doesn't follow the protocol semantics and fakes the protocol structure, it doesn't work with the intended usage above.

So, I'd like to extend my suggestion to redo this patch using the proper protocol inheritance, and volunteer to do such a refactor.

@dpgeorge
Copy link
Member Author
dpgeorge commented Dec 4, 2018

The features in this PR were implemented in a different way using uio.IOBase in af0932a

@dpgeorge dpgeorge closed this Dec 4, 2018
@dpgeorge dpgeorge deleted the user-streams branch December 4, 2018 05:02
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

Successfully merging this pull request may close these issues.

2 participants
0