policy concerning __classcall_private__ and __init__: who does the input checking

107 views
Skip to first unread message

Martin R

unread,
Jul 1, 2026, 4:32:21 AM (7 days ago) Jul 1
to sage-devel
Dear all,

is my understanding correct?

    in general, __classcall_private__ should do input validation and normalization, and call the _element_constructor_ of the correct parent, whereas __init__ should just do the object initialization.

For example, DyckWord does precisely that.  On the other hand, Core has almost identical __classcall_private__ and __init__:

    @staticmethod
    def __classcall_private__(cls, part, k):
...
        if isinstance(part, cls):
            return part
        part = Partition(part)
        if not part.is_core(k):
            raise ValueError("%s is not a %s-core" % (part, k))
        l = sum(part.k_boundary(k).row_lengths())
        return Cores(k, l)(part)

    def __init__(self, parent, core):
...
        k = parent.k
        part = Partition(core)
        if not part.is_core(k):
            raise ValueError("%s is not a %s-core" % (part, k))
        CombinatorialElement.__init__(self, parent, core)

Permutation does a mix of things, but the checking part can be switched off by passing check=False.

Martin

Antoine Leudière

unread,
Jul 1, 2026, 8:08:06 PM (6 days ago) Jul 1
to sage-devel
Very interested in an answer on this!

The way I understand it is "__init__ is the signature of the function, what it should be if it was purely living in the abstract math world", and "__classcall_private__ is the function you use to allow different signatures, flexibility of inputs, check inputs and feed proper inputs to __init__". That may be completely wrong though (in any case it is not very formal), and I believe __init__ should (to a reasonable extent) also check the input.

++
A.

Travis Scrimshaw

unread,
Jul 2, 2026, 5:12:20 AM (6 days ago) Jul 2
to sage-devel

It depends on how you expect the different parts to be called and where you want to gain speed and/or protection against bad user input. Hence, there is no policy on this (at least as far as I am aware).

As an example, you might need to normalize the input no matter what before you can figure out which parent to dispatch to, so it would make sense for most of that to be in the __classcall_private__, with some extra safeguards in _element_constructor_(). So by the time it gets to __init__, you know you have good inputs. On the other hand, you might have something that just always creates on generic parent (e.g., all Partitions()) and feed it to that because it doesn't make sense to try and make it more specific. To avoid code duplication (or at least narrow the breadth code), you then have the __init__ handle all the input validation.

Then again, there's also the fact that different people have (re)written different parts of the various combinatorial classes and have created a patchwork of different ideas...

Best,
Travis

Martin R

unread,
Jul 2, 2026, 7:50:19 AM (5 days ago) Jul 2
to sage-devel
I just checked, `Partition.__init__` does not do any input validation - it assumes that it receives a weakly decreasing list of integers, although it allows trailing 0's.  The actual input validation is done by `Partitions._element_constructor_`.

I think that it would pay to create a policy here, because this is confusing me even after 10 years of working with sage.

Most importantly, I would like to see whether there is a good reason to do input checking in `__init__`, if `__classcall_private__` exists.

A partial answer to this question is inheritance: for example, `Tableau` inherits from `ClonableList`, which does its input checking as part of its `__init__` provided `check=True`.

But the pattern looks unnecessarily complicated to me: the actual check is implemented in the tableau classes, but called by `ClonableList`.  The only difference to checking in `__classcall_private__` is that the object already exists when `check` is called.

Martin

Vincent Macri

unread,
Jul 3, 2026, 3:51:12 PM (4 days ago) Jul 3
to sage-...@googlegroups.com

I think whatever code path users construct objects through should be validated, and Sage library developers should be able to exclude validation from internal library code for the sake of speed/simplicity if it makes sense for them (ideally any assumptions are still mentioned in the docstrings though). With the Parent/Element framework this means validation is done by _element_constructor_ and not __init__. I think it can make sense for __init__ to perform normalization (or not) depending on the situation. To be bit more concrete, consider an implementation of the group of integer multiplication mod p (in this example normalization means "reduce an integer mod p").

Input should be validated when an element is created explicitly by a user (input must be coerceable to an integer, and then be converted to an Integer). If a user does a + b with a and b elements of the group, then _mul_ can call __init__ either with the unreduced product (so valid but not normalized) and __init__ can normalize it mod p, or _mul_ can perform the reduction mod p before calling __init__. Either of those I think would be reasonable, and which makes sense will depend on the specifics of the implementation. To keep the _mul_ method short, I would probably normalize in __init__ in this example, but if I were implementing the ring of integers mod p instead of just the multiplicative group I would normalize in the _add_ and _mul_ methods so that I could use a different reduction algorithm in each for speed (and also normalize in _element_constructor_).

I don't know if we need a strict policy for this, but some guidance in our developer guide could be helpful. I think the spirit of this guidance would be "users should not be able to break things using the public API (meaning they should get a clear error message if they try to do something that is not allowed), but no promises are made about what happens when internal functions/methods are called directly".

Vincent Macri (he/him)
On 2026-07-01 2:32 a.m., 'Martin R' via sage-devel wrote:
[△EXTERNAL]


--
You received this message because you are subscribed to the Google Groups "sage-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sage-devel+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/sage-devel/a6de5e8f-3b05-4e2f-8189-8164f873e1dbn%40googlegroups.com.

Travis Scrimshaw

unread,
12:30 AM (18 hours ago) 12:30 AM
to sage-devel
I don't see a benefit to creating a policy because we should allow the programmer to be flexible based on their circumstances. I also agree that (generally) user code pathways should validate input, but we need ways to skip that for library code for speed. However, it's also hard for us to check all possible inputs, and it's not unreasonable to avoid checking inputs too. So I'd prefer to leave things in the hands of the developer.

For ClonableList, it is a more general class, and the mechanism for check() is reflecting this. The base class doesn't know how to check things, but it provides the general code pathway to implement it.

Best,
Travis
Reply all
Reply to author
Forward
0 new messages