[Python-ideas] collections.Counter should implement __mul__, __rmul__

Tim Peters tim.peters at gmail.com
Thu Apr 19 00:10:01 EDT 2018


[Serhiy]
>> Isn't everyone expect that x*2 == x + x? Isn't this the definition of
>> multiplication?

[Steven D'Aprano <steve at pearwood.info>]
> I can't think of any counter-examples, but it wouldn't surprise me even
> a tiny bit to learn of some.

Sure you can:  explain -3.1 * -2.7 = 8.37 in terms of repeated addition ;-)

It's not even necessarily true in finite fields.  For example, look at
the "+" and "*" tables for the 4-element finite field GF(4) here:

    https://en.wikipedia.org/wiki/Finite_field#Field_with_four_elements

For every element x of that field, x+x = 0, so a*(1+a) = 1 in that
field can't be obtained by adding any number of a's (or any number of
1+a's)..  Adding x to itself just gives x again if an odd number of
x's are added together, or 0 if an even number.

The sense in which it's always true in fields is technical:  first,
x*y is guaranteed to be equivalent to repeated addition of x only if y
is "an integer"; and, more subtly, "integer" is defined as meaning the
result of adding the multiplicative identity any number of times to
the additive identity.  In GF(4), under that definition, only 0 (the
additive identity) and 1 (the multiplicative identity (1) added to the
additive identity (0)) are "integers".  Attempting to add 1 more times
just continues alternating between 0 and 1.  `a` and `1+a` can't be
reached that way, so are not integers, and none of a*(1+a), a*a,
(1+a)*a, or (1+a)*(1+a) is the same as adding either operand any
number of times.

You could nevertheless define x*n as _meaning_ x+x+...+x (n times) for
cardinal numbers n, but then n is outside the field.

What's the application to Counter.__mul__?  Not much ;-)  The current
'+' and '-' map _pairs_ of Counters to a Counter.  The proposed '*'
instead maps a Counter and "a scalar" to a Counter.  It's always nice
if x*n is the same as repeated addition of x when n is a cardinal
integer, but because Counter.__add__ does special stuff specific to
the multiset interpretation, the proposed '*' doesn't always do the
same _unless_ the Counter is a legitimate multiset.

So it still works fine _in_ the multiset view, provided that you're
actually working with multiset Counters.  But applications seeking to
mix Counters and scalars with "*" and "/" don't have multisets in mind
at all, so I'd be fine with `Counter * n` not being equivalent to
repeated multiset addition even when Counter is a legitimate multiset
and `n` is a cardinal.  It's just gravy that it _is_ equivalent in
that case.

Where it breaks down is that, e.g,

    >>> a = Counter(b=-100)
    >>> a
    Counter({'b': -100})
    >>> a + a
    Counter()

but the proposed a*2 would return Counter(a=-200).  But, in that case,
`a` wasn't a legit multiset to begin with.


More information about the Python-ideas mailing list