Python accesses the object property a la with-slots lisp

Python accesses the object property a la with-slots lisp … here is a solution to the problem.

Python accesses the object property a la with-slots lisp

In common-lisp can be written

(defclass thing ()
       ((x :initarg :x :accessor thing-x)
        (y :initarg :y :accessor thing-y)))

;; create foo of class thing with values (x=0,y=1)
(setq foo (make-instance 'thing :x 0 :y 1))

;; access attributes x and y in the scope defined by with-slots as
;; local variables and increment them
(with-slots (x y) foo 
    (incf x) (incf y))
;; now foo has values (x=1,y=2)

Now, in

Python3, I have implemented a mathematical model in which I have created a dictionary of variables and other components. If then I need to write some mathematical expressions with these variables, after creating the model, I have to write something similar

model.expr1 = model.var1 + data.coef2 * model.var2 ....

Of course, var1... varn has longer, more descriptive names.

To improve readability, I wanted something

with ModelSlots(model) as (var1, var2, ... varn):
    model.expr1 = var1 + data.coef2 * var2 ...
    ...

As I understand it, each context manager returns only one object, so the solution above should be impossible.

Do you know how to implement it in python?

The most obvious solution, of course, is

var1 = model.var1
var2 = model.var2
...

But this is verbose, difficult to read, confuses the context even more, and can also lead to vague errors as I might inadvertently initialize some local var variables to the wrong value.

Each variable has multiple context managers

with Var1(model) as var1:
    with Var2(model) as var2:
      ...

Nor is it a solution, since I can use multiple variables in the same scope and I might want to change or add new variables quickly. Having to define a context manager for each of them would be too cumbersome.

TIA

Edit 1

Comment on the Felix solution. The sorting/matching of slots can be solved in the following ways:

from bunch import Bunch

class ModelSlots:

def __init__(self, model, *slots):
    self._model = model
    self._slots = list(map(lambda x: getattr(model,x), slots))

def __enter__(self):
    return self._slots

def __exit__(self, *args):
    pass

if __name__ == '__main__':
    model = Bunch()
    model.foo = 1
    model.bar = 2
    with ModelSlots(model, "bar", "foo") as (bar,foo):
        print((foo, bar))
# prints (1,2)

But you need to repeat the name of the slot twice, with and without quotes….

Solution

Python supports tuple unpacking, even in with statements. See the action below

class ModelSlots:

def __init__(self, model):
        self._model = model

def __enter__(self):
        return self._model.values()

def __exit__(self, *args):
        pass

if __name__ == '__main__':
    model = {"foo": 1, "bar": 2}
    with ModelSlots(model) as (foo, bar):
        print(foo + bar)
        # prints 3

Is this what you want?

I’m not sure if that’s a good idea overall. The names foo and bar in the with statement have nothing to do with the names of variables in the model, so it’s easy to accidentally confuse them (for example by changing their order).

All in all, I think it’s “somewhat” possible, but it can be dangerous depending on your application.

Related Problems and Solutions