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.
--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to