Skip to content

Fix logger verbose level not restored after unpickling - #169

Open
JayeshSuryavanshi wants to merge 1 commit into
scikit-learn-contrib:mainfrom
JayeshSuryavanshi:fix/logger-level-after-unpickle
Open

Fix logger verbose level not restored after unpickling#169
JayeshSuryavanshi wants to merge 1 commit into
scikit-learn-contrib:mainfrom
JayeshSuryavanshi:fix/logger-level-after-unpickle

Conversation

@JayeshSuryavanshi

Copy link
Copy Markdown

Problem

The logger verbose level is not restored after a HierarchicalClassifier is pickled and unpickled (issue #146).

_create_logger obtains the logger via logging.getLogger(self.classifier_abbreviation) (e.g. "LCPN"), which is a process-global singleton, and Python pickles a logging.Logger by name only (restoring it via getLogger(name), discarding the level). So on unpickle, model.verbose survives (a plain attribute) but model.logger_.getEffectiveLevel() does not, it inherits whatever level the current process's singleton happens to have. Because every instance of a classifier shares one named logger, any other instance or process that touched it determines the restored level.

Fix

Add __setstate__ to HierarchicalClassifier that recreates the logger from the restored self.verbose:

def __setstate__(self, state):
    self.__dict__.update(state)
    if "logger_" in state:
        self._create_logger()

The if "logger_" in state guard means only fitted models rebuild the logger. An unfitted model has no logger_, and creating one would add a trailing-underscore attribute that would make check_is_fitted wrongly report it as fitted; I verified an unpickled unfitted model still correctly raises NotFittedError.

Test

Added test_logger_level_preserved_after_pickle, parametrized over all three local classifiers: fit with verbose=logging.WARNING, pickle, poison the shared global logger to DEBUG, unpickle, then assert both verbose and logger_.getEffectiveLevel() are still WARNING. It fails on main (assert 10 == 30) and passes with the fix.

Closes #146

HierarchicalClassifier obtains its logger via logging.getLogger(name), a
process-global singleton that pickles by name only, so the verbose level set at
fit time is lost on unpickle: the restored logger inherits whatever level the
current process's singleton happens to have. Add __setstate__ to recreate the
logger from the restored self.verbose for fitted models (guarded on logger_
being present so unfitted models are unaffected). Add a regression test that
poisons the shared logger and asserts the level survives a pickle round-trip.

Closes scikit-learn-contrib#146
@JayeshSuryavanshi

Copy link
Copy Markdown
Author

Note on the failing docs/readthedocs.org check: it's the docs build, not the test suite, and it appears pre-existing rather than caused by this PR. This change only adds a __setstate__ plus a test; I verified the docs example that exercises pickling (plot_model_persistence.py) runs fine on this branch, and all gallery examples pass locally except the ray-based plot_parallel_training.py (which downloads a dataset) that looks like the flaky cause. Separately I opened #170 to fix a real numpy 2.5 incompatibility (np.cross on 2-D vectors in the Venn-Abers calibrator).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

the logger verbose level is not maintained after unpickling it

1 participant