FAQ
I have created the following class definition with the idea of making
a clean syntax for non-programmers to created structured data within a
python environment.

I would appreciate comments on this code. First, is something like
this already done? Second, are there reasons for not doing this? If
this seems OK, how could I clean up the string conversion to have
indented format.

The expected use would have all items in the structure be simple
python types or AttrClass types. Code written in python could walk the
structure in a simple way to locate any desired values. Code in a
C/C++ extension should also be able to walk the structure to use any
value in the structure.

class AttrClass(object):
"""AttrClass lets you freely add attributes in nested manner"""

def __init__(self):
pass
def __setitem__(self, key, value):
return self.__dict__.__setitem__(key, value)
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, self.__dict__.__repr__())
def __str__(self):
ll = ['{']
for k,v in self.__dict__.iteritems():
ll.append("%s : %s" % (k, str(v)))
return '\n'.join(ll) + '}'

def test():
atr = AttrClass()
atr.first = 1
atr.second = 2
atr.third = 'three'

atrsub = AttrClass()
atrsub.aaa = 'AAA'
atrsub.bbb = 'BBB'

atr.fourth = atrsub
atr.fifth = 5

print atr
print
print repr(atr)
print
print atr.fourth.aaa
=========
test() gives the following output:
----
{
second : 2
fifth : 5
fourth : {
aaa : AAA
bbb : BBB}
third : three
first : 1}

AttrClass({'second': 2, 'fifth': 5, 'fourth': AttrClass({'aaa': 'AAA',
'bbb': 'BBB'}), 'third': 'three', 'first': 1})

AAA
----

Ken Newton

Search Discussions

  • Steven D'Aprano at Sep 3, 2009 at 11:30 pm

    On Thu, 03 Sep 2009 15:46:01 -0700, Ken Newton wrote:

    I have created the following class definition with the idea of making a
    clean syntax for non-programmers to created structured data within a
    python environment.
    What do you expect non-programmers to do with this class, without
    programming? How can they even use it?


    ...
    The expected use would have all items in the structure be simple python
    types or AttrClass types. Code written in python could walk the
    structure in a simple way to locate any desired values. Code in a C/C++
    extension should also be able to walk the structure to use any value in
    the structure.
    I don't see the purpose of it. Nested data structures are generally
    harder for people to understand than non-nested ones, hence the Python
    Zen: "flat is better than nested". For example, a list is easier to grasp
    than a tree. This especially applies to non-programmers.

    I don't see how this is simple enough for non-programmers, or useful for
    programmers. For programmers, just nest objects:

    class Parrot(object):
    pass

    obj = Parrot()
    obj.x = 1
    obj.y = Parrot()
    obj.y.z = 2
    obj.y.z.zz = Parrot()



    class AttrClass(object):
    """AttrClass lets you freely add attributes in nested manner"""
    You don't actually need a special class for that, unless you're providing
    extra functionality. A bare subclass of object will let you freely add
    attributes in a nested manner.

    It seems to me that you're combining two different sets of functionality,
    but only describing one. The first is, nested attributes -- but you get
    that for free with any class. The second is the ability to set (but
    strangely not retrieve!) attributes using dictionary-like syntax. But
    that's implied by the code, you haven't mentioned it in your description
    or documentation.

    def __init__(self):
    pass
    If the __init__ method doesn't do anything, you don't need it.

    def __setitem__(self, key, value):
    return self.__dict__.__setitem__(key, value)
    Simpler to just say:

    def __setitem__(self, key, value):
    self.__dict__[key] = value

    You don't strictly need the return, because methods return None by
    default, and dict.__setitem__ always returns None (unless it raises an
    exception).


    This method allows you to set attributes using dict syntax:

    instance['key'] = 123 # same as instance.key = 123

    But you don't have corresponding __getitem__ or __delitem__ methods, so
    you can't do these:

    value = instance['key'] # fails
    del instance['key'] # fails


    def __repr__(self):
    return "%s(%s)" % (self.__class__.__name__,
    self.__dict__.__repr__())
    This strongly implies that you should be able to create a new instance
    using that format:

    AttrClass({'key': 123, 'another_key': 456})

    but that fails. As a general rule, the repr() of a class should (if
    possible) work correctly if passed to eval(). This doesn't. That's not
    entirely wrong, but it is unusual and confusing.




    --
    Steven
  • Ken Newton at Sep 4, 2009 at 1:44 am

    On Thu, Sep 3, 2009 at 4:30 PM, Steven D'Aprano wrote:
    On Thu, 03 Sep 2009 15:46:01 -0700, Ken Newton wrote:

    I have created the following class definition with the idea of making a
    clean syntax for non-programmers to created structured data within a
    python environment.
    What do you expect non-programmers to do with this class, without
    programming? How can they even use it?
    A couple extra details: The users are scientists, and technically
    sophisticated.
    They are used to specifying complicated sets of parameters and this
    structured
    form matches the way they are used to thinking about their work. Though they
    are not programmers, they can define expressions to represent the desired
    calculations for this project and occasionally write small programs in the
    course
    of their work. However, they will not like a long learning curve and will
    want to be
    isolated from most of the details in good programming design (error
    handling,
    creating supporting infrastructure, etc.) In this case, Python will be used
    as
    a scripting language with most of the details specified by simple function
    expressions
    or defining structured data or specifying (linear) sequences of actions.

    ...
    The expected use would have all items in the structure be simple python
    types or AttrClass types. Code written in python could walk the
    structure in a simple way to locate any desired values. Code in a C/C++
    extension should also be able to walk the structure to use any value in
    the structure.
    I don't see the purpose of it. Nested data structures are generally
    harder for people to understand than non-nested ones, hence the Python
    Zen: "flat is better than nested". For example, a list is easier to grasp
    than a tree. This especially applies to non-programmers.

    I don't see how this is simple enough for non-programmers, or useful for
    programmers. For programmers, just nest objects:

    class Parrot(object):
    pass

    obj = Parrot()
    obj.x = 1
    obj.y = Parrot()
    obj.y.z = 2
    obj.y.z.zz = Parrot()

    This is close to what I started with when I started exploring this idea.
    But I felt I needed a way to create some additional support, that is
    the str and repr functions. These structures will be created and
    populated once and saved in a python source file, which will be
    loaded from the embedding C++ program. The (scientist) users will
    need a way to review the structure and current values while
    interactively changing a few of the values to observe their effect on
    the system. My recursive __str__() and __repr__() functions were
    intended to provide that support.

    class AttrClass(object):
    """AttrClass lets you freely add attributes in nested manner"""
    You don't actually need a special class for that, unless you're providing
    extra functionality. A bare subclass of object will let you freely add
    attributes in a nested manner.

    It seems to me that you're combining two different sets of functionality,
    but only describing one. The first is, nested attributes -- but you get
    that for free with any class. The second is the ability to set (but
    strangely not retrieve!) attributes using dictionary-like syntax. But
    that's implied by the code, you haven't mentioned it in your description
    or documentation.

    def __init__(self):
    pass
    If the __init__ method doesn't do anything, you don't need it.

    Good point. This was a placeholder. My intention was to add some
    code to initialize the class from a dict. But didn't get around to
    implementing that. Or the ideas I tried (couple years ago) didn't work.


    def __setitem__(self, key, value):
    return self.__dict__.__setitem__(key, value)
    Simpler to just say:

    def __setitem__(self, key, value):
    self.__dict__[key] = value

    You don't strictly need the return, because methods return None by
    default, and dict.__setitem__ always returns None (unless it raises an
    exception).
    Now, I can't remember why I ended up with my strange syntax. Unless
    it was related to the fact that I started doing this in IronPython and this
    made it work better in that context or on the C# side of things. I'm now
    planning this for a CPython embedding situation. And, perhaps as you
    pointed out, the __setitem__ isn't needed at all in the code I showed.

    This method allows you to set attributes using dict syntax:

    instance['key'] = 123 # same as instance.key = 123

    But you don't have corresponding __getitem__ or __delitem__ methods, so
    you can't do these:

    value = instance['key'] # fails
    del instance['key'] # fails

    The getitem and delete behaviors are not expected to be common uses.
    But if I continue with this approach, should be implemented for
    completeness.
    def __repr__(self):
    return "%s(%s)" % (self.__class__.__name__,
    self.__dict__.__repr__())
    This strongly implies that you should be able to create a new instance
    using that format:

    AttrClass({'key': 123, 'another_key': 456})

    but that fails. As a general rule, the repr() of a class should (if
    possible) work correctly if passed to eval(). This doesn't. That's not
    entirely wrong, but it is unusual and confusing.
    Yes, as mentioned above, I would like to initialize from a dict. Then the
    given repr syntax would be a better fit. Appending a dict or concatenating
    two or more objects might also be useful. I'd also consider serializing to
    an XML file, or a variation of the ConfigFile or *.ini file formats if the
    data
    is kept to only a depth of 2 levels.


    --
    Steven
    Thanks for all your comments.

    Ken
    -------------- next part --------------
    An HTML attachment was scrubbed...
    URL: <http://mail.python.org/pipermail/python-list/attachments/20090903/119d7f62/attachment.htm>
  • Peter Otten at Sep 4, 2009 at 7:32 am

    Ken Newton wrote:

    class AttrClass(object):
    """AttrClass lets you freely add attributes in nested manner"""

    def __init__(self):
    pass
    def __setitem__(self, key, value):
    return self.__dict__.__setitem__(key, value)
    def __repr__(self):
    return "%s(%s)" % (self.__class__.__name__,
    self.__dict__.__repr__())
    def __str__(self):
    ll = ['{']
    for k,v in self.__dict__.iteritems():
    ll.append("%s : %s" % (k, str(v)))
    return '\n'.join(ll) + '}'

    def test():
    atr = AttrClass()
    atr.first = 1
    atr.second = 2
    atr.third = 'three'

    atrsub = AttrClass()
    atrsub.aaa = 'AAA'
    atrsub.bbb = 'BBB'

    atr.fourth = atrsub
    atr.fifth = 5

    print atr
    print
    print repr(atr)
    print
    print atr.fourth.aaa
    Just in case you didn't note: your test() function will run successfully
    even if you remove the __setitem__() method. Allowing

    atr["x"] = 42

    but not

    print attr["x"]

    may puzzle your intended audience.
  • Jan Kaliszewski at Sep 4, 2009 at 1:01 pm
    [originally from python-list at python.org,
    crossposted to python-ideas at python.org]

    04-09-2009 o 00:46:01 Ken Newton wrote:
    I have created the following class definition with the idea of making
    a clean syntax for non-programmers to created structured data within a
    python environment.

    I would appreciate comments on this code. First, is something like
    this already done? Second, are there reasons for not doing this? If
    this seems OK, how could I clean up the string conversion to have
    indented format.

    The expected use would have all items in the structure be simple
    python types or AttrClass types. Code written in python could walk the
    structure in a simple way to locate any desired values. Code in a
    C/C++ extension should also be able to walk the structure to use any
    value in the structure.

    class AttrClass(object):
    """AttrClass lets you freely add attributes in nested manner"""

    def __init__(self):
    pass
    def __setitem__(self, key, value):
    return self.__dict__.__setitem__(key, value)
    def __repr__(self):
    return "%s(%s)" % (self.__class__.__name__,
    self.__dict__.__repr__())
    def __str__(self):
    ll = ['{']
    for k,v in self.__dict__.iteritems():
    ll.append("%s : %s" % (k, str(v)))
    return '\n'.join(ll) + '}'
    [snip]

    I find the idea interesting and close to my own needs in many
    situations, if I could alter it a bit.

    Of course, we always can use an empty class ('class MyStruct: pass')
    or simply use a dict... But both methods are inconvinient in some
    ways.

    In the case of dict we are convicted -- even when we need static
    access -- to mapping notation (obj['member']) which is less
    convenient and (what's more important) more error-prone than
    attribute dot-notation.

    In the case of empty class/object we can use convenient attr
    dot-notation but dynamic access is less natural...

    IMHO there could be -- in collections module or even as a built-in
    factory function -- something (somehow) similar to namedtuple, but
    mutable and more dict-like. I'am less focused on nesting such
    structures, and more on making it a namespace-like objects with
    convenience-and-today-usage features. Please consider the code:


    class AttrDict(dict): # (or maybe from OrderedDict)
    "It's only a model. (Shhh!)"

    def __getattr__(self, name):
    if name.startswith('_'):
    raise AttributeError("AttrDict's key can't "
    "start with underscore")
    else:
    return self[name]

    def __setattr__(self, name, value):
    self[name] = value

    def __delattr__(self, name):
    del self[name]

    def __repr__(self):
    return '{0}({1})'.format(self.__class__.__name__,
    dict.__repr__(self))
    def __str__(self):
    return self._as_str()

    def _gen_format(self, indwidth, indstate):
    indst = indstate * ' '
    ind = (indstate + indwidth) * ' '
    yield ('\n' + indst + '{' if indstate else '{')
    for key, val in self.items():
    valstr = (str(val) if not isinstance(val, AttrDict)
    else val._as_str(indwidth, indstate + indwidth))
    yield '{ind}{key}: {valstr}'.format(ind=ind, key=key,
    valstr=valstr)
    yield indst + '}'

    def _as_str(self, indwidth=4, indstate=0):
    return '\n'.join(self._gen_format(indwidth, indstate))

    def _as_dict(self):
    return dict.copy(self)


    # Test code:
    if __name__ == '__main__':
    struct = AttrDict()
    struct.first = 1
    struct.second = 2.0
    struct.third = '3rd'
    struct.fourth = [4]
    print(struct)
    # output:
    # {
    # 'second': 2.0
    # 'fourth': [4]
    # 'third': '3rd'
    # 'first': 1
    # }

    del struct.fourth

    print(repr(struct))
    # output:
    # AttrDict({'second': 2.0, 'third': '3rd', 'first': 1})

    print(struct.first) # (static access)
    # output:
    # 1

    for x in ('first', 'second', 'third'):
    print(struct[x]) # (dynamic access)
    # output:
    # 1
    # 2.0
    # 3rd

    struct.sub = AttrDict(a=1, b=2, c�)
    print(struct._as_dict())
    # output:
    # {'second': 2.0, 'sub': AttrDict({'a': 1, 'c': 89, 'b': 2}),\
    # 'third': '3rd', 'first': 1}

    print(struct._as_str(8))
    # output:
    # {
    # second: 2.0
    # sub:
    # {
    # a: 1
    # c: 89
    # b: 2
    # }
    # third: 3rd
    # first: 1
    # }


    What do you think about it?

    Cheers,
    *j

    --
    Jan Kaliszewski (zuo) <zuo at chopin.edu.pl>
  • Ken Newton at Sep 4, 2009 at 4:55 pm
    <top_posting>
    I like this version very much. I'm ready to put this into practice to see
    how it
    works in practice.

    A minor point: I envision this to be used in a context where all key values
    are
    strings (legal attribute identifiers). But constructing an AttrClass from a
    dict
    or setting values directly with the dict syntax can allow any valid item as
    a
    dict key -- specifically numbers, tuples, etc. If the user of this class
    chooses
    to do this, a number of the items become inaccessible to the attribute
    syntax.
    In my case, I think this won't be a problem since I anticipate that values
    will
    always be set by the attribute syntax, but it might be an issue for other
    uses.
    </top_posting>
    On Fri, Sep 4, 2009 at 6:01 AM, Jan Kaliszewski wrote:

    [originally from python-list at python.org,
    crossposted to python-ideas at python.org] [snip]
    class AttrDict(dict): # (or maybe from OrderedDict)
    "It's only a model. (Shhh!)"

    def __getattr__(self, name):
    if name.startswith('_'):
    raise AttributeError("AttrDict's key can't "
    "start with underscore")
    else:
    return self[name]

    def __setattr__(self, name, value):
    self[name] = value

    def __delattr__(self, name):
    del self[name]

    def __repr__(self):
    return '{0}({1})'.format(self.__class__.__name__,
    dict.__repr__(self))
    def __str__(self):
    return self._as_str()

    def _gen_format(self, indwidth, indstate):
    indst = indstate * ' '
    ind = (indstate + indwidth) * ' '
    yield ('\n' + indst + '{' if indstate else '{')
    for key, val in self.items():
    valstr = (str(val) if not isinstance(val, AttrDict)
    else val._as_str(indwidth, indstate + indwidth))
    yield '{ind}{key}: {valstr}'.format(ind=ind, key=key,
    valstr=valstr)
    yield indst + '}'

    def _as_str(self, indwidth=4, indstate=0):
    return '\n'.join(self._gen_format(indwidth, indstate))

    def _as_dict(self):
    return dict.copy(self)


    # Test code:
    if __name__ == '__main__':
    struct = AttrDict()
    struct.first = 1
    struct.second = 2.0
    struct.third = '3rd'
    struct.fourth = [4]
    print(struct)
    # output:
    # {
    # 'second': 2.0
    # 'fourth': [4]
    # 'third': '3rd'
    # 'first': 1
    # }

    del struct.fourth

    print(repr(struct))
    # output:
    # AttrDict({'second': 2.0, 'third': '3rd', 'first': 1})

    print(struct.first) # (static access)
    # output:
    # 1

    for x in ('first', 'second', 'third'):
    print(struct[x]) # (dynamic access)
    # output:
    # 1
    # 2.0
    # 3rd

    struct.sub = AttrDict(a=1, b=2, c�)
    print(struct._as_dict())
    # output:
    # {'second': 2.0, 'sub': AttrDict({'a': 1, 'c': 89, 'b': 2}),\
    # 'third': '3rd', 'first': 1}

    print(struct._as_str(8))
    # output:
    # {
    # second: 2.0
    # sub:
    # {
    # a: 1
    # c: 89
    # b: 2
    # }
    # third: 3rd
    # first: 1
    # }


    What do you think about it?

    Cheers,
    *j

    --
    Jan Kaliszewski (zuo) <zuo at chopin.edu.pl>

    --
    http://mail.python.org/mailman/listinfo/python-list
    -------------- next part --------------
    An HTML attachment was scrubbed...
    URL: <http://mail.python.org/pipermail/python-list/attachments/20090904/4edcb57c/attachment.htm>
  • Jan Kaliszewski at Sep 4, 2009 at 8:37 pm

    04-09-2009 Ken Newton wrote:

    I like this version very much. I'm ready to put this into practice to see
    how it works in practice.
    [snip]

    Not only you (Ken) and me. :-) It appears that the idea is quite old. Nick
    Coghlan replied at python-ideas at python.org:
    Jan Kaliszewski wrote:
    What do you think about it?
    It reminds me a bit of the old (short-lived) namespaces module:

    http://web.archive.org/web/20060216094030/http://namespace.python-hosting.com/

    Steven's draft PEP on the topic is still available in the python-list
    archives:

    http://mail.python.org/pipermail/python-list/2005-February/307235.html

    The problem we found with it was that the basic solutions (empty class
    and now named_tuple) were good enough that it wasn't worth the hassle
    involved in grabbing an extra library for it.
    Named tuples (which indeed are really very nice) are read-only, but the
    approach they represent could (and IMHO should) be extended to some kind
    of mutable objects.

    The old discussion, the above link points to, shows that such a
    dot-accessible dict-like class is something that many people need and
    repeatedly implemet it (more or less perfectly) for themselves.

    Maybe that past proposition (to add a separate namespace module which
    a number types for viewing, chaining and so on) was too sophisticated?

    Most common use cases could be covered with one attr-dict-like type,
    that could be placed in collections module (or even, in time, as
    a built-in factory function, together with namedtuple?).


    Cheers,
    *j
  • George Sakkis at Sep 4, 2009 at 9:04 pm

    On Fri, Sep 4, 2009 at 4:37 PM, Jan Kaliszewskiwrote:
    04-09-2009 Ken Newton wrote:
    I like this version very much. I'm ready to put this into practice to see
    how it works in practice.
    [snip]

    Not only you (Ken) and me. :-) It appears that the idea is quite old. Nick
    Coghlan replied at python-ideas at python.org:
    Jan Kaliszewski wrote:
    What do you think about it?
    It reminds me a bit of the old (short-lived) namespaces module:


    http://web.archive.org/web/20060216094030/http://namespace.python-hosting.com/

    Steven's draft PEP on the topic is still available in the python-list
    archives:

    http://mail.python.org/pipermail/python-list/2005-February/307235.html

    The problem we found with it was that the basic solutions (empty class
    and now named_tuple) were good enough that it wasn't worth the hassle
    involved in grabbing an extra library for it.
    Named tuples (which indeed are really very nice) are read-only, but the
    approach they represent could (and IMHO should) be extended to some kind
    of mutable objects.
    Maybe something like http://code.activestate.com/recipes/576555/ ?

    George
  • Steven D'Aprano at Sep 5, 2009 at 4:49 am

    On Fri, 04 Sep 2009 22:37:15 +0200, Jan Kaliszewski wrote:

    Named tuples (which indeed are really very nice) are read-only, but the
    approach they represent could (and IMHO should) be extended to some kind
    of mutable objects.
    What do you mean "read-only"? Do you mean immutable?

    What sort of extensions did you have in mind? You can add arbitrary
    attributes to a named tuple by subclassing:
    class Cls(namedtuple('C', 'x y z')):
    ... pass
    ...
    inst = Cls(1, 2, 3)
    inst.foo = 4

    It's hard to see how you could have a named list... how would it work? If
    you append data to the namedlist, what names would they get? If you sort
    the namedlist, do the names move, or just the data?


    The old discussion, the above link points to, shows that such a
    dot-accessible dict-like class is something that many people need and
    repeatedly implemet it (more or less perfectly) for themselves.
    I think it's something which people copy from other languages because
    that's what they're used to, not because they need it.

    It's just a change in syntax. Whether you write x.key or x['key'] is a
    matter of convenience. Attribute access is optimized for when you know
    the key names at compile time, key access is optimized for when you don't
    know the names until runtime. Compare:

    # You know the key when you write the code.
    x.key versus x['key']

    # You don't know the key until runtime.
    s = get_key()
    getattr(x, s) versus x[s]



    --
    Steven
  • Ken Newton at Sep 5, 2009 at 5:51 am

    On Fri, Sep 4, 2009 at 9:49 PM, Steven D'Apranowrote:
    ...
    The old discussion, the above link points to, shows that such a
    dot-accessible dict-like class is something that many people need and
    repeatedly implemet it (more or less perfectly) for themselves.
    I think it's something which people copy from other languages because
    that's what they're used to, not because they need it.

    It's just a change in syntax. Whether you write x.key or x['key'] is a
    matter of convenience. Attribute access is optimized for when you know
    the key names at compile time, key access is optimized for when you don't
    know the names until runtime. Compare:

    # You know the key when you write the code.
    x.key versus x['key']

    # You don't know the key until runtime.
    s = get_key()
    getattr(x, s) versus x[s]
    ...

    I would think this is much more than just copy from other language styles or
    'just' a syntax change -- the apparent widespread use would hint at a deeper
    need. ?For my use, the need is the significant simplification in interactive use
    (less and easier typing for x.key than x['key']). ?Even for the cases where
    my anticipated users will save lines of code in a script or module, they will
    do this in a context where they do generally know the keys when they write
    their code. ...And they will appreciate the simpler semantics of the attribute
    access. ?In my case also, execution speed will not be critical. This class will
    be used as a set of configuration parameters that will generally not be applied
    in time critical loops, but in occasional use generation of parameterized
    method sequences to control a main application coded in C/C++.

    The C interface is a reason that it would be nice to see this as a
    basic type rather
    than an add-on module. ?Having an API pre-defined on the C side for this would
    also be directly useful in my case.

    Ken
  • Steven D'Aprano at Sep 5, 2009 at 9:57 am

    On Fri, 04 Sep 2009 22:51:39 -0700, Ken Newton wrote:

    On Fri, Sep 4, 2009 at 9:49 PM, Steven
    D'Apranowrote: ...
    The old discussion, the above link points to, shows that such a
    dot-accessible dict-like class is something that many people need and
    repeatedly implemet it (more or less perfectly) for themselves.
    I think it's something which people copy from other languages because
    that's what they're used to, not because they need it.

    It's just a change in syntax. Whether you write x.key or x['key'] is a
    matter of convenience. Attribute access is optimized for when you know
    the key names at compile time, key access is optimized for when you
    don't know the names until runtime. Compare:

    # You know the key when you write the code. x.key versus x['key']

    # You don't know the key until runtime. s = get_key()
    getattr(x, s) versus x[s]
    ...

    I would think this is much more than just copy from other language
    styles or 'just' a syntax change -- the apparent widespread use would
    hint at a deeper need.
    "Apparent" is the key word there. There are lots of people who *say* this
    this useful functionality, but how many of them *actually* use it? And of
    those who do use it, how many of them know what they're doing? There are
    an awful lot of bad programmers out there.

    If you do need such functionality, it's easy to implement. Here's one:

    http://code.activestate.com/recipes/502219/

    No comment, no votes.

    Here's another:

    http://code.activestate.com/recipes/361668/

    Four comments, mostly negative, no votes.

    To me, this seems like a superficially attractive idea which is actually
    a bad idea, even when it's useful. Using a screwdriver as a chisel, or
    crowbar is useful too, and I've done so myself, but it's still usually a
    bad idea.

    Key lookup and attribute access are superficially similar, and they're
    implemented the same way in Python, but they're actually for different
    conceptual purposes. Attributes are conceptually part of the object,
    while key/values are conceptually data attached to the object. As a
    general rule, if obj.x is an attribute, then every valid obj should have
    an attribute x. But if obj['x'] is a key/value, then it is data-specific:
    some instances will have an 'x' key, and some won't. Consequently,
    obj.has_key('x') (better written these days as 'x' in obj) will be MUCH
    more common than hasattr(obj, 'x').

    For example, dict.clear has a special meaning shared by all dicts.
    dict['clear'] does not. You confuse the two at your peril.



    --
    Steven
  • Jan Kaliszewski at Sep 5, 2009 at 4:20 pm

    05-09-2009 Steven D'Aprano wrote:
    On Fri, 04 Sep 2009 22:37:15 +0200, Jan Kaliszewski wrote:

    Named tuples (which indeed are really very nice) are read-only, but the
    approach they represent could (and IMHO should) be extended to some kind
    of mutable objects.
    [snip]
    What sort of extensions did you have in mind?
    Two useful (from my point of view) concepts have appeared (or been linked
    to) in this thread -- on python-list and python-ideas:

    * the namespace/AttrDict concept (see Ken Newton's, Nick Coghlan's and my
    posts).

    * record concept (see George Sakkis post).
    The old discussion, the above link points to, shows that such a
    dot-accessible dict-like class is something that many people need and
    repeatedly implemet it (more or less perfectly) for themselves.
    I think it's something which people copy from other languages because
    that's what they're used to, not because they need it.
    I don't think so, especially if we say about the former. IMHO it is simply
    useful in practice, especially for scripting (but not only) -- being more
    convenient than using empty class.

    It offers (in compact way, without additional efforts and verbose
    syntax -- once you have got such a tool implemented) three things at
    the same time, without necessity to choose between them: comfortable
    static attribute access, flexible dict-like dynamic access when needed
    and possibility of iteration.
    It's just a change in syntax. Whether you write x.key or x['key'] is a
    matter of convenience. Attribute access is optimized for when you know
    the key names at compile time, key access is optimized for when you don't
    know the names until runtime.
    Exactly. It is a matter of *convenience* (as well as large areas of Python)
    and that's the point. I suppose that that is the reason for people to
    repeatedly implement it for themselves.

    05-09-2009 Steven D'Aprano wrote:
    On Fri, 04 Sep 2009 22:51:39 -0700, Ken Newton wrote:
    [snip]
    I would think this is much more than just copy from other language
    styles or 'just' a syntax change -- the apparent widespread use would
    hint at a deeper need.
    "Apparent" is the key word there. There are lots of people who *say* this
    this useful functionality, but how many of them *actually* use it? And of
    those who do use it, how many of them know what they're doing? There are
    an awful lot of bad programmers out there.

    If you do need such functionality, it's easy to implement. Here's one:
    Neither you nor me have hard evidence about popularity/unpopularity of the
    idea (number of places where you can find similar, more or less successful,
    attempts to implement it seems to testify in favour of the idea) -- nor
    about how it is used or abused.

    Obviously there are a lot of bad programmers who are able to use globals
    instead of function arguments etc.... Thats the fate of every language
    feature.

    But it's not the reason to resign from a feature that has particular common
    and proper use-cases. Even official Python tutorial mentions a case that is
    typical for the matter:

    http://docs.python.org/3.1/tutorial/classes.html#odds-and-ends
    As a
    general rule, if obj.x is an attribute, then every valid obj should have
    an attribute x. But if obj['x'] is a key/value, then it is data-specific:
    some instances will have an 'x' key, and some won't.
    It's often true but not always (see e.g. the above example in docs).

    Cheers,
    *j

    --
    Jan Kaliszewski (zuo) <zuo at chopin.edu.pl>
  • Ethan Furman at Sep 6, 2009 at 6:20 pm

    Steven D'Aprano wrote:
    On Fri, 04 Sep 2009 22:51:39 -0700, Ken Newton wrote:

    I would think this is much more than just copy from other language
    styles or 'just' a syntax change -- the apparent widespread use would
    hint at a deeper need.

    "Apparent" is the key word there. There are lots of people who *say* this
    this useful functionality, but how many of them *actually* use it? And of
    those who do use it, how many of them know what they're doing? There are
    an awful lot of bad programmers out there.
    In the dbf module I wrote, I use both the attribute access and the key
    lookup. The attribute access is great for interactive use, and for all
    the routines that play with the tables we have at work, where all the
    field names are indeed known at compile (aka coding) time. On the other
    hand, some routines don't know which fields they'll mucking about with,
    and so the key access is vital for them.

    Of course, I could have done the whole thing using key access, and I did
    have to impose some restrictions on method names so they wouldn't clash
    with possible field names, but I love being able to type

    current_record.full_name == last_record.full_name

    instead of

    current_record['full_name'] == last_record['full_name']

    and it's much easier on my wrists, too.

    Hopefully-not-a-bad-programmer-ly yours,

    ~Ethan~
  • Jan Kaliszewski at Sep 6, 2009 at 11:37 pm

    06-09-2009 o 20:20:21 Ethan Furman wrote:

    In the dbf module I wrote, I use both the attribute access and the key
    lookup. The attribute access is great for interactive use, and for all
    the routines that play with the tables we have at work, where all the
    field names are indeed known at compile (aka coding) time. On the other
    hand, some routines don't know which fields they'll mucking about with,
    and so the key access is vital for them.

    Of course, I could have done the whole thing using key access, and I did
    have to impose some restrictions on method names so they wouldn't clash
    with possible field names, but I love being able to type

    current_record.full_name == last_record.full_name

    instead of

    current_record['full_name'] == last_record['full_name']
    Me too, and I suppose many people too...

    The latter:

    * makes your code less readable if there is high density of such
    expressions;

    * makes typing much more strenuous/irritating -- what is not very
    important in case of advanced development (when time of typing is
    short in relation to time of thinking/reading/testing) but becomes
    quite important in case of scripting (which is still important area
    of Python usage).

    --
    Jan Kaliszewski (zuo) <zuo at chopin.edu.pl>
  • Jan Kaliszewski at Sep 8, 2009 at 1:02 am

    08-09-2009 o 02:15:10 Steven D'Aprano wrote:
    On Mon, 7 Sep 2009 09:37:35 am Jan Kaliszewski wrote:
    06-09-2009 o 20:20:21 Ethan Furman wrote:
    ... I love being able to type

    current_record.full_name == last_record.full_name

    instead of

    current_record['full_name'] == last_record['full_name']
    Me too, and I suppose many people too...

    The latter:

    * makes your code less readable if there is high density of such
    expressions;

    * makes typing much more strenuous/irritating -- what is not very
    important in case of advanced development (when time of typing is
    short in relation to time of thinking/reading/testing) but becomes
    quite important in case of scripting (which is still important
    area of Python usage).
    If you have a large number of such expressions, what's wrong with this?
    a['xyz'] = something['blablabla'] + somethingelse['foobar']
    b['ababababa'] += afun(bobo['dodo']['kookoo'] * pofopofo['gh'][0]['a'])
    cupu['abc'] = (kukumunu['bo'], kukumunu['kuu'].mbmbmb['lalala'])

    a.xyz = something.blablabla + somethingelse.foobar
    b.ababababa += afun(bobo.dodo.kookoo * pofopofo.gh[0].a)
    cupu.abc = (kukumunu.bo, kukumunu.kuu.mbmbmb.lalala)

    For me the latter is definitely easier to read and understand.
    FNAME = "full_name" # Define the string in one place only.
    current_record[FNAME] == last_record[FNAME] # Use it in many places.

    Not only is it shorter to use, but it's easy to change the
    key "full_name" to (say) "complete_name" or "volledige_naam" with one
    edit, and without mistakenly changing some other string which just
    happens to match the key.
    You are right, but it's a bit different story... I don't say that attr
    access is always better than key access -- but only that sometimes it is.
    (I don't know about others, but when I'm
    first working on a piece of code, and before I settle on an API or
    database schema, I often change field names two or three times before I
    settle in on the final version.)
    Me too! :)
    In any case, while I accept that this is sometimes useful, I also think
    that it's a something which is simple enough to add to your classes
    when necessary with just a few lines -- all you really need are the
    __*attr__ methods, everything else is superfluous. If you're doing this
    a lot, avoid boilerplate with a class decorator. Here's an untested
    minimalistic version which probably does everything necessary:

    def add_attr(cls):
    """Class decorator which adds attribute access to mappings."""
    def __getattr__(self, name):
    return self[name]
    def __setattr__(self, name, value):
    self[name] = value
    def __delattr__(self, name):
    del self[name]
    for func in (__getattr__, __setattr__, __delattr__):
    setattr(cls, func.__name__, func)
    return cls
    I'd add to it also dict-like iteration (__iter__(), _keys(), _values(),
    _items()) and __str__ adjusted to nice nested representation (like in
    some posts in this thread, e.g. my proposition).
    Fields of an object (attributes) and keys of a mapping are generally for
    different purposes, and I'm not sure we should encourage people to
    conflate the two. I think this belongs in the cookbook, not the
    standard library.
    I think it depends how often people need to implement such boiler-plate
    code for themselves. Now I see that this thread is not very popular, so
    indeed maybe you are right... Though it'd be nice to have OOTB such
    a factory in `collections` module...

    Cheers,
    *j

    --
    Jan Kaliszewski (zuo) <zuo at chopin.edu.pl>
  • Ken Newton at Sep 8, 2009 at 2:02 am
    On Mon, Sep 7, 2009 at 6:02 PM, Jan Kaliszewskiwrote:
    ...
    I think it depends how often people need to implement such boiler-plate
    code for themselves. Now I see that this thread is not very popular, so
    indeed maybe you are right... Though it'd be nice to have OOTB such
    a factory in `collections` module...
    ...

    How about, if this is not judged to be popular enough to become part of core
    or other standard module, at least put a version into a FAQ for easy future
    access by those of us that have a use for it? The suggestions here (and
    past versions) are much better than my first do-it-myself version - a standard
    peer-reviewed recipe would have been very nice to find.
  • Jim Jewett at Sep 8, 2009 at 2:53 pm

    On Mon, Sep 7, 2009 at 9:02 PM, Jan Kaliszewskiwrote:
    08-09-2009 o 02:15:10 Steven D'Aprano wrote:
    ... what's wrong with this?
    a['xyz'] = something['blablabla'] + somethingelse['foobar']
    b['ababababa'] += afun(bobo['dodo']['kookoo'] * pofopofo['gh'][0]['a'])
    cupu['abc'] = (kukumunu['bo'], kukumunu['kuu'].mbmbmb['lalala'])
    a.xyz = something.blablabla + somethingelse.foobar
    b.ababababa += afun(bobo.dodo.kookoo * pofopofo.gh[0].a)
    cupu.abc = (kukumunu.bo, kukumunu.kuu.mbmbmb.lalala)
    For me the latter is definitely easier to read and understand.
    I would describe it as "less difficult" rather than "easier". My
    biggest problem is that at that stage, I'm still typing raw, and
    inclined to make typos.

    The difference between fname and fnam won't be caught either way, but
    field access at least keeps me from forgetting quotes, or forgetting
    them at one end.
    ... I often change field names two or three times
    before I settle in on the final version.
    And often because of an ambiguity with another field that I hadn't
    originally thought to name. Neither solution fixes this, but
    attribute access is slightly easier to change.
    [recipe to simplify attr-access]
    I think it depends how often people need to
    implement such boiler-plate code for themselves.
    Attribute access is clearly better -- except for one thing.

    While I'm doing this, I'm still in exploratory mode, and I *will* need
    to clean up the API if I ever want better than quick-and-dirty. If
    the quick-and-dirty is already using attribute access, that makes the
    transition a bit trickier. If the quick-and-dirty is using dict
    access, at least I have a clear marker.

    -jJ
  • Colin J. Williams at Sep 4, 2009 at 3:12 pm

    Jan Kaliszewski wrote:
    [originally from python-list at python.org,
    crossposted to python-ideas at python.org]

    04-09-2009 o 00:46:01 Ken Newton wrote:
    I have created the following class definition with the idea of making
    a clean syntax for non-programmers to created structured data within a
    python environment.

    I would appreciate comments on this code. First, is something like
    this already done? Second, are there reasons for not doing this? If
    this seems OK, how could I clean up the string conversion to have
    indented format.

    The expected use would have all items in the structure be simple
    python types or AttrClass types. Code written in python could walk the
    structure in a simple way to locate any desired values. Code in a
    C/C++ extension should also be able to walk the structure to use any
    value in the structure.

    class AttrClass(object):
    """AttrClass lets you freely add attributes in nested manner"""

    def __init__(self):
    pass
    def __setitem__(self, key, value):
    return self.__dict__.__setitem__(key, value)
    def __repr__(self):
    return "%s(%s)" % (self.__class__.__name__,
    self.__dict__.__repr__())
    def __str__(self):
    ll = ['{']
    for k,v in self.__dict__.iteritems():
    ll.append("%s : %s" % (k, str(v)))
    return '\n'.join(ll) + '}'
    [snip]

    I find the idea interesting and close to my own needs in many
    situations, if I could alter it a bit.

    Of course, we always can use an empty class ('class MyStruct: pass')
    or simply use a dict... But both methods are inconvinient in some
    ways.

    In the case of dict we are convicted -- even when we need static
    access -- to mapping notation (obj['member']) which is less
    convenient and (what's more important) more error-prone than
    attribute dot-notation.

    In the case of empty class/object we can use convenient attr
    dot-notation but dynamic access is less natural...

    IMHO there could be -- in collections module or even as a built-in
    factory function -- something (somehow) similar to namedtuple, but
    mutable and more dict-like. I'am less focused on nesting such
    structures, and more on making it a namespace-like objects with
    convenience-and-today-usage features. Please consider the code:


    class AttrDict(dict): # (or maybe from OrderedDict)
    "It's only a model. (Shhh!)"

    def __getattr__(self, name):
    if name.startswith('_'):
    raise AttributeError("AttrDict's key can't "
    "start with underscore")
    else:
    return self[name]

    def __setattr__(self, name, value):
    self[name] = value

    def __delattr__(self, name):
    del self[name]

    def __repr__(self):
    return '{0}({1})'.format(self.__class__.__name__,
    dict.__repr__(self))
    def __str__(self):
    return self._as_str()

    def _gen_format(self, indwidth, indstate):
    indst = indstate * ' '
    ind = (indstate + indwidth) * ' '
    yield ('\n' + indst + '{' if indstate else '{')
    for key, val in self.items():
    valstr = (str(val) if not isinstance(val, AttrDict)
    else val._as_str(indwidth, indstate + indwidth))
    yield '{ind}{key}: {valstr}'.format(ind=ind, key=key,
    valstr=valstr)
    yield indst + '}'

    def _as_str(self, indwidth=4, indstate=0):
    return '\n'.join(self._gen_format(indwidth, indstate))

    def _as_dict(self):
    return dict.copy(self)


    # Test code:
    if __name__ == '__main__':
    struct = AttrDict()
    struct.first = 1
    struct.second = 2.0
    struct.third = '3rd'
    struct.fourth = [4]
    print(struct)
    # output:
    # {
    # 'second': 2.0
    # 'fourth': [4]
    # 'third': '3rd'
    # 'first': 1
    # }

    del struct.fourth

    print(repr(struct))
    # output:
    # AttrDict({'second': 2.0, 'third': '3rd', 'first': 1})

    print(struct.first) # (static access)
    # output:
    # 1

    for x in ('first', 'second', 'third'):
    print(struct[x]) # (dynamic access)
    # output:
    # 1
    # 2.0
    # 3rd

    struct.sub = AttrDict(a=1, b=2, c�)
    print(struct._as_dict())
    # output:
    # {'second': 2.0, 'sub': AttrDict({'a': 1, 'c': 89, 'b': 2}),\
    # 'third': '3rd', 'first': 1}

    print(struct._as_str(8))
    # output:
    # {
    # second: 2.0
    # sub:
    # {
    # a: 1
    # c: 89
    # b: 2
    # }
    # third: 3rd
    # first: 1
    # }


    What do you think about it?

    Cheers,
    *j
    I like both suggestions. The dot notation is simpler than the dictionary one,
    in may cases.

    struct is perhaps a name to avoid, as it is a standard module.

    The result has similarities to the Pascal Record:
    http://uva.ulb.ac.be/cit_courseware/pascal/pas048.htm

    Colin W.
  • Scott David Daniels at Sep 4, 2009 at 3:35 pm
    Ken Newton wrote: ...
    I would appreciate comments on this code. First, is something like
    this already done? Second, are there reasons for not doing this? ...

    class AttrClass(object): ...
    def __repr__(self):
    return "%s(%s)" % (self.__class__.__name__, self.__dict__.__repr__())
    def __str__(self):
    ll = ['{']
    for k,v in self.__dict__.iteritems():
    ll.append("%s : %s" % (k, str(v)))
    return '\n'.join(ll) + '}'
    Yes, I've done stuff something like this (I use setattr /
    getattr rather than direct access to the __dict__).

    You'd do better to sort the keys before outputting them, so
    that you don't confuse the user by printing two similarly
    built parts in different orders.

    Personally, I'd filter the outputs to avoid names beginning
    with '_', as they may contribute to clutter without adding
    much information.

    An equality operator would be nice as well (don't bother with
    ordering though, you get lost in a twisty maze of definitions
    all different).

    --Scott David Daniels
    Scott.Daniels at Acm.Org

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouppython-list @
categoriespython
postedSep 3, '09 at 10:46p
activeSep 8, '09 at 2:53p
posts19
users9
websitepython.org

People

Translate

site design / logo © 2022 Grokbase