Skip to content

Inheritance for native types is not supported #1159

Open

Description

@pfalcon

Default load_attr implementation (as used for all native types) doesn't take type inheritance (->bases_tuple) into account. I'm working on OrderedDict implementation, and it inherits most of its methods from dict, but adds few (well, 1) new method. Of course, this new method can be ignored, and old trick of putting the same .locals_dict to both types can be used. But I wanted to look into making truly inheritable methods, which requires setting custom .load_attr type method. And we already have implementation of this method which takes inheritance into account - for user classes/instances. But trying to use it, it assumes that object whose attr is being looked up is instance, so making it work with native objects means adding even more conditions to existing bunch of those (and which are already pretty mind-boggling). Another alternative is to make clean, "optimized" inheritance-friendly .load_attr for native types, but that means code duplication (and refactoring of existing code paths, factoring out functions, which means more stack usage, etc.)

Any thoughts?

Activity

  1. added a commit that references this issue on Sep 6, 2018
  2. amirgon commented on Nov 27, 2018

    @amirgon
    Contributor

    I don't understand how this never got priority since 2015.
    When providing Micropython API to an existing library, I have to duplicate all methods of base classes for each sub class instead of simply specify the base class. I get huge locals_dicts with duplicated data. Feels like the mechanism for extending Micropython is crippled.

  3. stinos commented on Nov 28, 2018

    @stinos
    Contributor

    I have to duplicate all methods of base classes

    Not really, there are basic tricks which minimize boilerplate to the point you can't really speak of duplication anymore. For example:

    def CopyAttributes(src, dst, names=None):
      if not names:
        names = (e for e in dir(src) if not e.startswith('__'))
      for entry in names:
        setattr(dst, entry, getattr(src, entry))
    
    class Derived:
      def __init__(self):
        self.base = Base()
        CopyAttributes(self.base, self)
    
  4. amirgon commented on Nov 28, 2018

    @amirgon
    Contributor

    @stinos I'm not providing python code. I'm providing an extension to micropython by adding a module in C.
    Do you suggest I post-process every object I add from C with python code? This isn't very scalable or convenient. May also have performance/memory penalty.

  5. stinos commented on Nov 28, 2018

    @stinos
    Contributor

    The same principle can also be applied in C if you're not using a const dict.

  6. amirgon commented on Nov 28, 2018

    @amirgon
    Contributor

    @stinos Please explain how to achieve this when adding a module. I tried setting .parent field of mp_obj_type_t but this does not work for native (C) objects.

  7. stinos commented on Nov 28, 2018

    @stinos
    Contributor

    I don't have an example ready but thinking of it, doing this in C is actually a bit different but less bloated as you don't need storage for each method. The idea is: you implement the type's attr function and when MicorPython asks you for an attribute you look it up in your class first, and if not found you look in your 'base' class instead and return it if found. Here's some untested pseudocode thrown together:

    static void mytype_make_new(...) {
      //all the usual stuff, plus keep a 'base' class object
      self->myBasePtr = mybase_make_new(...);
    }
    
    static void myattr(mp_obj_t self_in, qstr attr, mp_obj_t* dest) {
      mytype *self = (mytype*) self_in;
      if(*dest == MP_OBJ_NULL) { //called to load attribute
          mp_obj_t myOwnAttribute = mp_map_lookup(self->base.type->locals_dict->map, new_qstr(attr), MP_MAP_LOOKUP);
          if(myOwnAttribute != MP_OBJ_NULL) {
            dest[0] = elem->value;
            dest[1] = self_in;
          }
          //we don't have that attribute, maybe base does? just forward call
        self->myBasePtr->base.type.attr(self->myBasePtr, attr, dest);
      } else {
        //do whatever needed to store attribute
      }
    }
    
    const mp_obj_type_t my_type = {
        //the usual stuff
        .make_new = mytype_make_new,
        .attr = myattr
    };
    
  8. amirgon commented on Nov 28, 2018

    @amirgon
    Contributor

    @stinos Thanks for your example, I'll give it a try.
    Don't you think this is something worth supporting as part of Micropython, instead of everyone implement the same functionality again and again?
    Also I guess that in REPL, these functions will not be completed with TAB, will not appear in help etc...

  9. peterhinch commented on Nov 29, 2018

    @peterhinch
    Contributor

    Apologies if I'm missing the point here but it seems to me that providing generalised inheritance of built in types is considerably more complex than copying attributes. Consider properties like mutability, the ability to support item assignment, the ability to iterate over the instance, support for the buffer protocol and doubtless more. These may or may not be present in the base class.

  10. amirgon commented on Nov 29, 2018

    @amirgon
    Contributor

    @stinos @peterhinch It's worth noting that at least in Python this is already covered. (PEP253, an example)

  11. stinos commented on Nov 29, 2018

    @stinos
    Contributor

    Don't you think this is something worth supporting as part of Micropython, instead of everyone implement the same functionality again and again?

    Yes of course, that's also why this issue exists.

    Apologies if I'm missing the point here but it seems to me that providing generalised inheritance of built in types is considerably more complex

    You're not missing any point, the example shown isn't generalised at all.

    in Python this is already covered

    Well I surely hope there are shortcuts not shown in those example then, instead of having to copy that 40-line type definition :P

  12. dpgeorge commented on Dec 15, 2018

    @dpgeorge
    Member

    See #4360 for a solution to this issue. It just does single inheritance but I think that is enough.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementFeature requests, new feature implementations

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions