List replication operator

Rob Gaddi rgaddi at highlandtechnology.invalid
Fri May 25 12:58:19 EDT 2018


On 05/25/2018 04:53 AM, Steven D'Aprano wrote:
> On Fri, 25 May 2018 09:28:01 +0200, Peter Otten wrote:
> 
>> Yet another arcanum to learn for beginners with little return. If you
>> cannot refrain from tinkering with the language at least concentrate on
>> the features with broad application. Thank you.
> 
> Broader than multi-dimensional arrays? There are a bazillion uses for
> them. How many do you need before it is "broad application"?
> 
 > [snip]
> 
> This is a frequent, recurring pain point. Experienced programmers forget
> how confusing the behaviour of * is because they're so used to the
> execution model. They forget that writing a list comp is not even close
> to obvious, not only for beginners but even some experienced Python
> programmers.
> 

I agree that it's non-obvious, but I disagree with your diagnosis.  The 
non-obvious bit is that the empty list is a reference to a newly created 
mutable, not an immutable constant.  Or, more the point, that the word 
"mutable" is one that you need to know and think about in the first place.

The thing tripping the young pups is not understanding the underlying 
Python concepts of objects and their references.  And I sympathize; 
Python's handling there is seriously non-trival.  I guarantee everyone 
on this list got bit by it when they were starting out, either in this 
case or as a default argument.  I probably still manage to screw it up a 
couple times a year due to not thinking about it clearly enough.

So, in the spirit of explicit being better than implicit, please assume 
that for actual implementation replicate would be a static method of 
actual list, rather than the conveniently executable hackjob below.

_list = list
_nodefault = object()

class list(_list):
   @staticmethod
   def replicate(*n, fill=_nodefault, call=list):
     """Return a list of specified dimensions.

     Fill and call can be used to prime the list with initial values, the
     default is to create a list of empty lists.

     Parameters:
       n : List of dimensions
       fill: If provided, the fill object is used in all locations.
       call: If fill is not provided, the result of call (a function of
       no arguments) is used in all locations.

     Example:
       >>> a = list.replicate(2, 3)
       >>> a
       [[[], []], [[], []], [[], []]]
       >>> a[0][0].append('a')
       >>> a
       [[['a'], []], [[], []], [[], []]]

       >>> b = list.replicate(2, 3, fill=[])
       >>> b
       [[[], []], [[], []], [[], []]]
       >>> b[0][0].append('a')
       >>> b
       [[['a'], ['a']], [['a'], ['a']], [['a'], ['a']]]

       >>> c = list.replicate(2, 3, call=dict)
       >>> c
       [[{}, {}], [{}, {}], [{}, {}]]
       >>> c[0][0]['swallow'] = 'unladen'
       >>> c
       [[{'swallow': 'unladen'}, {}], [{}, {}], [{}, {}]]

       >>> d = list.replicate(2, 3, fill=0)
       >>> d
       [[0, 0], [0, 0], [0, 0]]
       >>> d[0][0] = 5
       >>> d
       [[5, 0], [0, 0], [0, 0]]
     """

     if n:
       this = n[-1]
       future = n[:-1]
       return [
         list.replicate(*future, fill=fill, call=call)
           for _ in range(this)
       ]
     elif fill is _nodefault:
       return call()
     else:
       return fill


-- 
Rob Gaddi, Highland Technology -- www.highlandtechnology.com
Email address domain is currently out of order.  See above to fix.



More information about the Python-list mailing list