2

In the pursuit of some nice-looking, functional-feeling Python code, I'm wondering how to bind a set of names to the result of applying a function to their currently bound values. Consider this code:

# preamble
a = [4]
b = [5]
c = [6]

# code
a = len(a)
b = len(b)
c = len(c)

print(a, b, c)

However, typing x = len(x) for each of the three variables must surely be redundant, right?

Attempt at in-line solution...

I can reassign the names like this instead:

a, b, c = (len(x) for x in (a, b, c))

... but this also carries some redundancy, because I had to list a, b, c twice!

Attempt using inspect...

We can use inspect to make things a bit better:

import inspect

def apply_in_scope(f, v):
  parent = inspect.stack()[1].frame.f_globals
  for i in v:
    parent[i] = f(parent[i])

a = [5]
b = [7, 9]
c = [9, 10, 11, 12]

apply_in_scope(len, ("a", "b", "c"))

print(a, b, c)

... but now I have to put the names of my variables in quotes, instead of using them directly. The problem is that when you call a function in Python, the function gets passed a new reference.

A quadratic solution that "works"

Changing the code slightly, we can do a quadratic search for arguments that match the given parameters, which is very slow, but I believe works:

import inspect

def apply_in_scope(f, values):
  parent = inspect.stack()[1].frame.f_globals
  names = [k for k, v in parent.items() if any(v is x for x in values)]

  for name in names:
    parent[name] = f(parent[name])

a = [5]
b = [7, 9]
c = [9, 10, 11, 12]

apply_in_scope(len, (a, b, c))

print(a, b, c)

... but I can't help thinking there must be a better way, or that I'm missing something simple. This would be reasonably easy in LISP, which makes me think it shouldn't require so much gymnastics in Python.

(warning: the above solution will apply the function multiple times if more than one variable is the same value!)

Edit: this question is distinct from how to make a variable number of variables because that question (1) applies to the global scope and not the local function scope and (2) does not solve the basic meta-programming question asked here of reassigning and applying a function.

Ryan Marcus
  • 966
  • 8
  • 21
  • 5
    Why do this *at all*? Just put the lists in a dictionary, with `'a'`, `'b'` and `'c'` as the keys. – Martijn Pieters Jun 26 '17 at 21:21
  • `a, b, c = map(len, (a,b,c))`? But really, you should be working with a container. – juanpa.arrivillaga Jun 26 '17 at 21:28
  • Because then I'd have to refer to each variable as `d["a"]`, `d["b"]`, etc., or manually unpack them as I showed in my example. Is there an easy way to "merge" a dictionary with the local scope that I'm not aware of? – Ryan Marcus Jun 26 '17 at 21:28
  • 1
    Also, I'm not sure how altering global state is "functional feeling". – juanpa.arrivillaga Jun 26 '17 at 21:29
  • @juanpa.arrivillaga I believe that is equivalent to the "in-line" solution I wrote in my post -- although I'll admit that's probably the nicest way to do it if there's no other way. – Ryan Marcus Jun 26 '17 at 21:30
  • @juanpa.arrivillaga I very specifically don't want to modify the global state! Just the state of the function I am in. This would be a simple and clean LISP (or scheme) macro, which is why I thought it shouldn't be too hard in Python. – Ryan Marcus Jun 26 '17 at 21:31
  • Modifying state at all is against functional programming design. E.g. `a = len(a)` i.e. reassignment is very much *not* functional. – juanpa.arrivillaga Jun 26 '17 at 21:33
  • Also, "when you call a function in Python, the function gets passed a new reference." I don't think that's true. Passing an argument to a function assigns to the argument names *in the local scope*, but it's still a reference to the same object. Just to be nitpicky. – juanpa.arrivillaga Jun 26 '17 at 21:35
  • @juanpa.arrivillaga don't think of the local variables as state, instead think of each assignment operator itself as a function that returns a new version of the local scope, which is automatically used as input to the next assignment operator. This is the standard way we go about creating state in LISP / scheme, if I remember my SICP correctly. :) – Ryan Marcus Jun 26 '17 at 21:35
  • @juanpa.arrivillaga your nitpicking is appreciated! Here's what I was trying to paraphrase in the linked answer: "When you call a function with a parameter, a new reference is created that refers to the object passed in. This is separate from the reference that was used in the function call, so there's no way to update that reference and make it refer to a new object." – Ryan Marcus Jun 26 '17 at 21:36

0 Answers0