This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: 'attrmap' function, attrmap(x)['attname'] == x.attname
Type: enhancement Stage:
Components: Library (Lib) Versions:
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: collinwinter, gregsmith, josiahcarlson, loewis, rhettinger
Priority: normal Keywords:

Created on 2005-01-26 16:28 by gregsmith, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Messages (10)
msg54354 - (view) Author: Gregory Smith (gregsmith) Date: 2005-01-26 16:28
One of the side effects of the new-style classes is that
objects don't necessarily have __dict__ attached to them.
It used to be possible to write things like

   def __str__(self):
          return "Node %(name)s, %(nlinks)d links,
active: %(active)s" % self.__dict__


... but this doesn't work if the class doesn't have a
__dict__. Even if does, I'm not sure it will always get
members from base classes.

There is a 'vars' function; you could put 'vars(self)'
in the above instead of self.__dict__, but it still
doesn't work if
the class doesn't have a __dict__.

I can see different solutions for this:

(1) change the 'string %' operator so that it allows
 %(.name)s, leading to a getattr() on the right-side
argument
  rather than a getitem. 

          return "Node %(.name)s, %(.nlinks)d links,
active: %(.active)s" % self

(2) Make a builtin like vars, but which works when the
object doesn't have a __dict__ I.e. attrmap(x) would
return a mapping which is bound to x, and reading
attrmap(x)['attname'] is the same as
getattr(x,'attname'). Thus

          return "Node %(name)s, %(nlinks)d links,
active: %(active)s" % attrmap(self)


This attrmap() function can be implemented in pure
python, of course.

I originally thought (1) made a lot of sense, but (2) seems
to work just as well and doesn't require changing much.
Also, (1) allows  cases like "%(name)s %(.name2)s",
which are not very useful, but are very likely to be
created by
accident; whereas in (2) you are deciding on the right
of the '%' whether you are naming attributes or
providing mapping keys. 

I'm not sure it's a good idea change 'vars' to have
this behaviour, since vars(x).keys() currently works in
a predictable way when vars(x) works; whereas
attrmap(x).keys() may not be complete, or possible, 
even when attrmap(x) is useful. I.e. when x has a
__getattr__ defined.
On the other hand, vars(x) doesn't currently do much at
all, so maybe it's possible to enhance it like this
without breaking anything.

The motivation for this came from the "%(name)s" issue,
but the attrmap() function would be useful in other
places e.g.

processdata( infile,  outfile, **attrmap(options))

... where options might be obtained from optparse, e.g.
  
Or, an attrmap can be used with the new Templates:
string.Template('Node $name').substitute( attrmap(node))

Both of these examples will work with vars(), but only
when the object actually has __dict__. This is why
I'm thinking it may make sense to enhance vars: some
code may be broken by the change; but other code,
broken by new-style classes, may be unbroken by this
change.

The proxy could be writable, so that
    attrmap(x)['a'] = y
is the same as
     x.a = y
.. which could have more uses.

A possible useful (possibly weird) variation: attrmap
accepts 1 or more parameters, and the resulting
proxy is bound to all of them. when attrmap(x,y,z)['a']
is done, the proxy will try x.a, y.a, z.a until one of
them doesn't raise AttributeError. So it's equivalent
to merging dictionaries. This would be useful
in the %(name)s or Template cases, where you want
information from several objects.



msg54355 - (view) Author: Martin v. Löwis (loewis) * (Python committer) Date: 2005-02-12 00:51
Logged In: YES 
user_id=21627

I think I would rather not to see this as a builtin, e.g.
putting it into UserDict.AttrMap instead.
msg54356 - (view) Author: Josiah Carlson (josiahcarlson) * (Python triager) Date: 2005-06-26 17:47
Logged In: YES 
user_id=341410

An implementation of the base attrmap functionality (as
suggested by Reinhold in his post in python-dev)...

class attrmap:
    def __init__(self, obj):
        self.obj = obj
    def __getitem__(self, key):
        return getattr(self.obj, key)

To Gregory Smith:  If you merely add the __getitem__ method
with a 'self' as the first argument of getattr, you don't
even need attrmap.

As an aside, when I want dictionary-like behavior from my
classes, I use a dictionary or something that implements a
subset of the mapping protocol.
msg54357 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2005-06-27 04:18
Logged In: YES 
user_id=80475

-1

While potentially useful, the function is entirely unintuitive (it has 
to be studied a bit before being able to see what it is for).  Also, 
the OP is short on use cases (none were presented).  IMO, this 
belongs as a cookbook recipe.
msg54358 - (view) Author: Gregory Smith (gregsmith) Date: 2005-10-26 18:14
Logged In: YES 
user_id=292741


To josiahcarlson: Yes, I can add __getitem__ to any class.
But I don't see why I should make a whole bunch of classes
act like dictionaries,  only so that I can pass them to
single API (like string.Template) which requires a
dictionary. A class should have a dictionary behaviour if it
makes sense for that class; if I have to add it
specifically, and only, to make it work well in one
particular situation, that's a kluge. If I have to add dict
behaviour to a whole bunch of classes to make them all work
in that one situation, that's a serious kluge. And entirely
unintuitive, to borrow a phrase. When you encounter this
situation, IMO, it's clear that a little 'pipe-fitting' like
this is the best solution.

Furthermore, and to underline the above, what if I have a
class which already has a __getitem__ interface for it's own
purposes? Chances are this behaves in a way which makes it
useless for harvesting general attributes from the object;
the object always has  a getattr interface, however.

Since ALL objects have a getattr interface, and  there's a
fair number of APIs that require objects with a read-only,
string-keyed dict interface, why not have this universal
pipe-fitting to tie them together, so as not to have to
modify the objects and/or interfaces.

To rhettinger:
First off, what do you mean by 'use case'? I gave three
examples of the use of the function, with %attrmap(x),
string.Template(...).substitute(attrmap(x));
attrmap(options).
 I can expand these to more detail if you like. I didn't
give use examples for the 'write' case; I haven't
encountered a use for that.

Yes, you have to think a bit before you see what it's for.
That's arguably true for super(), slice() and
operator.attrgetter() as well. Once you've run into the
situation where it's useful, it becomes pretty clear what
it's for.  Given the general problem it solves, it's hard to
imagine a neater and more intuitive solution IMO.

Here's a 2-line sales pitch, which was implied, but not
given, in the original post:


  def __str__(self):
     return "MyObj(name=%(name)s, n=%(n)d, links = %(links)d
)" % attrmap(self)

vs. the harder-to-maintain

     return "MyObj( name=%s, n=%d, links = %d )" %
(self.name, self.n, self.links)

...this requires the programmer to maintain the same list of
symbols twice, a burden which is very common in languages
inferior to Python.

And obviously it's very simple (the base functionality is 5
lines). Like attrgetter, It becomes more useful when it's
always available in the standard lib, and a little faster if
it happens to be in C.

to loewis:
   fair enough, maybe it doesn't qualify for a builtin. But
it might make more sense to do it as operator.attrmap rather
than UserDict.AttrMap; it seems to fit into the same
category as attrgettr and itemgetter. I wasn't aware of
those two when I originally posted.

And am I the only one who thinks the first example I gave
(with __str__ using "..."%self.__dict__) is a pretty neat
trick? And who has noticed it's been broken  with new-style
classes? That by itself, IMO, calls for a fix, and replacing
self.__dict__ with attrmap(self) is a pretty simple fix.


msg54359 - (view) Author: Josiah Carlson (josiahcarlson) * (Python triager) Date: 2005-10-27 06:16
Logged In: YES 
user_id=341410

gregsmith:

class attrmap:
    def __getitem__(self, key):
        return getattr(self, key)

Use that as a mixin with any class you want to offer
attribute mapping support to.  As in...

class foo(..., attrmap):
    ...

Now your class has attribute mapping support.  Amazing!

As for the neatness of __dict__, I have used it before, but
when I need to rely on using __dict__, I make sure my object
has one (generally by not creating or using __slots__).
msg54360 - (view) Author: Gregory Smith (gregsmith) Date: 2006-05-04 15:19
Logged In: YES 
user_id=292741

Josiah, I'm well aware of that, but as I said 10/26 I don't
want to add attribute mapping to a whole set of classes just
so they will all work a certain way in one specific
situation; I want a separate adapter so I can have attribute
mapping for ANY object in that specific situation. I can't
mixin a new base class into every class I might encounter,
since most of them are not under my control; but all of them
have an attribute interface and will all work with attrmap.
There's no reason to mess things up like that when
I can implement attrmap already (and have done) -- and you
have shown how short the code is on your 6/26/'05 post --
I'm just suggesting this is simple, elegant, and generally
useful enough to be included as a standard python gizmo.

msg54361 - (view) Author: Josiah Carlson (josiahcarlson) * (Python triager) Date: 2006-05-05 06:28
Logged In: YES 
user_id=341410

As I and others have expressed perhaps hundreds of times
over the last few years; not all X line functions/classes
should be built into Python.  This particular object doesn't
have enough general usefulness to make it into Python as a
builtin, so you would need to import something anyways.

If you are going to need to import to get at it, you may as
well put this somewhere, and perhaps monkey-patch
__builtins__ yourself so that you can get at it everywhere
without importing it.  Heck, you could even put it into
site.py .

If this were going to make it into Python, I would suggest
it go into the operator module, where a similar feature
already exists in the operator.attrgetter.  If there is also
desire, one may want to offer mapattr class which does
x.attr -> x['attr'] .

All in all, I'm -0; it would be useful, but I believe most
people would just write the 5-liner and call it good.
msg54362 - (view) Author: Gregory Smith (gregsmith) Date: 2006-05-07 15:27
Logged In: YES 
user_id=292741

I can't disagree with that -- one of the things I like about
python is that simple funcs I use fairly often can usually
be retyped out of my head in less time than it takes to find
them and copy them from another software project- and more
importantly, there's basically no risk that the fresh one
will be buggy, if it's expression is simple and clear.
So, the overhead of maintaining a zillion 'standard' utility
funcs outweighs the cost of having to recode them instead,
when they are small and simple. This applies as much to the
core library as it does to a site-specific library.

I do prefer if they have the same names each time I use them
though, since it makes it easier to transplant higher-level
chunks of code from one program to another. When I ran
across this issue and its solution, I figured it would be
something that, if available, could be used often enough to
justify have a standard name. But I agree now it shouldn't
be a builtin; having it as operator.attrmap still means you
can copy code using it from one application to another
without having to hunt down where 'attrmap' got implemented. 
msg54363 - (view) Author: Collin Winter (collinwinter) * (Python committer) Date: 2007-03-30 07:29
Closing due to lack of support.

If you're still interested in an operator.attrmap() function, please try and work up a patch to Modules/operator.c and resubmit.
History
Date User Action Args
2022-04-11 14:56:09adminsetgithub: 41492
2005-01-26 16:28:40gregsmithcreate