I think attributes are considered more pythonic for many things than
methods. We insist on methods for many things in Sage because you can
document methods (but it's hard and not intrinsically supported by
python to document and introspect attribute documentation).
This is from the PEP 8 style guide: http://www.python.org/dev/peps/pep-0008/
- For simple public data attributes, it is best to expose just the
attribute name, without complicated accessor/mutator methods.
Keep in
mind that Python provides an easy path to future enhancement,
should
you find that a simple data attribute needs to grow functional
behavior. In that case, use properties to hide functional
implementation behind simple data attribute access syntax.
Note 1: Properties only work on new-style classes.
Note 2: Try to keep the functional behavior side-effect free,
although
side-effects such as caching are generally fine.
Note 3: Avoid using properties for computationally expensive
operations; the attribute notation makes the caller believe
that access is (relatively) cheap.
Thanks,
Jason
I think attributes are considered more pythonic for many things than
methods. We insist on methods for many things in Sage because you can
document methods (but it's hard and not intrinsically supported by
python to document and introspect attribute documentation).
"Aha!" I thought, "you should be using *cached* properties." But then I
tried it:
sage: class Bar(object):
....: @property
....: @cached_method
....: def property_test(self):
....: print "calling test property"
....: return 5
....: @lazy_attribute
....: def lazy_test(self):
....: print "calling lazy attribute"
....: return 7
....:
sage: b=Bar()
sage: b.property_test
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
[snip]<ipython console> in <module>()
TypeError: 'CachedMethod' object is not callable
I'm not exactly sure why I got this error. Would it be easy for someone
to enlighten me?
Also, it seems that the brilliant people that implemented lazy_attribute
thought deeply about it compared with properties; at least, there is
some discussion of properties in the docstring for lazy_attribute. It
would be great if someone who has thought more deeply about the relative
advantages of each to chime in. My guess is that there are some
inheritance issues where the two approaches differ.
Thanks,
Jason
That's really cool. I didn't realize that ipython would pick up the
documentation to properties. I assume it appears in the Sphinx
documentation too?
Thanks,
Jason
Sorry for reacting this late...
The main reason why those two functions are functions and not attributes is
that at the time of designing the category system, they where a very strong
agreement that no attribute should be exposed to users, only methods. I think
this agreement still holds. We use lazy attribute for programming
technicalities (eg: element_class). For what concerns super_categories
all_super_categories, they definitely carry some important mathematical
information so I don't think we can consider them as programming
technicalities. Therefore we put them as function. Note that I'm pretty sure
that at some point they where lazy_attribute. Here is an old diff.
diff -r 0e6f5c052819 sage/categories/category_types.py
--- a/sage/categories/category_types.py Mon Dec 22 15:25:50 2008 -0800
+++ b/sage/categories/category_types.py Mon Dec 22 17:16:50 2008 -0800
@@ -738,6 +738,10 @@ class Fields(Category_uniq):
"""
return Fields, tuple([])
+ @lazy_attribute
+ def super_categories(self):
+ return[Rings()]
+
Cheers,
Florent
Hi There,On Fri, Oct 28, 2011 at 10:52:13PM -0700, Simon King wrote:
> With a deprecation warning, too? Or better no deprecation, since your
> solution implies that there will be a all_super_categories *method*
> also in future?
Sorry for reacting this late...
The main reason why those two functions are functions and not attributes is
that at the time of designing the category system, they where a very strong
agreement that no attribute should be exposed to users, only methods. I think
this agreement still holds.
Hi Maarten,
On 29 Okt., 12:59, Maarten Derickx <m.derick...@gmail.com>
wrote:
(2) means: We have best speed and we have documentation, but the
method carrying the documentation is not used internally.
> And even more important it allows
> for tab completion in ipython as shown below.
That's exactly the same for lazy attributes.
> I would find this tabcompletion very usefull for certain properties since I
> have often done something like:
> tmp = Object.method_wich_could_be_an_attribute()
> just to be able to have tab completion on tmp to see if it allowed me to do
> with it what I want.
When you do tmp = b.some_property, followed by tmp?, then you see the
documentation of the return value of b.some_property.
On Sun, Oct 30, 2011 at 04:32:36AM -0700, Maarten Derickx wrote:
> I was talking about tab completion. I now see that I explained my ideas
> slightly short and cryptic. What I meant to say I is that it is nice to be
> able to do l.hello.p<tab> like in the example I posted above. This is
> possible with all __get__ descriptors (so with lazy attributes and
> properties for example).
Just to be precise: For lazy attributes, this will only work if the
attribute has not been evaluated before. Otherwise, it's replaced by a
usual attribute, and the documentation is lost.
Cheers,
Nicolas
--
Nicolas M. Thi�ry "Isil" <nth...@users.sf.net>
http://Nicolas.Thiery.name/
Sorry for the late reaction; I have been off e-mail for the last week.
For _super_categories: yes I can be convinced that the speed gain is
worth the added complexity because there is some very low-level
algorithmic using it intensively; maybe _super_categories should be
only used in such low-level algorithmic though; e.g. only in the core
categories code.
For _all_super_categories: in most use cases, someone accessing
all_super_categories will go through the result (or maybe half of it
in average?). Since that result is quite big (a list of typically 5,
10, or even 20 categories), I guess that the speed gain is negligible
in practice (but please prove me wrong if that's not the case!), and
thus does not justify the added complexity.
Sorry again for the late suggestion; I hope it's not too much work
reverting half of the change!
Cheers,
Nicolas
Btw: there could be another point in separating super_categories / and
_super_categories: namely to handle full super categories. But let's
discuss this face to face when you come over.
Thanks for the enlightenment :-)
> In elliptic curve computations, you typically have polynomial rings
> over many (~1000) different finite fields. Some low-level methods will
> check whether such polynomial ring really belongs to the category of
> rings. But (at least with #9138) the category of a polynomial ring is
> the category of algebras over its base ring.
>
> Hence, here, we have ~1000 different categories, namely "Algebras"
> over the different finite fields.
>
> Testing whether F['x'] belongs to Rings() means: Testing whether
> Rings() appears in Algebras(F).all_super_categories().
Ok. Containment in a tiny list is darn cheap in Python :-)
So let's push the logic further. On one hand we are creating a second
gadget to model all the super categories of a category. On the other
hand, the use cases where speed is critical seem to be containment
tests. Doing a containment test using a list has had a bad smell to me
since I wrote that code, but I was too lazy to fix it.
So while we are at it, what about making this gadget into a
(frozen_)set rather than a list? I.e. we would have:
- a method C.all_super_categories(proper=false) returning a list
(order matters; btw we should make that a tuple at some point)
- a lazy attribute C._all_super_categories_as_set
- the doc of C.all_super_categories would mention the existence of
C._all_super_categories_as_set for containment tests.
This would also speed up is_subcategory tests (at least their first
call if there is a cache). And this seems better than caching
is_subcategory, since one does not waste memory for caching the result
of all the negative subcategory tests.
One caveat to test is whether containment tests in tiny Python lists
are not faster than containment tests in tiny Python sets.
Cheers,
Nicolas
Pretty convincing indeed :-)
> However, using #11115, it would still make sense to have
> C.is_subcategory(R) as a cached method: It would return the cached
> answer in a a few hundred nanoseconds.
At a little memory price. But I don't know how many negatives test we
are having in average, so whether this is any problem. An alternative
would be to have is_subcategory cythoned. Actually, maybe all of
Category should be eventually cythoned. But let's keep that for later.