Preventing assignment isn't necessary for the internal workings of
SymPy. The only advantage it has is potentially avoiding confusion for
users. I don't particularly see users getting confused about this
though (although maybe that's because of the widespread use of
properties).

What I think is important is that any properties/attributes are
derivable from `.args` and not stored separately. Also I think it's
good to avoid using `.args` directly where possible so something like:

class MyThing(Basic):
    def __new__(cls, one, another):
        obj = Basic.__new__(cls, one, another)

    @property
    def one(self):
        return self.args[0]

    @property
    def another(self):
        return self.args[1]

    def get_something(self):
        return f(self.one)    # Using one rather than .args[0]

Then apart from the properties none of the other methods should
directly access args. This makes it possible to easily subclass
MyThing and have a different args layout in the subclass. This is
common when creating a singleton subclass:

class MyThingZero(MyThin, Singleton):
    def __new__(cls):
        return Basic.__new__(cls)

    @property
    def one(self):
        return 0

    @property
    def another(self):
        return oo

Here MyThingZero doesn't need to have .args because its a singleton so
they couldn't mean anything. MyThingZero can inherit all of the
methods of MyThing and only needs to redefine the accessor properties
because nothing else touches .args directly.

Sometimes it is desirable to cache some property of the object as an
attribute in __new__ but this should only done if it is something
derivable from .args and actually I think it's better to do the
caching in a property.

class Bad(Basic):
    def __new__(self, one, another):
        obj = Basic.__new__(cls, another)
        obj._one = one    # <---------------  don't do this

This is bad because the `_one` attribute is stored outside of .args.
Once you've done this you've interrupted the machinery of Basic and
you'll end up being tempted to override __eq__, __hash__, .args,
.func, etc and there's no way you'll get all the details right so
there will be subtle bugs and things you haven't thought of like
pickling will break. You can try to fix these things one at a time but
by the time you're finished you will have reimplemented a good chunk
of Basic.

It might be desirable to cache an object as an attribute to avoid
recomputing it. As long as it is derived from .args that is okay:

class Okay(Basic)
    def __new__(self, one, another):
        obj = Basic.__new__(cls, one, another)
        # This is derivable from args but expensive so we cache it
        obj._attribute = expensive_function(one)

    @property
    def attribute(self):
        return self._attribute

Better though is to put that caching inside the property so if we
don't need it then expensive_function is never called:

class Better(Basic):
    def __new__(self, one, another):
        obj = Basic.__new__(cls, one, another)

    @property
    def attribute(self):
        val = getattr(self, '_attribute', None)
        if val is not None:
            return val
        else:
            val = self._attribute = expensive_function(self.one)
            return val

I'm not sure if there is already a decorator that does this kind of
caching (it should be added if there isn't).

The examples of bad things I show above are all ones that I've seen in
SymPy's own codebase so please judge for yourself what is reasonable
rather than looking for precedents in the existing code. Currently the
codebase is inconsistent and there are a lot of Basic subclasses doing
dodgy things with attributes and args. I would like to clean that up
though.

Oscar

On Sun, 8 Sep 2019 at 11:49, Rybalka Denys <rybalka.de...@gmail.com> wrote:
>
> There are several ways to implement properties of sympy objects. The simplest 
> one is:
> ```
> def __new__(*args):
>     obj = Basic(*args)
>     obj.prop = 'some_property'
> ```
> A slightly more complicated one is:
> ```
> def __new__(*args):
>     obj = Basic(*args)
>     obj._prop = 'some_property'
>
> @property
> def prop(self):
>     return self._prop
> ```
>
> The first method is simpler and shorter, but the second one explicitly 
> forbids assignments `obj.prop = ...`. I have tried looking into various sympy 
> modules and the practices are different. Which implementation is preferable? 
> And on a more general topic, is there a "coding guidelines" webpage, where 
> issues like this should be resolved once and for all? Maybe it makes sense to 
> create one? I believe it will improve both the code and user experience.
>
> Best Regards,
> Denys Rybalka
>
> --
> You received this message because you are subscribed to the Google Groups 
> "sympy" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to sympy+unsubscr...@googlegroups.com.
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sympy/CAOsx_LqFFGo9wifF30QC8sT%3DYutaEcj6aYPNU7Tt154ZbPnD2w%40mail.gmail.com.

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sympy+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sympy/CAHVvXxQzUPZTEai%3DCh0Y%2BKN%2BRRRQRu8P0pwpk9JLxFJL64DEJw%40mail.gmail.com.

Reply via email to