Allow accessing multiple members using a.(x, y, z)

This would help with things like the abundant code during initialization.
current:

class A:
   def __init__(self, x, y, z):
      self.x = x
      self.y = y
      self.z = z

suggested:

class A:
   def __init__(self, x, y, z):
      self.(x, y, z) = x, y, z

Judging from discussions online I don’t seem to be the only one who would like a simpler way of doing this.
This implementation would be fairly consistent with the existing a, b, c = x, y, z syntax and still would be easy to read and understand.

If this were to be done you would also need to support syntax like a.(x.(b, c), y, z) = (b, c), y, z to stay consistent with (a, b), c, d = (w, x), y, z being valid. I would discourage this though, it gets unreadable pretty quickly and

a.x.(b, c) = b, c
a.(y, z) = y, z

would be way easier to read.

4 Likes

Is there an example of a language imolementing a similar feature?

I accidentally replied to my own post instead of yours so I had to insert this. Rust’s import syntax looks similar: use a::{b::{c, d}, e, f} but for variable assignment python’s own multiple assignment syntax is the closest thing I’m aware of.

Have you looked at dataclasses or attrs? They solve this particular problem very nicely, you can skip writing __init__ methods entirely.

1 Like

That’s true for this particular application but if you have more complex classes it’s not as simple with dataclasses (e.g. you want to do something else in init too you’d have to use new or something). It also does have other applications. For example you might have a position and an object that takes an x and y. Unless the class implements a property with a setter you’d have to set each coordinate in a seperate line. Or maybe you want to pass a bunch of attributes of a single object to a function. You could then use fn(*object.(a, b, c)) instead of fn(object.a, object.b, object.c)

2 Likes

You use __post_init__: dataclasses — Data Classes — Python 3.12.1 documentation

1 Like

I think it would be more pythonic and without needing a specific syntax, just creating an __attr__ attribute that has all the attributes of the class:

class Spam:
     def __init__(self, x, y, z) -> None:
         self.__attr__ += (x, y, z)

The current syntax to achieve this would be

(self.x, self.y, self.z) = point

which looks a little cleaner to me in this specific instance. I do agree the proposal has merits when unpacking into nested attributes, but the original post’s example doesn’t show it

person.body.pose.position.(x, y) = cs.geod2enu(lat, lon)
2 Likes

@EpicWink
My “casual and simple”(5/10) solution would probably be:

class C:
	def __init__(self, x, y, z):
		self.x__y__z = x, y, z

	def __getattr__(self, name):
		attr_names = name.split("__")
		return tuple(C.__dict__[attr_name] for attr_name in attr_names)

obj = C(1, 2, 3)
print(obj.x__y__z)

It’s con is that you can not include “__” in your attribute name(you can make it tripple underscores -or anything else you do not use- to be able to work with dunder methods) which doesn’t seem like a big deal when f-strings are considered.
Seems ok for simple programs.
EDIT: This foolish answer was just trying to shorten the code.obj.get_some_attr(x, y) would be much prettier.

This is similar to various past proposals.

https://mail.python.org/archives/list/python-ideas@python.org/thread/VLI3DOFA5VWMGJMJGRDC7JZTRKEPPZNU/

https://mail.python.org/archives/list/python-ideas@python.org/thread/SCXHEWCHBJN3A7DPGGPPFLSTMBLLAOTX/

The suggested syntax is different, but the aim is to solve the same issue.

Your proposal is syntactic sugar for operator.attrgetter:

class K:
    spam = 1
    eggs = 2
    cheese = 3

from operator import attrgetter
getter = attrgetter('spam', 'eggs', 'cheese')
getter(K)  # returns (1, 2, 3)

There’s no attrsetter or attrdeleter equivalent.

Personally, I think that the syntax looks too much like a typo:

obj(x, y, z)   # Function call.
obj.(x, y, z)  # New syntax for multiple attribute access.
2 Likes

Marcio suggested:

“creating an __attr__ attribute that has all the attributes of the class”

Python has had something similar forever. Classes, and most attributes, have a __dict__ dictionary that has all the attributes of the object.

We can access that dunder __dict__ using the vars() function, so we can do this:

class K:
    def __init__(self, x, y, z):
        vars(self).update(locals())
        del self.self

to assign x, y, z as attributes.

This does not work with __slots__, but it will work for most Python classes.

2 Likes

Great example. This sort of thing is super annoying when you have to retype long strings like this. I agree that this is a better use of the feature than in my example.

Yeah but this isn’t readable at all you need to know how locals() and vars() work which will make this very confusing for beginners. As others suggested dataclasses are best suited to solve this particular problem.

dot complained:

“Yeah but this isn’t readable at all you need to know how locals() and vars() work which will make this very confusing for beginners.”

Do you think that beginners will know what obj.(spam, eggs, cheese) means?

It will be much easier for people to look up locals and vars to find out what they mean than to work out the difference between obj(...) and obj.(...).

We shouldn’t add new syntax that makes the language less understandable unless it allows us to do things which is impossible or difficult today, and assigning multiple attributes is neither impossible nor difficult.

1 Like

I actually disagree. The solution with locals() uses complicated concepts. Dot’s proposal has the advantage that you can easily guess what it is supposed to mean by generalizing what you already know (IF you notice the dot).

3 Likes

Convergent evolution is a thing also for programming languages: given enough time the syntax of all languages will converge to resemble Perl.

Is it really worth coming up with new syntactic constructs just to save keystrokes? Is a syntax that allows stuff like a.(x.(b, c), y, z) = (b, c), y, z something to look forward? Even after trying to think hard about it I am not able to understand what that thing does without having to rewrite it spelling out the expansion.

I’m definitely not a very good typist and I don’t feel that the number of characters I need to type is the bottleneck in expressing any non-trivial piece of code (or of prose, for what matters). For non-trivial code, I think that being required to be explicit is actually a good thing. If you find yourself writing a log of trivial code, maybe something else can be done, rather than changing the language to make typing trivial code faster.

2 Likes

You can hide complex parts to make them easier to read, using with-statement as follows:

>>> class A:
...     def __init__(self, x, y, z):
...         with self:
...             # Note: with statement does not create a new scope.
...             # thus, vars x, y, z are created in self namespace.
...             t = True
...     
...     def __enter__(self):
...         return self
...     
...     def __exit__(self, *args):
...         self.__dict__.update(sys._getframe().f_back.f_locals)
...         del self.self
... 
>>> vars(A(1,2,3))
{'x': 1, 'y': 2, 'z': 3, 't': True}

Alternative syntax proposal:

(a,b,c)@obj = x,y,z