1

It is possible to increase a number object like int within a lambda function?

Imagine having a peek function like:

def _peek(cb, iter):
    for i in iter:
        cb(i)

How can I peek and add these values to a sum like in this following simple example:

numbers = (1, 2, 3)
s = 0

# Doesn't work, because __add__ doesn't add inline
_peek(s.__add__, numbers)

# Doesn't work, because s is outside of scope (syntax error)
_peek(lambda x: s += x, numbers)

# Does work, but requires an additional function
def _sum(var):
  nonlocal s
  s += var

_peek(_sum, numbers)

# Does work, but reduces numbers
sum = reduce(lambda x, y: x+y, numbers)

Now this is a real world example:

@dataclass
class Vote:
    count = 0
    def add_count(self, count: int):
      self.count += count

vote = Vote()

# Doesn't work work
_peek(lambda x: vote.count += x, map(lambda x: x['count'], data))

# Does work, but requires additional function
_peek(vote.add_count, map(lambda x: x['count'], data))

In Java, I can write easily:

@Test
public void test_numbers() {
    class Vote {
        int count = 0;
    }

    var vote = new Vote();

    var count = Stream.of(1,2,3).peek(i -> vote.count+=i).filter(i -> i > 1).count();
    assert vote.count == 6;
    assert count == 2;
}
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Mike Reiche
  • 382
  • 3
  • 12
  • 2
    You can't "increase a number object", unless you create your own - the standard library numbers are immutable. – jonrsharpe May 11 '23 at 16:57
  • 4
    The first lambda failure has nothing to do with scope. It fails because `lambda` needs an expression in the body, and assignments are not expressions (and the walrus operator also isn't allowed). – Barmar May 11 '23 at 17:00
  • @jonrsharpe I don't think he really wants to mutate the integer, just increment the variable. – Barmar May 11 '23 at 17:03
  • 1
    I don't think you can do it without the extra function. They went to great pains to prohibit direct assignments in lambdas, it has to be hidden in a function. – Barmar May 11 '23 at 17:04
  • Are you potentially looking to do something more like `def _peek2(cb, iter): return cb(iter)` that would be used like `s += _peek2(sum, numbers)`? – JonSG May 11 '23 at 17:04
  • "# Does work, but requires an additional function" no it doesn't. It requires precisely one function, which is **exactly** what a lambda expression would create, a function. So it really isn't clear what doesn't work for you. IOW, you *have* a solution. – juanpa.arrivillaga May 11 '23 at 17:08
  • @juanpa.arrivillaga I think by "additional" they mean named and defined outside of the call to peek. So additional from what might be considered tidy :-) – JonSG May 11 '23 at 17:09
  • 1
    Perhaps you want to research [assignment inside lambda expressions](https://stackoverflow.com/questions/6282042/assignment-inside-lambda-expression-in-python)? – Random Davis May 11 '23 at 17:10
  • "# Does work, but reduces numbers" what do you mean by "but reduces numbers"? What's the problem? – juanpa.arrivillaga May 11 '23 at 17:11
  • 1
    In any case, the answer to your quesiton is basically, "no, python lambda expressions are purposefully limited to executing simple expressions. With just simple expressions, there is no sane way to do what you want to do (modify a variable), so in Python, you should *just use a function definition statement to create your function*. Or perhaps, don't rely on mutable state, and go with `reduce` or something (it still isn't clear to me what the problem is with `reduce`) – juanpa.arrivillaga May 11 '23 at 17:12
  • @JonSG Python is generally not a language that aims for tersness, although, it is still very expressive. The only reason you need lambda expressions in Java is because Java lacks first-class functions, in Python, in a local scope, you can just `def _callback(x): vote.count += x` then pass it. That's fine. There is nothing wrong with that, `_peek(_callback, (1,2,3)); print(vote.count)`. The *only* thing a lambda expression allows you to do is *not* assign the function object to a local name. – juanpa.arrivillaga May 11 '23 at 17:30
  • @juanpa.arrivillaga I was not aware I was suggesting otherwise. If I appeared to suggest that your example was invalid, I apologize. – JonSG May 11 '23 at 17:36
  • 1
    @JonSG sorry, I'm mostly just elaborating prompted by your response, not disagreeing with you particularly – juanpa.arrivillaga May 11 '23 at 17:38

1 Answers1

1

You could use a callable class.

def _peek(cb, iter):
    for i in iter:
        cb(i)

class Adder:
    def __init__(self, *args, **kwargs):
        self.value = 0
    def __call__(self, *args, **kwargs):
        self.value += args[0]

adder = Adder()
_peek(adder, (1, 2, 3))
print(adder.value)

The result is 6.

Matthias
  • 12,873
  • 6
  • 42
  • 48
  • Thanks for the answer. Do you know if a class library for basic datatypes exists already? Like `Integer` in Java? – Mike Reiche May 15 '23 at 06:22