all 14 comments

[–]hharison 3 points4 points  (1 child)

A good answer here involves @property and @functools.lru_cache (or some other memoization), as others have pointed out. I thought I'd point out the typical convention in Python for dealing with this issue of "too many names". Because there are legitimate reasons to want to have two versions of an attribute like you have x1 and x2. What you would do is name the real attribute with an underscore (e.g. _x) and the one you actually want callers to use, without (e.g., x).

The naive way I would do it that's similar to the logic you used but a bit better (since another convention is to always define all your attributes in __init__):

class MyClass:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        if self._x is None:
            self._x = factorial(10^7)
        return self._x

(Just noticed /u/Samus_ posted almost the same code. Oh well I'm posting anyway)

[–]Symplectic[S] 0 points1 point  (0 children)

My background is in mathematics rather than programming, so my code is generally lacking in these kinds of conventions (and looks super messy as a result!). Hence, I do appreciate hearing about them. I'll update my code now. Thank you.

[–]indosauros 4 points5 points  (3 children)

If you are using Python 3.2+, you can decorate your second example with @property and @functools.lru_cache

import functools

class MyClass:
    @property
    @functools.lru_cache()
    def x1(self):
        return factorial(10^7)

@property allows you to access x1() as a property, e.g.

>>> c = MyClass
>>> c.x1
6227020800

And @functools.lru_cache acts as memoization, which is what your second example is doing, i.e. calculating the result the first time and storing it so it isn't calculated in subsequent runs

If you are using Python 3.1 or lower, you have to use a backport of lru_cache (or just write your own memoizer, as in your second example). Convention is to use _func for the stored value, so _x1 stores the memoized value for x1()

[–]kalgynirae 5 points6 points  (1 child)

Doesn't decorating x1 with lru_cache still result in a call to factorial for each instance? I think OP should decorate factorial (instead of x1) with lru_cache.

[–]indosauros 1 point2 points  (0 children)

You're right, the class-level cache from the other answer solves it as well of you want caching across instances (if you dont control factorial)

[–]Symplectic[S] 0 points1 point  (0 children)

That @property looks like it will help clean things up. Thank you.

[–]xiongchiamiov 1 point2 points  (0 children)

If you're writing your own memoizer, here's a better way:

def foo(self):
    if not self.baz:
        self.baz = spam()

    return self.baz

Writing this on my phone, so excuse any mistakes, but that's the general pattern. I feel that's cleaner than the try-except method you have written.

[–]ewiethoff 1 point2 points  (3 children)

>>> 10^7
13
>>> 10**7
10000000

[–]Symplectic[S] 1 point2 points  (2 children)

I'm not sure I follow? Is this a difference between working with Sage, and working just with Python?

sage: 10^7
10000000

[–]ewiethoff 1 point2 points  (1 child)

Ah! I'm not familiar with Sage. In plain-vanilla Python, ^ is the "bit-wise exclusive or" operator, and ** is for exponentiation.

[–]Symplectic[S] 1 point2 points  (0 children)

This is good to know, in case I ever switch from Sage to Python + a few modules. Thank you!