[Python-ideas] Adding __getter__ to compliment __iter__.

Ron Adam ron3200 at gmail.com
Thu Jul 18 19:51:22 CEST 2013



On 07/18/2013 10:06 AM, Oscar Benjamin wrote:
> On 18 July 2013 10:08, Nick Coghlan <ncoghlan at gmail.com> wrote:
>>
>> Then builtin sum() would have the following case added to handle the
>> builder protocol:
>>
>>      try:
>>          bldr = builder(start)
>>      except TypeError:
>>          pass
>>      else:
>>          for item in iterable:
>>              bldr += item
>>          return bldr.finish()
>
> What use cases would the builder protocol have apart from using sum
> with collections (since that particular case is already well-covered
> by chain/join)?
>
> Wouldn't it be easier to put that logic into the constructor for
> type(collection) or into a factory function. Then you wouldn't need an
> additional protocol or an additional class (for each buildable
> collection).
>
> Why would you want to do this
>
>      bldr = builder(()) # Build a tuple
>      for val in stuff:
>          bldr += item  # Or append/extend
>      result = bldr.finish()
>
> when you can just do this
>
>      result = tuple(chain(stuff)) # or tuple(stuff)
>
> Most non-string collections already support this interface in their
> constructors or in a factory function.

If you know the result will always be a tuple, then you can certainly do 
that.  And probably should.  But if you want the same result to be the same 
type as the parts you start with, you need to write that for every type 
your routine may handle.

It would look closer to this...

        result = stuff[0]
        result += chain(stuf[1:])

Which wouldn't always work due to not everything has an __iadd__.

So you would need to do...

        result = stuff[0]
        rest = chain(stuff[1:])
        result = result + rest

But that creates a new object rather than extending the result.  One of the 
points is to be able to extend an existing obj easily.


We could do...

        result = stuff[0]
        rest = chain(stuff[1:])
        result[:0] = rest[:]

But here again, not every type support slicing.


The current version I'm testing, based on Nicks example...


class DefaultBuilder(list):
     def __init__(self, obj):
         if hasattr(obj, "__iter__"):
             self._obj = obj
             self.__iadd__ = self.extend
         else:
             raise TypeError("%r is not iterabe" % obj)

     def finish(self):
         if self._obj is None:
             raise RuntimeError("Builder already finished")
         if isinstance(self._obj, str):
            result = self._obj + ''.join(self)
         else:
            result = self._obj
            result += type(self._obj)(self)
         self._obj = None
         return result


def builder(obj):
     try:
         meth = obj.__builder__
     except AttributeError:
         pass
     else:
         return meth(obj)
     return DefaultBuilder(obj)


The advantages over chain, is builtin builders (Written in C) could have 
access to both objects, (destination, and stuff to add), internally and may 
be able to do fast memory copies and/or moves of the objects.

If we can reach that point with this suggestion, then:

     1.  More consistent way to copy and move data around.
         (Although it may not be obvious right now.)

     2.  Do it much faster due to not iterating
         over each object in many cases.

     3.  Makes it easier to write generalised routines like sum(). (*)

(* Although we are using sum() as an example, there isn't any intention of 
replacing the current builtin sum() function at this time.  It makes a nice 
test case though.)


Cheers,
    Ron















More information about the Python-ideas mailing list