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.