Best way to do attribute docstrings?

K

Ken Kuhlman

What's the best method for annotating attributes with a docstring?
I'm looking to be able to introspect my class & get the comments back
out.

The technique I'm using is a little hoaky, but works.. basically I
create subclasses of the builtin types that I want to be able to use &
then instantiate them in my attribute assignments. This gives me an
object that I can tweak the __doc__ attribute on. An example of what
my code looks like is included below.

I saw that PEP 224 [1] was trying to meet this need, but got rejected
because the proposed syntax wasn't clear enough.

Thanks!
-Ken

[1] http://www.python.org/dev/peps/pep-0224/


attr_doc.py::

# Subclass str so we can modify instance's attributes (like __doc__)
class _(str): pass

# Boolean gave me extra fits that I don't understand, but this
technique works
class MyBool(int):
def __str__(self):
return `self.__int__() > 0`
def __repr__(self):
return self.__str__()

def bool_func(val):
def blah():
return MyBool(val)
return blah
true = bool_func(True)
false = bool_func(False)


class Animal(object):
four_legs = true()
four_legs.__doc__ = "Animal has four legs"

favorite_food = _('cheese')
favorite_food.__doc__ = "Animal's favorite food"

has_fur = true()
has_fur.__doc__ = "Animal has fur"

print Animal.four_legs.__doc__, '::', Animal.four_legs
print Animal.favorite_food.__doc__, '::', Animal.favorite_food
print Animal.has_fur.__doc__, '::', Animal.has_fur

------
This gives the expected results:

Animal has four legs :: True
Animal's favorite food :: cheese
Animal has fur :: True
 
G

Gabriel Genellina

What's the best method for annotating attributes with a docstring?
I'm looking to be able to introspect my class & get the comments back
out.

I don't know which is the *best* method. Perhaps you might use properties
instead. Or some kind of convention, like
four_legs__doc__ = "Animal has four legs"
to be the docstring associate to `four_legs` attribute.
The technique I'm using is a little hoaky, but works.. basically I
create subclasses of the builtin types that I want to be able to use &
then instantiate them in my attribute assignments. This gives me an
object that I can tweak the __doc__ attribute on. An example of what
my code looks like is included below.

I don't like subclassing builtins, it doesn't work in general. _("Hello")
+ _(" world!") is a plain str object, not your derived class.
What about instance attributes? All your examples show class attributes.
Your docstring appears to be attached to the attribute "value", but it
should be attached to the attribute definition, or be a class attribute,
or something like that, shared between all instances and somewhat
permanent. Else, reassigning the attribute will lose the docstring.
# Boolean gave me extra fits that I don't understand, but this
technique works
class MyBool(int):
def __str__(self):
return `self.__int__() > 0`
def __repr__(self):
return self.__str__()

Let Python do the boolean conversion: -1 is a true value, but MyBool(-1)
would say False.
I don't understand exactly why do you want this class, but I'd write it
this way:

class MyBool(int):
def __str__(self):
return str(bool(self))
__repr__ = __str__
def bool_func(val):
def blah():
return MyBool(val)
return blah
true = bool_func(True)
false = bool_func(False)

And why do you need such functions?
 
K

Ken Kuhlman

Thanks for the response! It was just the kick I needed to realize
that I was letting my frustration get the best of me and that I was
going down a dead-end. I blame the fact that I was coding on little
sleep :)

I went with the solution of giving a naming convention to the
docstrings and relying on that for the introspection -- that's a lot
less of a kludge than what I was attempting.

I still think it would be great if you could do something like:
@doc('Animal has four legs')
four_legs = True

IMHO, that syntax is nice (which was the objection to PEP 224), but
I'm not going to try to propose another PEP over it. My itch is
scratched for now -- if the naming convention hack gets under my skin
eventually I'll try going down a source parsing route next.

Thanks again for the help!
-Ken

PS: I hope it's OK that I ignored your questions -- my initial
solution was just too hideous to be scrutinized too closely.
 
K

Ken Kuhlman

Replying to myself in case someone finds this interesting.

Anyway, I took another shot at this with a little fresher mind, and it
was quickly obvious that I was trying to force attributes to behave
more like classes. It was a small step from there to creating a
factory function to return instances of the appropriate class. I'm
much happier with the result than either of the two previously posted
kludges.


# A factory function for generating attributes that can be annotated.
def attr(value, doc=None):
base = type(value)
if base == bool:
# bool class can't be used as a base, so we make one that can.
class MyBool(int):
def __str__(self):
return str(bool(self))
__repr__ = __str__
base = MyBool

class FancyAttr(base):
pass

fa = FancyAttr(value)
fa.__doc__ = doc
return fa


class Animal(object):
four_legs = attr(value = True, doc = "Animal has four legs")
favorite_food = attr('cheese', doc = "Animal's favorite food")
has_fur = attr(False)
has_fur.__doc__ "Animal has fur"

print Animal.four_legs.__doc__, '::', Animal.four_legs
print Animal.favorite_food.__doc__, '::', Animal.favorite_food
print Animal.has_fur.__doc__, '::', Animal.has_fur
 
G

Gabriel Genellina

Replying to myself in case someone finds this interesting.

Anyway, I took another shot at this with a little fresher mind, and it
was quickly obvious that I was trying to force attributes to behave
more like classes. It was a small step from there to creating a
factory function to return instances of the appropriate class. I'm
much happier with the result than either of the two previously posted
kludges.

Still there is something I don't understand on your design. You appear to
be always concerned about *class* attributes. *Instance* attributes are
far more comon, and usually they change their value many times.
class Animal(object):
four_legs = attr(value = True, doc = "Animal has four legs")
favorite_food = attr('cheese', doc = "Animal's favorite food")
has_fur = attr(False)
has_fur.__doc__ "Animal has fur"

As an example, how would you annotate attributes on a hierarchy like this?

class Animal(object):
can_fly = False # no Animal can fly unless explicitely noted
lives = 1 # most Animals have a single life
def __init__(self, color, legs, favorite_food):
self.color = color
self.legs = legs
self.favorite_food = favorite_food

class Mammal(Animal):
pass

class Cat(Mammal):
def __init__(self, color):
Mammal.__init__(self, color=color, legs=4, favorite_food='mice')
self.lives = 7 # a cat starts with 7 lives

class Mouse(Mammal):
def __init__(self, color, personality):
Mammal.__init__(self, color=color, legs=4, favorite_food='cheese')
self.personality = personality

class Bird(Animal):
can_fly = True
def __init__(self, color):
Animal.__init__(self, color=color, legs=2, favorite_food='seed')

tweety = Bird('yellow')
sylvester = Cat('black')
tom = Cat('blue')
jerry = Mouse('brown', 'nice')
itchy = Mouse('blue', 'sadist')
scratchy = Cat('black')
scratchy.lives = 7**7 # or so...

print tweety.legs
print scratchy.color
print scratchy.lives
 
K

Ken Kuhlman

Still there is something I don't understand on your design. You appear to
be always concerned about *class* attributes. *Instance* attributes are
far more comon, and usually they change their value many times.
....
Gabriel Genellina


I've only cared about class attributes to this point because my needs
have been simple. They may not always be, however, so I'll take a
shot at your challenge (below). I've basically just added a base
class that defines __setattr__ to the mix.

Thanks for the replies!
-Ken


# A factory function for generating attributes that can be annotated.
def attr(value, doc=None):
base = type(value)
if base == bool:
# bool class can't be used as a base, so we make one that can.
class MyBool(int):
def __str__(self):
return str(bool(self))
__repr__ = __str__
base = MyBool

class FancyAttr(base):
pass

fa = FancyAttr(value)
fa.__doc__ = doc
return fa

class AnnotatedAttrsBase(object):
_sticky_docs = True #False
def __setattr__(self, name, value):
""" Make sure attributes are fancy, maintaining docs if
they're sticky. """
if type(value).__name__ != 'FancyAttr':
doc = None
if self._sticky_docs and hasattr(self, name):
doc = self.__dict__[name].__doc__
self.__dict__[name] = attr(value, doc)
else:
self.__dict__[name] = value

class Animal(AnnotatedAttrsBase):
can_fly = attr(value = False,
doc = "no Animal can fly unless explicitly noted")
lives = attr(value = 1,
doc = "most Animals have a single life")
def __init__(self, color, legs, favorite_food):
self.color = attr(color, "every animal has a color")
self.legs = attr(legs, "most animals have legs")
self.favorite_food = favorite_food

class Mammal(Animal):
pass

class Cat(Mammal):
def __init__(self, color):
Mammal.__init__(self, color=color, legs=4,
favorite_food='mice')
self.lives = attr(value = 7,
doc = "a cat starts with 7 lives")

class Mouse(Mammal):
def __init__(self, color, personality):
Mammal.__init__(self, color=color, legs=4,
favorite_food='cheese')
self.personality = personality

class Bird(Animal):
can_fly = True
_sticky_docs = False
def __init__(self, color):
Animal.__init__(self, color=color, legs=2,
favorite_food='seed')

tweety = Bird('yellow')
tweety.lives = 42
sylvester = Cat('black')
tom = Cat('blue')
jerry = Mouse('brown', 'nice')
itchy = Mouse('blue', 'sadist')
scratchy = Cat('black')
scratchy.lives = attr(7**7, "or so...")

print scratchy.color, scratchy.color.__doc__
print scratchy.lives, scratchy.lives.__doc__
print tweety.legs, tweety.legs.__doc__
print tweety.lives, tweety.lives.__doc__
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top