问题
Today, I automatically wrote some thing like this:
class Foo():
def __init__(self, x):
self.x = x
s = [Foo(1), Foo(2), Foo(3)]
sum_x = sum(s, key = lambda foo: foo.x)
And got this:
TypeError: sum() takes no keyword arguments
Is there any special reason for sum()
does not have a key
arg?
回答1:
Because you can just write sum(foo.x for foo in s)
instead. If you tried to do this with one of the functions that does take a key
argument (sorted
, min
, max
, etc.), the function would end up returning the key(s) rather than the original item(s), and getting the original items while sorting by keys is tricky enough that Python gives you a built-in way to do that via a keyword argument.
Thus: There's no special reason for sum
to not take a key
; rather, those other functions have special reasons why they do take a key
. key
is the exception, not the rule.
回答2:
There is no key
argument because sum()
doesn't return the original elements (like sorted()
, min()
and max()
do). Instead, it just sums the inputs.
If, say, min()
did not take a key
argument, it could not return the minimum Foo()
object based on an attribute; it could only return the value of that attribute. But sum()
doesn't work that way, it doesn't need to preserve the original objects.
You can easily transform the inputs in a generator expression:
sum(item.x for item in s)
回答3:
Although there is no key
param, the good news is that You can use sum
with your Foo
object! Others have already pointed out that the easiest way to
do this
sum(item.x for item in s)
However, it's also possible to use it without the comprehension.
Object addition
In order for sum to work, basic addition needs to work first.
In [2]: class Foo:
...: def __init__(self, x):
...: self.x = x
...:
In [3]: Foo(3) + Foo(5)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-f0e9c3a4abb9> in <module>()
----> 1 Foo(3) + Foo(5)
TypeError: unsupported operand type(s) for +: 'Foo' and 'Foo'
We can enable addition by defining the __add__
method.
In [4]: class Foo:
...: def __init__(self, x):
...: self.x = x
...: def __add__(self, other):
...: return Foo(self.x + other.x)
...:
In [5]: Foo(3) + Foo(5)
Out[5]: <__main__.Foo at 0x102bdc2e8>
to make clear that it worked
In [6]: result = Foo(3) + Foo(5)
In [7]: result.x
Out[7]: 8
But this doesn't solve everything.
In [8]: sum([Foo(3), Foo(5)])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-8-70968119f3ba> in <module>()
----> 1 sum([Foo(3), Foo(5)])
TypeError: unsupported operand type(s) for +: 'int' and 'Foo'
We didn't try to add an int
, but the sum
function thinks we did. What gives?
THE sum
FUNCTION
using ipython to inspect the sum
function, you can see that it includes an
optional start
parameter
In [1]: sum??
Docstring:
sum(iterable[, start]) -> value
Return the sum of an iterable of numbers (NOT strings) plus the value
of parameter 'start' (which defaults to 0). When the iterable is
empty, return start.
Type: builtin_function_or_method
So according to that, sum(s)
is the same as sum(s, 0)
and it's this start
value that is causing the error. All we have to do is replace the start value
with the equivalent Foo
object
In [9]: sum([Foo(3), Foo(5)], Foo(0))
Out[9]: <__main__.Foo at 0x102bdc9e8>
In [10]: result = sum([Foo(3), Foo(5)], Foo(0))
In [11]: result.x
Out[11]: 8
This also applies to some other types
In [12]: sum([[1,2,3], [4,5,6]], [])
Out[12]: [1, 2, 3, 4, 5, 6]
but not all of them
In [13]: sum(["abc", "def"], "")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-13-452a33de0457> in <module>()
----> 1 sum(["abc", "def"], "")
TypeError: sum() can't sum strings [use ''.join(seq) instead]
来源:https://stackoverflow.com/questions/31574541/why-sum-does-not-have-the-key-arg