Repository: ipython/traitlets (python)

Issue:
how to extend dynamic defaults with super
With traitlets 4.0, it's easy to extend dynamic defaults of parent classes, because the name of the default generator is part of the public API:

``` python
class SubClass(Parent):
    def _some_trait_default(self):
        previous_default = super()._trait_default()
        return modified(previous_default)
```

Now that the name of the method that happens to implement the default generator is no longer a public API, what's the best way to determine what the dynamic default _would have been_ in the parent class, and then extend it in a subclass?


Pull request description:
Create All Default Values With Generators
Closes #256 

``` python
class MyInt(Int):
    def make_dynamic_default(self):
        if self.default_value != 0:
            return self.default_value
        else:
            return 10

class A(HasTraits):
    i = MyInt()

class B(A):
    i = MyInt(20)

class C(B):
    @default('i')
    def _i_default(self):
        return 30

grandparent = super(B, C)
parent = super(C, C)
child = C

c = C()

print(
    grandparent._trait_default_generators['i'](c),
    parent._trait_default_generators['i'](c),
    child._trait_default_generators['i'](c)
)
```

```
10, 20, 30
```


Patch:
diff --git a/.travis.yml b/.travis.yml
index 32ad100..362ed0d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,7 @@ python:
 sudo: false
 install:
     - pip install .
-    - pip install traitlets[test] pytest-cov codecov pytest-warnings --upgrade
+    - pip install traitlets[test] pytest-cov codecov
 script:
     - py.test --cov traitlets -v traitlets
 after_success:
diff --git a/setup.cfg b/setup.cfg
index 5217e5d..3c6e79c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,2 @@
 [bdist_wheel]
 universal=1
-
-[tool:pytest]
-filterwarnings=default
diff --git a/traitlets/config/application.py b/traitlets/config/application.py
index 91a6f12..a3f790a 100644
--- a/traitlets/config/application.py
+++ b/traitlets/config/application.py
@@ -413,7 +413,7 @@ class Application(SingletonConfigurable):
         self.print_options()
 
         if classes:
-            help_classes = self._classes_with_config_traits()
+            help_classes = self._classes_in_config_sample()
             if help_classes:
                 print("Class parameters")
                 print("----------------")
@@ -565,10 +565,8 @@ class Application(SingletonConfigurable):
 
         # flatten flags&aliases, so cl-args get appropriate priority:
         flags,aliases = self.flatten_flags()
-        classes = tuple(self._classes_with_config_traits())
         loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
-                                        flags=flags, log=self.log,
-                                        classes=classes)
+                                        flags=flags, log=self.log)
         self.cli_config = deepcopy(loader.load_config())
         self.update_config(self.cli_config)
         # store unparsed args in extra_args
@@ -638,7 +636,7 @@ class Application(SingletonConfigurable):
         self.update_config(new_config)
 
 
-    def _classes_with_config_traits(self):
+    def _classes_in_config_sample(self):
         """
         Yields only classes with own traits, and their subclasses.
 
@@ -674,7 +672,7 @@ class Application(SingletonConfigurable):
         """generate default config file from Configurables"""
         lines = ["# Configuration file for %s." % self.name]
         lines.append('')
-        for cls in self._classes_with_config_traits():
+        for cls in self._classes_in_config_sample():
             lines.append(cls.class_config_section())
         return '\n'.join(lines)
 
diff --git a/traitlets/config/configurable.py b/traitlets/config/configurable.py
index 86171c6..7e480de 100644
--- a/traitlets/config/configurable.py
+++ b/traitlets/config/configurable.py
@@ -10,15 +10,7 @@ from copy import deepcopy
 import warnings
 
 from .loader import Config, LazyConfigValue, _is_section_key
-from traitlets.traitlets import (
-    HasTraits,
-    Instance,
-    Container,
-    Dict,
-    observe,
-    observe_compat,
-    default,
-)
+from traitlets.traitlets import HasTraits, Instance, observe, observe_compat, default
 from ipython_genutils.text import indent, dedent, wrap_paragraphs
 
 
@@ -120,7 +112,7 @@ class Configurable(HasTraits):
         If I am Bar and my parent is Foo, and their parent is Tim,
         this will return merge following config sections, in this order::
 
-            [Bar, Foo.bar, Tim.Foo.Bar]
+            [Bar, Foo.Bar, Tim.Foo.Bar]
 
         With the last item being the highest priority.
         """
@@ -235,20 +227,7 @@ class Configurable(HasTraits):
         """
         assert inst is None or isinstance(inst, cls)
         lines = []
-        header = "--%s.%s" % (cls.__name__, trait.name)
-        if isinstance(trait, (Container, Dict)):
-            multiplicity = trait.metadata.get('multiplicity', 'append')
-            if isinstance(trait, Dict):
-                sample_value = '<key-1>=<value-1>'
-            else:
-                sample_value = '<%s-item-1>' % trait.__class__.__name__.lower()
-            if multiplicity == 'append':
-                header = "%s=%s..." % (header, sample_value)
-            else:
-                header = "%s %s..." % (header, sample_value)
-        else:
-            header = '%s=<%s>' % (header, trait.__class__.__name__)
-        #header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
+        header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
         lines.append(header)
         if inst is not None:
             lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
diff --git a/traitlets/config/loader.py b/traitlets/config/loader.py
index 21dbed1..ecbb49c 100644
--- a/traitlets/config/loader.py
+++ b/traitlets/config/loader.py
@@ -6,7 +6,7 @@
 
 import argparse
 import copy
-import functools as fnt
+import logging
 import os
 import re
 import sys
@@ -17,7 +17,7 @@ from ipython_genutils.path import filefind
 from ipython_genutils import py3compat
 from ipython_genutils.encoding import DEFAULT_ENCODING
 from six import text_type
-from traitlets.traitlets import (HasTraits, Container, List, Dict, Any)
+from traitlets.traitlets import HasTraits, List, Any
 
 #-----------------------------------------------------------------------------
 # Exceptions
@@ -62,36 +62,36 @@ class ArgumentParser(argparse.ArgumentParser):
 
 class LazyConfigValue(HasTraits):
     """Proxy object for exposing methods on configurable containers
-
+    
     Exposes:
-
+    
     - append, extend, insert on lists
     - update on dicts
     - update, add on sets
     """
-
+    
     _value = None
-
+    
     # list methods
     _extend = List()
     _prepend = List()
-
+    
     def append(self, obj):
         self._extend.append(obj)
-
+    
     def extend(self, other):
         self._extend.extend(other)
-
+    
     def prepend(self, other):
         """like list.extend, but for the front"""
         self._prepend[:0] = other
-
+    
     _inserts = List()
     def insert(self, index, other):
         if not isinstance(index, int):
             raise TypeError("An integer is required")
         self._inserts.append((index, other))
-
+    
     # dict methods
     # update is used for both dict and set
     _update = Any()
@@ -102,14 +102,14 @@ class LazyConfigValue(HasTraits):
             else:
                 self._update = set()
         self._update.update(other)
-
+    
     # set methods
     def add(self, obj):
         self.update({obj})
-
+    
     def get_value(self, initial):
         """construct the value from the initial one
-
+        
         after applying any insert / extend / update changes
         """
         if self._value is not None:
@@ -120,7 +120,7 @@ class LazyConfigValue(HasTraits):
                 value.insert(idx, obj)
             value[:0] = self._prepend
             value.extend(self._extend)
-
+        
         elif isinstance(value, dict):
             if self._update:
                 value.update(self._update)
@@ -129,10 +129,10 @@ class LazyConfigValue(HasTraits):
                 value.update(self._update)
         self._value = value
         return value
-
+    
     def to_dict(self):
         """return JSONable dict form of my data
-
+        
         Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples.
         """
         d = {}
@@ -146,12 +146,6 @@ class LazyConfigValue(HasTraits):
             d['inserts'] = self._inserts
         return d
 
-    def __repr__(self):
-        if self._value is not None:
-            return "<%s value=%r>" % (self.__class__.__name__, self._value)
-        else:
-            return "<%s %r>" % (self.__class__.__name__, self.to_dict())
-
 
 def _is_section_key(key):
     """Is a Config key a section name (does it start with a capital)?"""
@@ -167,10 +161,10 @@ class Config(dict):
     def __init__(self, *args, **kwds):
         dict.__init__(self, *args, **kwds)
         self._ensure_subconfig()
-
+    
     def _ensure_subconfig(self):
         """ensure that sub-dicts that should be Config objects are
-
+        
         casts dicts that are under section keys to Config objects,
         which is necessary for constructing Config objects from dict literals.
         """
@@ -180,11 +174,11 @@ class Config(dict):
                     and isinstance(obj, dict) \
                     and not isinstance(obj, Config):
                 setattr(self, key, Config(obj))
-
+    
     def _merge(self, other):
         """deprecated alias, use Config.merge()"""
         self.merge(other)
-
+    
     def merge(self, other):
         """merge another config object into this one"""
         to_update = {}
@@ -200,13 +194,13 @@ class Config(dict):
                     to_update[k] = v
 
         self.update(to_update)
-
+    
     def collisions(self, other):
         """Check for collisions between two config objects.
-
+        
         Returns a dict of the form {"Class": {"trait": "collision message"}}`,
         indicating which values have been ignored.
-
+        
         An empty dict indicates no collisions.
         """
         collisions = {}
@@ -220,7 +214,7 @@ class Config(dict):
                     collisions.setdefault(section, {})
                     collisions[section][key] = "%r ignored, using %r" % (mine[key], theirs[key])
         return collisions
-
+    
     def __contains__(self, key):
         # allow nested contains of the form `"Section.key" in config`
         if '.' in key:
@@ -228,15 +222,15 @@ class Config(dict):
             if first not in self:
                 return False
             return remainder in self[first]
-
+        
         return super(Config, self).__contains__(key)
-
+    
     # .has_key is deprecated for dictionaries.
     has_key = __contains__
-
+    
     def _has_section(self, key):
         return _is_section_key(key) and key in self
-
+    
     def copy(self):
         return type(self)(dict.copy(self))
 
@@ -254,7 +248,7 @@ class Config(dict):
                 value = copy.copy(value)
             new_config[key] = value
         return new_config
-
+    
     def __getitem__(self, key):
         try:
             return dict.__getitem__(self, key)
@@ -462,12 +456,12 @@ class PyFileConfigLoader(FileConfigLoader):
             raise ConfigFileNotFound(str(e))
         self._read_file_as_dict()
         return self.config
-
+    
     def load_subconfig(self, fname, path=None):
         """Injected into config file namespace as load_subconfig"""
         if path is None:
             path = self.path
-
+        
         loader = self.__class__(fname, path)
         try:
             sub_config = loader.load_config()
@@ -477,13 +471,13 @@ class PyFileConfigLoader(FileConfigLoader):
             pass
         else:
             self.config.merge(sub_config)
-
+    
     def _read_file_as_dict(self):
         """Load the config file into self.config, with recursive loading."""
         def get_config():
             """Unnecessary now, but a deprecation warning is more trouble than it's worth."""
             return self.config
-
+        
         namespace = dict(
             c=self.config,
             load_subconfig=self.load_subconfig,
@@ -502,8 +496,14 @@ class CommandLineConfigLoader(ConfigLoader):
     here.
     """
 
-    def _parse_config_value(self, rhs):
-        """Python-evaluates any cmd-line argument values."""
+    def _exec_config_str(self, lhs, rhs):
+        """execute self.config.<lhs> = <rhs>
+        
+        * expands ~ with expanduser
+        * tries to assign with literal_eval, otherwise assigns with just the string,
+          allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent.  *Not*
+          equivalent are `--C.a=4` and `--C.a='4'`.
+        """
         rhs = os.path.expanduser(rhs)
         try:
             # Try to see if regular Python syntax will work. This
@@ -513,25 +513,8 @@ class CommandLineConfigLoader(ConfigLoader):
         except (NameError, SyntaxError, ValueError):
             # This case happens if the rhs is a string.
             value = rhs
-        return value
-
-    def _exec_config_str(self, lhs, rhs, trait=None):
-        """execute self.config.<lhs> = <rhs>
-
-        * expands ~ with expanduser
-        * tries to assign with literal_eval, otherwise assigns with just the string,
-          allowing `--C.a=foobar` and `--C.a="foobar"` to be equivalent.  *Not*
-          equivalent are `--C.a=4` and `--C.a='4'`.
-        """
-        if isinstance(trait, Dict):
-            value = {r[0]: self._parse_config_value(r[1])
-                                for r in rhs}
-        elif isinstance(rhs, (list, tuple)):
-            value = [self._parse_config_value(r) for r in rhs]
-        else:
-            value = self._parse_config_value(rhs)
 
-        exec(u'self.config.%s = value' % lhs, None, locals())
+        exec(u'self.config.%s = value' % lhs)
 
     def _load_flag(self, cfg):
         """update self.config from a flag, which can be a dict or Config"""
@@ -705,17 +688,12 @@ class KeyValueConfigLoader(CommandLineConfigLoader):
 class ArgParseConfigLoader(CommandLineConfigLoader):
     """A loader that uses the argparse module to load from the command line."""
 
-    def __init__(self, argv=None, aliases=None, flags=None, log=None, classes=None,
-                 *parser_args, **parser_kw):
+    def __init__(self, argv=None, aliases=None, flags=None, log=None,  *parser_args, **parser_kw):
         """Create a config loader for use with argparse.
 
         Parameters
         ----------
 
-        classes : optional, list
-          The classes to scan for *container* config-traits and decide
-          for their "multiplicity" when adding them as *argparse* arguments.
-
         argv : optional, list
           If given, used to read command-line arguments from, otherwise
           sys.argv[1:] is used.
@@ -740,7 +718,6 @@ class ArgParseConfigLoader(CommandLineConfigLoader):
         self.argv = argv
         self.aliases = aliases or {}
         self.flags = flags or {}
-        self.classes = classes or ()
 
         self.parser_args = parser_args
         self.version = parser_kw.pop("version", None)
@@ -748,7 +725,7 @@ class ArgParseConfigLoader(CommandLineConfigLoader):
         kwargs.update(parser_kw)
         self.parser_kw = kwargs
 
-    def load_config(self, argv=None, aliases=None, flags=None, classes=None):
+    def load_config(self, argv=None, aliases=None, flags=None):
         """Parse command line arguments and return as a Config object.
 
         Parameters
@@ -761,7 +738,11 @@ class ArgParseConfigLoader(CommandLineConfigLoader):
         self.clear()
         if argv is None:
             argv = self.argv
-        self._create_parser(aliases, flags, classes)
+        if aliases is None:
+            aliases = self.aliases
+        if flags is None:
+            flags = self.flags
+        self._create_parser(aliases, flags)
         self._parse_args(argv)
         self._convert_to_config()
         return self.config
@@ -772,16 +753,11 @@ class ArgParseConfigLoader(CommandLineConfigLoader):
         else:
             return []
 
-    def _create_parser(self, aliases=None, flags=None, classes=None):
+    def _create_parser(self, aliases=None, flags=None):
         self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
-        self._add_arguments(aliases, flags, classes)
+        self._add_arguments(aliases, flags)
 
-    def _parse_config_traits(self):
-        for cls in self.classes:
-            for trait, traitname in cls.class_traits(config=True).items():
-                yield ()
-
-    def _add_arguments(self, aliases=None, flags=None, classes=None):
+    def _add_arguments(self, aliases=None, flags=None):
         raise NotImplementedError("subclasses must implement _add_arguments")
 
     def _parse_args(self, args):
@@ -796,93 +772,45 @@ class ArgParseConfigLoader(CommandLineConfigLoader):
         for k, v in vars(self.parsed_data).items():
             exec("self.config.%s = v"%k, locals(), globals())
 
-## For Dict traits, describes the cmd-line option as `key=value`
-_kv_opt_pattern = re.compile(r'^([^=]+)=(.*)$')
-
-def _kv_opt(traitname, opt_value):
-    """
-    Used as `type` when adding args into :meth:`ArgumentParser.add_argument()`
-    corresponding to config Dict-traits.
-    """
-    m = _kv_opt_pattern.match(opt_value)
-    if not m:
-        raise ArgumentError("Expecting <key>=<value> for Dict-trait '%s', got %r!"
-                            % (traitname, opt_value))
-    return m.groups()
-
-
 class KVArgParseConfigLoader(ArgParseConfigLoader):
     """A config loader that loads aliases and flags with argparse,
     but will use KVLoader for the rest.  This allows better parsing
     of common args, such as `ipython -c 'print 5'`, but still gets
     arbitrary config with `ipython --InteractiveShell.autoindent=False`"""
 
-    def _add_arguments(self, aliases=None, flags=None, classes=None):
+    def _add_arguments(self, aliases=None, flags=None):
         self.alias_flags = {}
         # print aliases, flags
         if aliases is None:
             aliases = self.aliases
         if flags is None:
             flags = self.flags
-        if classes is None:
-            classes = self.classes
         paa = self.parser.add_argument
-
-        ## An index of all container traits collected::
-        #
-        #     { <traitname>: (<trait>, <argparse-kwds>) }
-        #
-        #  Used to add the correct type into the `config` tree.
-        #  Used also for aliases, not to re-collect them.
-        self.argparse_traits = argparse_traits = {}
-        for cls in classes:
-            for traitname, trait in cls.class_traits(config=True).items():
-                argname = '%s.%s' % (cls.__name__, traitname)
-                argparse_kwds = {'type': text_type}
-                if isinstance(trait, (Container, Dict)):
-                    multiplicity = trait.metadata.get('multiplicity', 'append')
-                    if multiplicity == 'append':
-                        argparse_kwds['action'] = multiplicity
-                    else:
-                        argparse_kwds['nargs'] = multiplicity
-                    if isinstance(trait, Dict):
-                        argparse_kwds['type'] = fnt.partial(_kv_opt, traitname)
-                argparse_traits[argname] = (trait, argparse_kwds)
-                paa('--'+argname, **argparse_kwds)
-
-        for keys, traitname in aliases.items():
+        for keys,value in aliases.items():
             if not isinstance(keys, tuple):
                 keys = (keys, )
             for key in keys:
-                argparse_kwds = {'type': text_type, 'dest': traitname}
-                ## Is alias for a sequence-trait?
-                #
-                if traitname in argparse_traits:
-                    argparse_kwds.update(argparse_traits[traitname][1])
-                    if 'action' in argparse_kwds:
-                        ## A flag+alias should have `nargs='?'` multiplicity,
-                        #  but base config-property had 'append' multiplicity!
-                        #
-                        if key in flags:
-                            raise ArgumentError(
-                                "The alias `%s` for the 'append' sequence "
-                                "config-trait `%s` cannot be also a flag!'"
-                                % (key, traitname))
+                if key in flags:
+                    # flags
+                    nargs = '?'
                 else:
-                    if key in flags:
-                        argparse_kwds['nargs'] = '?'
-                keys = ('-'+key, '--'+key) if len(key) is 1 else ('--'+key, )
-                paa(*keys, **argparse_kwds)
-
-        for keys, (value, _) in flags.items():
+                    nargs = None
+                if len(key) is 1:
+                    paa('-'+key, '--'+key, type=text_type, dest=value, nargs=nargs)
+                else:
+                    paa('--'+key, type=text_type, dest=value, nargs=nargs)
+        for keys, (value, help) in flags.items():
             if not isinstance(keys, tuple):
                 keys = (keys, )
             for key in keys:
                 if key in self.aliases:
+                    #
                     self.alias_flags[self.aliases[key]] = value
                     continue
-                keys = ('-'+key, '--'+key) if len(key) is 1 else ('--'+key, )
-                paa(*keys, action='append_const', dest='_flags', const=value)
+                if len(key) is 1:
+                    paa('-'+key, '--'+key, action='append_const', dest='_flags', const=value)
+                else:
+                    paa('--'+key, action='append_const', dest='_flags', const=value)
 
     def _convert_to_config(self):
         """self.parsed_data->self.config, parse unrecognized extra args via KVLoader."""
@@ -898,11 +826,8 @@ class KVArgParseConfigLoader(ArgParseConfigLoader):
                 # it was a flag that shares the name of an alias
                 subcs.append(self.alias_flags[k])
             else:
-                trait = self.argparse_traits.get(k)
-                if trait:
-                    trait = trait[0]
                 # eval the KV assignment
-                self._exec_config_str(k, v, trait=trait)
+                self._exec_config_str(k, v)
 
         for subc in subcs:
             self._load_flag(subc)
diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py
index 3f0a894..54b8dd3 100644
--- a/traitlets/traitlets.py
+++ b/traitlets/traitlets.py
@@ -393,6 +393,9 @@ class BaseDescriptor(object):
         self.this_class = cls
         self.name = name
 
+    def subclass_init(self, cls):
+        pass
+
     def instance_init(self, obj):
         """Part of the initialization which may depend on the underlying
         HasDescriptors instance.
@@ -416,6 +419,23 @@ class TraitType(BaseDescriptor):
     read_only = False
     info_text = 'any value'
 
+    def class_init(self, cls, name):
+        super(TraitType, self).class_init(cls, name)
+        if self.name not in cls._trait_default_generators:
+            if hasattr(self, 'make_dynamic_default'):
+                cls._trait_default_generators[self.name] = (
+                    lambda obj: self.make_dynamic_default())
+            elif self.default_value is not Undefined:
+                cls._trait_default_generators[self.name] = (
+                    lambda obj : self.default_value)
+
+    def subclass_init(self, cls):
+        if '_%s_default' % self.name in cls.__dict__:
+            method = getattr(cls, '_%s_default' % self.name)
+            _deprecated_method(method, cls, '_%s_default' % self.name,
+                "use @default decorator instead.")
+            cls._trait_default_generators[self.name] = method
+
     def __init__(self, default_value=Undefined, allow_none=False, read_only=None, help=None, **kwargs):
         """Declare a traitlet.
 
@@ -480,57 +500,18 @@ class TraitType(BaseDescriptor):
         obj._trait_values[self.name] = value
         return value
 
-    def _dynamic_default_callable(self, obj):
-        """Retrieve a callable to calculate the default for this traitlet.
-
-        This looks for:
-
-        * default generators registered with the @default descriptor.
-        * obj._{name}_default() on the class with the traitlet, or a subclass
-          that obj belongs to.
-        * trait.make_dynamic_default, which is defined by Instance
-
-        If neither exist, it returns None
-        """
-        # Traitlets without a name are not on the instance, e.g. in List or Union
-        if self.name:
-
-            # Only look for default handlers in classes derived from self.this_class.
-            mro = type(obj).mro()
-            meth_name = '_%s_default' % self.name
-            for cls in mro[:mro.index(self.this_class) + 1]:
-                if hasattr(cls, '_trait_default_generators'):
-                    default_handler = cls._trait_default_generators.get(self.name)
-                    if default_handler is not None and default_handler.this_class == cls:
-                        return types.MethodType(default_handler.func, obj)
-
-                if meth_name in cls.__dict__:
-                    method = getattr(obj, meth_name)
-                    _deprecated_method(method, cls, meth_name, "use @default decorator instead.")
-                    return method
-
-        return getattr(self, 'make_dynamic_default', None)
-
-    def instance_init(self, obj):
-        # If no dynamic initialiser is present, and the trait implementation or
-        # use provides a static default, transfer that to obj._trait_values.
-        with obj.cross_validation_lock:
-            if (self._dynamic_default_callable(obj) is None) \
-                    and (self.default_value is not Undefined):
-                v = self._validate(obj, self.default_value)
-                if self.name is not None:
-                    obj._trait_values[self.name] = v
-
     def get(self, obj, cls=None):
         try:
             value = obj._trait_values[self.name]
         except KeyError:
             # Check for a dynamic initializer.
-            dynamic_default = self._dynamic_default_callable(obj)
-            if dynamic_default is None:
-                raise TraitError("No default value found for %s trait of %r"
-                                 % (self.name, obj))
-            value = self._validate(obj, dynamic_default())
+            try:
+                dgen = cls._trait_default_generators[self.name]
+            except:
+                raise TraitError("No default value found for "
+                    "the '%s' trait named '%s' of %r" % (
+                    type(self).__name__, self.name, obj))
+            value = self._validate(obj, dgen(obj))
             obj._trait_values[self.name] = value
             return value
         except Exception:
@@ -748,6 +729,10 @@ class MetaHasDescriptors(type):
             if isinstance(v, BaseDescriptor):
                 v.class_init(cls, k)
 
+        for k, v in getmembers(cls):
+            if isinstance(v, BaseDescriptor):
+                v.subclass_init(cls)
+
 
 class MetaHasTraits(MetaHasDescriptors):
     """A metaclass for HasTraits."""
@@ -755,6 +740,13 @@ class MetaHasTraits(MetaHasDescriptors):
     def setup_class(cls, classdict):
         cls._trait_default_generators = {}
         super(MetaHasTraits, cls).setup_class(classdict)
+        new = {}
+        for c in reversed(cls.mro()):
+            if hasattr(c, "_trait_default_generators"):
+                new.update(c._trait_default_generators)
+        cls._trait_default_generators = new
+
+
 
 
 def observe(*names, **kwargs):
@@ -1398,6 +1390,28 @@ class HasTraits(six.with_metaclass(MetaHasTraits, HasDescriptors)):
         """Returns True if the object has a trait with the specified name."""
         return isinstance(getattr(self.__class__, name, None), TraitType)
 
+    def trait_defaults(self, *names, **metadata):
+        """Return a trait's default value or a dictionary of them
+
+        Notes
+        -----
+        Dynamically generated default values may
+        depend on the current state of the object."""
+        if len(names) == 1 and len(metadata) == 0:
+            return self._trait_default_generators[names[0]](self)
+
+        for n in names:
+            if not has_trait(self, n):
+                raise TraitError("'%s' is not a trait of '%s' "
+                    "instances" % (n, type(self).__name__))
+        trait_names = self.trait_names(**metadata)
+        trait_names.extend(names)
+
+        defaults = {}
+        for n in trait_names:
+            defaults[n] = self._trait_default_generators[n](self)
+        return defaults
+
     def trait_names(self, **metadata):
         """Get a list of all the names of this class' traits."""
         return list(self.traits(**metadata))
@@ -2686,19 +2700,3 @@ class UseEnum(TraitType):
         if self.allow_none:
             return result + " or None"
         return result
-
-class Callable(TraitType):
-    """A trait which is callable.
-
-    Notes
-    -----
-    Classes are callable, as are instances
-    with a __call__() method."""
-
-    info_text = 'a callable'
-
-    def validate(self, obj, value):
-        if six.callable(value):
-            return value
-        else:
-            self.error(obj, value)


Test patch:
diff --git a/traitlets/config/tests/test_application.py b/traitlets/config/tests/test_application.py
index f9efe5c..dcc3c2f 100644
--- a/traitlets/config/tests/test_application.py
+++ b/traitlets/config/tests/test_application.py
@@ -13,7 +13,7 @@ import logging
 import os
 import sys
 from io import StringIO
-from unittest import TestCase, skip
+from unittest import TestCase
 
 try:
     from unittest import mock
@@ -34,7 +34,7 @@ from traitlets.config.application import (
 
 from ipython_genutils.tempdir import TemporaryDirectory
 from traitlets.traitlets import (
-    Bool, Unicode, Integer, List, Tuple, Set, Dict
+    Bool, Unicode, Integer, List, Dict
 )
 
 class Foo(Configurable):
@@ -42,16 +42,12 @@ class Foo(Configurable):
     i = Integer(0, help="The integer i.").tag(config=True)
     j = Integer(1, help="The integer j.").tag(config=True)
     name = Unicode(u'Brian', help="First name.").tag(config=True)
-    la = List([]).tag(config=True)
-    fdict = Dict().tag(config=True, multiplicity='+')
+
 
 class Bar(Configurable):
 
     b = Integer(0, help="The integer b.").tag(config=True)
     enabled = Bool(True, help="Enable bar.").tag(config=True)
-    tb = Tuple(()).tag(config=True, multiplicity='*')
-    aset = Set().tag(config=True, multiplicity='+')
-    bdict = Dict().tag(config=True)
 
 
 class MyApp(Application):
@@ -68,9 +64,6 @@ class MyApp(Application):
                     ('fooi', 'i') : 'Foo.i',
                     ('j', 'fooj') : 'Foo.j',
                     'name' : 'Foo.name',
-                    'la': 'Foo.la',
-                    'tb': 'Bar.tb',
-                    'D': 'Bar.bdict',
                     'enabled' : 'Bar.enabled',
                     'log-level' : 'Application.log_level',
                 })
@@ -122,35 +115,6 @@ class TestApplication(TestCase):
         self.assertEqual(config.Bar.enabled, False)
         self.assertEqual(config.MyApp.log_level,50)
 
-    def test_config_seq_args(self):
-        app = MyApp()
-        app.parse_command_line("--la 1 --tb AB 2 --Foo.la=ab --Bar.aset S1 S2 S1".split())
-        config = app.config
-        self.assertEqual(config.Foo.la, [1, 'ab'])
-        self.assertEqual(config.Bar.tb, ['AB', 2])
-        self.assertEqual(config.Bar.aset, 'S1 S2 S1'.split())
-        app.init_foo()
-        self.assertEqual(app.foo.la, [1, 'ab'])
-        app.init_bar()
-        self.assertEqual(app.bar.aset, {'S1', 'S2'})
-        self.assertEqual(app.bar.tb, ('AB', 2))
-
-    def test_config_dict_args(self):
-        app = MyApp()
-        app.parse_command_line(
-            "--Foo.fdict a=1 b=b c=3 "
-            "--Bar.bdict k=1 -D=a=b -D 22=33 "
-            .split())
-        fdict = {'a': 1, 'b': 'b', 'c': 3}
-        bdict = {'k': 1, 'a': 'b', '22': 33}
-        config = app.config
-        self.assertDictEqual(config.Foo.fdict, fdict)
-        self.assertDictEqual(config.Bar.bdict, bdict)
-        app.init_foo()
-        self.assertEqual(app.foo.fdict, fdict)
-        app.init_bar()
-        self.assertEqual(app.bar.bdict, bdict)
-
     def test_config_propagation(self):
         app = MyApp()
         app.parse_command_line(["--i=10","--Foo.j=10","--enabled=False","--log-level=50"])
@@ -472,4 +436,4 @@ def test_help_output():
 
 if __name__ == '__main__':
     # for test_help_output:
-    MyApp.launch_instance()
+    MyApp.launch_instance()
\ No newline at end of file
diff --git a/traitlets/config/tests/test_configurable.py b/traitlets/config/tests/test_configurable.py
index f94eab4..a2e31cd 100644
--- a/traitlets/config/tests/test_configurable.py
+++ b/traitlets/config/tests/test_configurable.py
@@ -5,7 +5,6 @@
 # Distributed under the terms of the Modified BSD License.
 
 import logging
-import sys
 from unittest import TestCase
 
 from pytest import mark
@@ -58,44 +57,11 @@ if PY3:
 class Foo(Configurable):
     a = Integer(0, help="The integer a.").tag(config=True)
     b = Unicode('nope').tag(config=True)
-    flist = List([]).tag(config=True)
-    fdict = Dict().tag(config=True)
 
 
 class Bar(Foo):
     b = Unicode('gotit', help="The string b.").tag(config=False)
     c = Float(help="The string c.").tag(config=True)
-    bset = Set([]).tag(config=True, multiplicity='+')
-    bdict = Dict().tag(config=True, multiplicity='+')
-
-foo_help=u"""Foo(Configurable) options
--------------------------
---Foo.a=<Int>
-    Default: 0
-    The integer a.
---Foo.b=<Unicode>
-    Default: 'nope'
---Foo.fdict=<key-1>=<value-1>...
-    Default: {}
---Foo.flist=<list-item-1>...
-    Default: []"""
-
-bar_help=u"""Bar(Foo) options
-----------------
---Bar.a=<Int>
-    Default: 0
-    The integer a.
---Bar.bdict <key-1>=<value-1>...
-    Default: {}
---Bar.bset <set-item-1>...
-    Default: set()
---Bar.c=<Float>
-    Default: 0.0
-    The string c.
---Bar.fdict=<key-1>=<value-1>...
-    Default: {}
---Bar.flist=<list-item-1>...
-    Default: []"""
 
 
 class TestConfigurable(TestCase):
@@ -174,12 +140,8 @@ class TestConfigurable(TestCase):
         self.assertEqual(c.b, 'and')
         self.assertEqual(c.c, 20.0)
 
-    @mark.skipif(sys.version_info < (3, ),
-                 reason="Set, Int in py2 get printed as 'set([])', 'Integer'!'")
     def test_help(self):
         self.assertEqual(MyConfigurable.class_get_help(), mc_help)
-        self.assertEqual(Foo.class_get_help(), foo_help)
-        self.assertEqual(Bar.class_get_help(), bar_help)
 
     def test_help_inst(self):
         inst = MyConfigurable(a=5, b=4)
@@ -202,6 +164,7 @@ class TestSingletonConfigurable(TestCase):
         self.assertEqual(Bar.initialized(), False)
         self.assertEqual(Bam.initialized(), False)
         bam = Bam.instance()
+        bam == Bar.instance()
         self.assertEqual(Bar.initialized(), True)
         self.assertEqual(Bam.initialized(), True)
         self.assertEqual(bam, Bam._instance)
@@ -439,7 +402,7 @@ class TestConfigContainers(TestCase):
 
         # reset deprecation limiter
         _deprecations_shown.clear()
-        with expected_warnings(['metadata should be set using the \.tag\(\) method']):
+        with expected_warnings(['metadata should be set using the \.tag\(\) method', "use @default decorator instead\\."]):
             class DefaultConfigurable(Configurable):
                 a = Integer(config=True)
                 def _config_default(self):
@@ -472,7 +435,7 @@ class TestLogger(TestCase):
         logger = logging.getLogger('test_warn_match')
         cfg = Config({'A': {'bat': 5}})
         with self.assertLogs(logger, logging.WARNING) as captured:
-            TestLogger.A(config=cfg, log=logger)
+            a = TestLogger.A(config=cfg, log=logger)
 
         output = '\n'.join(captured.output)
         self.assertIn('Did you mean one of: `bar, baz`?', output)
@@ -480,7 +443,7 @@ class TestLogger(TestCase):
 
         cfg = Config({'A': {'fool': 5}})
         with self.assertLogs(logger, logging.WARNING) as captured:
-            TestLogger.A(config=cfg, log=logger)
+            a = TestLogger.A(config=cfg, log=logger)
 
         output = '\n'.join(captured.output)
         self.assertIn('Config option `fool` not recognized by `A`.', output)
@@ -488,7 +451,7 @@ class TestLogger(TestCase):
 
         cfg = Config({'A': {'totally_wrong': 5}})
         with self.assertLogs(logger, logging.WARNING) as captured:
-            TestLogger.A(config=cfg, log=logger)
+            a = TestLogger.A(config=cfg, log=logger)
 
         output = '\n'.join(captured.output)
         self.assertIn('Config option `totally_wrong` not recognized by `A`.', output)
diff --git a/traitlets/config/tests/test_loader.py b/traitlets/config/tests/test_loader.py
index bfca88f..50c8659 100644
--- a/traitlets/config/tests/test_loader.py
+++ b/traitlets/config/tests/test_loader.py
@@ -5,6 +5,7 @@
 # Distributed under the terms of the Modified BSD License.
 
 import copy
+import logging
 import os
 import pickle
 import sys
@@ -23,8 +24,6 @@ from traitlets.config.loader import (
     KVArgParseConfigLoader,
     ConfigError,
 )
-from traitlets import (HasTraits, Union, List, Tuple, Dict, Int, Unicode)
-from traitlets.config import Configurable
 
 
 pyfile = """
@@ -122,14 +121,14 @@ class TestFileCL(TestCase):
         f = os.fdopen(fd, 'w')
         f.write('{}')
         f.close()
-
+        
         with JSONFileConfigLoader(fname, log=log) as config:
             config.A.b = 1
-
+        
         with self.assertRaises(TypeError):
             with JSONFileConfigLoader(fname, log=log) as config:
                 config.A.cant_json = lambda x: x
-
+        
         loader = JSONFileConfigLoader(fname, log=log)
         cfg = loader.load_config()
         assert cfg.A.b == 1
@@ -173,24 +172,17 @@ class TestFileCL(TestCase):
         with self.assertRaises(ValueError):
             cl.load_config()
 
-def _parse_int_or_str(v):
-    try:
-        return int(v)
-    except:
-        return str(v)
 
 class MyLoader1(ArgParseConfigLoader):
-    def _add_arguments(self, aliases=None, flags=None, classes=None):
+    def _add_arguments(self, aliases=None, flags=None):
         p = self.parser
         p.add_argument('-f', '--foo', dest='Global.foo', type=str)
         p.add_argument('-b', dest='MyClass.bar', type=int)
         p.add_argument('-n', dest='n', action='store_true')
         p.add_argument('Global.bam', type=str)
-        p.add_argument('--list1', action='append', type=_parse_int_or_str)
-        p.add_argument('--list2', nargs='+', type=int)
 
 class MyLoader2(ArgParseConfigLoader):
-    def _add_arguments(self, aliases=None, flags=None, classes=None):
+    def _add_arguments(self, aliases=None, flags=None):
         subparsers = self.parser.add_subparsers(dest='subparser_name')
         subparser1 = subparsers.add_parser('1')
         subparser1.add_argument('-x',dest='Global.x')
@@ -228,14 +220,6 @@ class TestArgParseCL(TestCase):
         self.assertEqual(config.n, True)
         self.assertEqual(config.Global.bam, 'wow')
 
-    def test_list_args(self):
-        cl = MyLoader1()
-        config = cl.load_config('--list1 1 wow --list2 1 2 3 --list1 B'.split())
-        self.assertEqual(list(config.Global.keys()), ['bam'])
-        self.assertEqual(config.Global.bam, 'wow')
-        self.assertEqual(config.list1, [1, 'B'])
-        self.assertEqual(config.list2, [1, 2, 3])
-
 
 class TestKeyValueCL(TestCase):
     klass = KeyValueConfigLoader
@@ -258,7 +242,7 @@ class TestKeyValueCL(TestCase):
         # non-literal expressions are not evaluated
         self.assertEqual(config.Foo.Bam.value, 'list(range(10))')
         self.assertEqual(config.D.C.value, 'hi there')
-
+    
     def test_expanduser(self):
         cl = self.klass(log=log)
         argv = ['--a=~/1/2/3', '--b=~', '--c=~/', '--d="~/"']
@@ -267,7 +251,7 @@ class TestKeyValueCL(TestCase):
         self.assertEqual(config.b, os.path.expanduser('~'))
         self.assertEqual(config.c, os.path.expanduser('~/'))
         self.assertEqual(config.d, '~/')
-
+    
     def test_extra_args(self):
         cl = self.klass(log=log)
         config = cl.load_config(['--a=5', 'b', '--c=10', 'd'])
@@ -276,24 +260,24 @@ class TestKeyValueCL(TestCase):
         self.assertEqual(config.c, 10)
         config = cl.load_config(['--', '--a=5', '--c=10'])
         self.assertEqual(cl.extra_args, ['--a=5', '--c=10'])
-
+    
     def test_unicode_args(self):
         cl = self.klass(log=log)
         argv = [u'--a=épsîlön']
         config = cl.load_config(argv)
         self.assertEqual(config.a, u'épsîlön')
-
+    
     def test_unicode_bytes_args(self):
         uarg = u'--a=é'
         try:
             barg = uarg.encode(sys.stdin.encoding)
         except (TypeError, UnicodeEncodeError):
             raise skip("sys.stdin.encoding can't handle 'é'")
-
+        
         cl = self.klass(log=log)
         config = cl.load_config([barg])
         self.assertEqual(config.a, u'é')
-
+    
     def test_unicode_alias(self):
         cl = self.klass(log=log)
         argv = [u'--a=épsîlön']
@@ -301,17 +285,6 @@ class TestKeyValueCL(TestCase):
         self.assertEqual(config.A.a, u'épsîlön')
 
 
-class CBase(HasTraits):
-    a = List().tag(config=True)
-    b = List().tag(config=True, multiplicity='*')
-    c = List().tag(config=True, multiplicity='append')
-    adict = Dict().tag(config=True)
-
-class CSub(CBase):
-    d = Tuple().tag(config=True)
-    e = Tuple().tag(config=True, multiplicity='+')
-    bdict = Dict().tag(config=True, multiplicity='*')
-
 class TestArgParseKVCL(TestKeyValueCL):
     klass = KVArgParseConfigLoader
 
@@ -321,36 +294,13 @@ class TestArgParseKVCL(TestKeyValueCL):
         config = cl.load_config(argv, aliases=dict(a='A.a', b='A.b'))
         self.assertEqual(config.A.a, os.path.expanduser('~/1/2/3'))
         self.assertEqual(config.A.b, '~/1/2/3')
-
+    
     def test_eval(self):
         cl = self.klass(log=log)
         argv = ['-c', 'a=5']
         config = cl.load_config(argv, aliases=dict(c='A.c'))
         self.assertEqual(config.A.c, u"a=5")
-
-    def test_seq_traits(self):
-        cl = self.klass(log=log, classes=(CBase, CSub))
-        aliases = {'a3': 'CBase.c', 'a5': 'CSub.e'}
-        argv = ("--CBase.a A --CBase.a 2 --CBase.b 1 2 3 --a3 AA --CBase.c BB "
-                "--CSub.d 1 --CSub.d BBB --CSub.e 1 a bcd").split()
-        config = cl.load_config(argv, aliases=aliases)
-        self.assertEqual(config.CBase.a, ['A', 2])
-        self.assertEqual(config.CBase.b, [1, 2, 3])
-        self.assertEqual(config.CBase.c, ['AA', 'BB'])
-
-        self.assertEqual(config.CSub.d, [1, 'BBB'])
-        self.assertEqual(config.CSub.e, [1, 'a', 'bcd'])
-
-    def test_dict_traits(self):
-        cl = self.klass(log=log, classes=(CBase, CSub))
-        aliases = {'D': 'CBase.adict', 'E': 'CSub.bdict'}
-        argv = ["--CBase.adict", "k1=v1", "-D=k2=2", "-D", "k3=v 3", "-E", "k=v", "22=222"]
-        config = cl.load_config(argv, aliases=aliases)
-        self.assertDictEqual(config.CBase.adict,
-                {'k1': 'v1', 'k2': 2, 'k3': 'v 3'})
-        self.assertEqual(config.CSub.bdict,
-                {'k': 'v', '22': 222})
-
+    
 
 class TestConfig(TestCase):
 
@@ -418,12 +368,12 @@ class TestConfig(TestCase):
     def test_builtin(self):
         c1 = Config()
         c1.format = "json"
-
+    
     def test_fromdict(self):
         c1 = Config({'Foo' : {'bar' : 1}})
         self.assertEqual(c1.Foo.__class__, Config)
         self.assertEqual(c1.Foo.bar, 1)
-
+    
     def test_fromdictmerge(self):
         c1 = Config()
         c2 = Config({'Foo' : {'bar' : 1}})
@@ -439,7 +389,7 @@ class TestConfig(TestCase):
         self.assertEqual(c1.Foo.bar, 1)
         self.assertEqual(c1.Foo.baz, 2)
         self.assertNotIn('baz', c2.Foo)
-
+    
     def test_contains(self):
         c1 = Config({'Foo' : {'baz' : 2}})
         c2 = Config({'Foo' : {'bar' : 1}})
@@ -447,14 +397,14 @@ class TestConfig(TestCase):
         self.assertIn('Foo.baz', c1)
         self.assertIn('Foo.bar', c2)
         self.assertNotIn('Foo.bar', c1)
-
+    
     def test_pickle_config(self):
         cfg = Config()
         cfg.Foo.bar = 1
         pcfg = pickle.dumps(cfg)
         cfg2 = pickle.loads(pcfg)
         self.assertEqual(cfg2, cfg)
-
+    
     def test_getattr_section(self):
         cfg = Config()
         self.assertNotIn('Foo', cfg)
@@ -483,20 +433,6 @@ class TestConfig(TestCase):
             _ = cfg._repr_html_
         self.assertNotIn('_repr_html_', cfg)
         self.assertEqual(len(cfg), 0)
-    
-    def test_lazy_config_repr(self):
-        cfg = Config()
-        cfg.Class.lazy.append(1)
-        cfg_repr = repr(cfg)
-        assert '<LazyConfigValue' in cfg_repr
-        assert "extend" in cfg_repr
-        assert " [1]}>" in cfg_repr
-        assert 'value=' not in cfg_repr
-        cfg.Class.lazy.get_value([0])
-        repr2 = repr(cfg)
-        assert repr([0,1]) in repr2
-        assert 'value=' in repr2
-        
 
     def test_getitem_not_section(self):
         cfg = Config()
@@ -504,7 +440,7 @@ class TestConfig(TestCase):
         foo = cfg['foo']
         assert isinstance(foo, LazyConfigValue)
         self.assertIn('foo', cfg)
-
+    
     def test_merge_no_copies(self):
         c = Config()
         c2 = Config()
diff --git a/traitlets/tests/test_traitlets.py b/traitlets/tests/test_traitlets.py
index 11b334c..6a12828 100644
--- a/traitlets/tests/test_traitlets.py
+++ b/traitlets/tests/test_traitlets.py
@@ -95,7 +95,7 @@ class TestTraitType(TestCase):
         # Defaults are validated when the HasTraits is instantiated
         class B(HasTraits):
             tt = MyIntTT('bad default')
-        self.assertRaises(TraitError, B)
+        self.assertRaises(TraitError, getattr, B(), 'tt')
 
     def test_info(self):
         class A(HasTraits):
@@ -896,7 +896,7 @@ class TestType(TestCase):
         class C(HasTraits):
             klass = Type(None, B)
 
-        self.assertRaises(TraitError, C)
+        self.assertRaises(TraitError, getattr, C(), 'klass')
 
     def test_str_klass(self):