leewz
leewz

Reputation: 3346

A member tuple (as opposed to member variable) of a class?

This is what I wish to do:

obj = object()
obj.(a,b,c) = 1,2,3
f(obj.(d,e))

This is what I know how to do:

obj = object()
obj.a, obj.b, obj.c = 1,2,3

Is there a syntax to do this in Python?

Follow-up questions:

("Why do you want to do this?" Because it feels intuitive to me to think about groups of member variables as a package sometimes. There are classes for which it makes sense, like a fixed-dimensional point, or a complex number.)

Upvotes: 3

Views: 195

Answers (4)

leewz
leewz

Reputation: 3346

Based on sweeneyrod answer, I've written a way to have somewhat similar syntax, taking a comma-delimited string and square brackets. Usage:

thing = myclass()
thing['x,y'] = 3,6
x,y = thing['x,y']
thing['x,y,z'] = 1,2,3

This gives

>>> x
3
>>> y
6
>>> thing.x
1
>>> thing.z
3

Class definition:

#user class
class myclass:
    def __getitem__(self,keys):
        keys = keys.split(',')
        return tuple(getattr(self, attr) for attr in keys)

    def __setitem__(self,keys,values):
        keys = keys.split(',')
        if len(keys) != len(values):
            raise ValueError('too many values to unpack (expected %d)'%len(keys))
        for attr,val in zip(keys, values):
            setattr(self, attr, val)
    def __delitem__(self,keys):
        keys = keys.split(',')
        for attr in keys:
            delattr(self, attr)

Issues:

  • Overlaps with __getitem__, if you need it.
  • Not exception-safe (can __setattr__ throw an exception?)

I originally thought to use thing.grouping('x,y')=3,6, but I forgot that I can't assign to a function call.


Decorator version:

try:
    from itertools import izip as zip #python 2
except:
    pass #python 3 already has zip

#decorator
def attribute_tuple(cls):

    if not hasattr(attribute_tuple,'initialized'):
        attribute_tuple.initialized=True
        def get(self,keys):
            keys = keys.split(',')
            return tuple(getattr(self, attr) for attr in keys)
        attribute_tuple.get = get
        def set(self,keys,values):
            keys = keys.split(',')
            if len(keys) != len(values):
                raise ValueError('too many values to unpack (expected %d)'%len(keys))
            for attr,val in zip(keys, values):
                setattr(self, attr, val)
        attribute_tuple.set = set
        def delete(self,keys):
            keys = keys.split(',')
            for attr in keys:
                delattr(self, attr)
        attribute_tuple.delete = delete

    cls.__getitem__ = attribute_tuple.get
    cls.__setitem__ = attribute_tuple.set
    cls.__delitem__ = attribute_tuple.delete

    return cls

@attribute_tuple
class myclass:
    pass

Upvotes: 0

wim
wim

Reputation: 362837

It is not possible because of the grammar of attribute references.

An attribute reference is a primary followed by a period and a name:

attributeref ::=  primary "." identifier

And an identifier must begin with a letter, not parentheses.

Identifiers (also referred to as names) are described by the following lexical definitions:

identifier ::=  (letter|"_") (letter | digit | "_")*
letter     ::=  lowercase | uppercase
lowercase  ::=  "a"..."z"
uppercase  ::=  "A"..."Z"
digit      ::=  "0"..."9"

This means you will get a SyntaxError no matter how much hacking you do on __getattr__ and/or __getattribute__.

Upvotes: 1

rlms
rlms

Reputation: 11060

I don't think that there is exact syntax for that, but to me this feels similar:

class Thing:
    def __init__(self, x, y):
        self.point = (x, y)
    def __getattr__(self, name):
        if name == "x":
            return self.point[0]
        elif name == "y":
            return self.point[1]
        else:
            raise AttributeError
    def __setattr__(self, name, value):
        if name == "x":
            self.point = (value, self.point[1])
        elif name == "y":
            self.point = (self.point[0], value)
        else:
            object.__setattr__(self, name, value)

thing = Thing(4, 7)
thing.point = (3, 6)
thing.x = 5

Upvotes: 3

senshin
senshin

Reputation: 10360

There is no "member tuple" syntax of precisely the kind you're describing. Here is a possible workaround, though, for setting "tuples" of attributes and passing "tuples" of attributes. It's kind of bulky, so I don't know if I would actually want to use it, though.

class MyClass:
    pass

def set_tuple(obj, **attrs):
    for key in attrs:
        setattr(obj, key, attrs[key])

def pass_tuple(obj, *attrnames):
    return (getattr(obj, x) for x in attrnames)

def print_things(*iterable):
    for x in iterable:
        print(x)

obj = MyClass()
set_tuple(obj, a=1, b=2, c=3)
print(obj.a, obj.b, obj.c, '\n')
print_things(*pass_tuple(obj, 'a', 'c'))

Result:

1 2 3 

1
3

Upvotes: 0

Related Questions