Le vendredi 28 septembre 2012 à 09:13 +0200, Joachim Durchholz a écrit :
> Hi all,
>
> I'm having some trouble with S and C and would like to find out the root
> cause, and which ways to deal with that are available.
>
> The basic situation is that S and C do not show their members in the
> source code, the members are created dynamically during import.
> This is a nifty usage of Python's dynamic features, but it's giving code
> analysis tools a really hard time.
> For example, for every occurrence of C.<whatever> in SymPy, Eclipse
> Pydev spits out a warning that C.<whatever> isn't present in class C.
> Last time I ran that check, that was a four-digit number of warnings.
>
> This in itself wouldn't be a problem. Pydev allows suppressing specific
> kinds of warnings.
> However, suppressing warnings can hide real problems. E.g. if a member
> of C is renamed/removed during refactoring, but there's still a
> reference to it somewhere inside SymPy, and unit tests happen to miss
> the situation that would trigger the error.
There is another problem: C is a flat, global namespace, which makes
name collisions likely. Worse, these collisions can happen behind the
scenes, causing spooky action at a distance - for instance:
>>> trigsimp(cos(x)**2 + sin(x)**2)
1
>>> c = Function('cos')
>>> trigsimp(cos(x)**2 + sin(x)**2)
sin(x)**2 + cos(x)**2
(what happened is that Function('cos') registered C.cos = c, but
trigsimp relies on C.cos == cos ...)
>
> For this reason, I'd like to eliminate the irrelevant warnings.
>
> There are two issues that are blocking me from moving forward with this:
>
> 1) I do not fully understand what C and S actually do.
> C says that it is needed to avoid problems with cyclic imports, but it
> does not describe what the problem exactly is, or how having C avoids
> those problems.
C is used to gain access to a class without importing the module where
it's defined - which prevents a cyclic import if that module imports the
current one. However, doing the import at function/module-level would
solve the issue just as well.
> For S, it's similar to C but not saying anything about cyclic imports,
> so I'm wondering if that's just not documented or whether S does indeed
> serve a different purpose than C. S' purpose also remains unclear - it's
> a singleton mechanism, I can see that easily, but if it's just an
> implementation vehicle, I don't understand why all singletons are made
> into members of S - the construction should rather be that S provides
> the singleton machinery and classes that need a singleton use a member
> that's a subclass of S.
S registers all singleton instances, but it's not an essential part of
the implementation of singletons. It should really be thought of as a
simple dictionary that happens to use the nicer attribute access syntax
"S.foo" instead of the usual item access syntax "S['foo']".
>
> 2) I'm not sure how C and S are being used "in the wild", so I don't
> know which kinds of publicly visible changes will break code of SymPy
> users. This makes it hard to decide how to fix the problem, or whether
> fixing it is even worth the potential breakage.
I suspect that C isn't used much, but S is probably used, due to the
existence of S() as a shortcut for sympify() and to the fact that S.One
looks like the nicest way of accessing the One() singleton.
In any case, changing or removing them would require a significant
deprecation period.
> Here's a short list of approaches that could be explored. I'd like to
> know what you people think about each:
>
> a) Turn S into a pure implementation vehicle, making S instances into
> hidden members of all classes that need a singleton.
> I think some initialization code populates S with singletons that are
> available to user code, so that would break a lot of user code. It would
> be the conceptually cleanest approach for S (if S was indeed intended to
> be just an implementation vehicle).
>
> b) Eliminate C, solve the import cycle problems in some other way (one
> could shortcut the second invocation of the import, or the cyclic
> dependency could be factored out into a separate module that's imported
> by all classes that are part of the cycle).
More simply, it should be the case that every use of C.* can be replaced
by a function-level import (because by the time 'C.Foo' is executed, Foo
must have been defined, which means that its module has been imported by
the interpreter already). Of course, it's better to move the import to
the top of the module whenever possible.
> This could break huge amounts of user code because people tend to use
> C.<whatever>. It would require changing all the call sites to C inside
> SymPy itself (something I'm willing to do so, sacrifices must be made).
Thanks for offering to do it. That would be very useful.
>
> C) Make sure each member exists as source code.
> E.g. extend C and S with a stub for each member that gets introduced by
> some other module. The stub just throws an exception that says "Please
> import module Foo to use C.bar" so that people get meaningful error
> messages if they forget to import something that should have populated C
> (or S).
> This adds another clerical task on anybody who wishes to extend C or S
> with a new member. On the other hand, it will give them a heads-up if
> they create a name conflict, or make them look whether what they're
> adding isn't already available under a slightly different name, so it
> can be argued that that's not just bad.
Let's not do that. Just not. Please!
> D) Leave everything as it is, declaring it's not worth it.
Given the problems with C, I think we should really do something about
it. Dealing with S is probably worth it as well, but less of a priority,
I think.