Merge branch 'pull-74'
diff --git a/.travis.yml b/.travis.yml
index b89a510..025e9b3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -64,7 +64,7 @@
     - env: "TOXENV=py37-unit2"
       python: "3.7"
 
-install: pip install tox
+install: pip install tox==3.7.0
 script: tox
 # !!! WARNING !!!
 # This file is automatically generated by ./rebuild-travis-yaml
diff --git a/parameterized/parameterized.py b/parameterized/parameterized.py
index dd9f0ed..bb3b62c 100644
--- a/parameterized/parameterized.py
+++ b/parameterized/parameterized.py
@@ -44,7 +44,10 @@
     def make_method(func, instance, type):
         return MethodType(func, instance, type)
 
+
 CompatArgSpec = namedtuple("CompatArgSpec", "args varargs keywords defaults")
+
+
 def getargspec(func):
     if PY2:
         return CompatArgSpec(*inspect.getargspec(func))
@@ -58,11 +61,14 @@
         ) %(func, ))
     return CompatArgSpec(*args[:4])
 
+
 _param = namedtuple("param", "args kwargs")
 
+
 def skip_on_empty_helper(*a, **kw):
     raise SkipTest("parameterized input is empty")
 
+
 def reapply_patches_if_need(func):
 
     def dummy_wrapper(orgfunc):
@@ -79,6 +85,7 @@
             func = patch_obj.decorate_callable(func)
     return func
 
+
 def delete_patches_if_need(func):
     if hasattr(func, 'patchings'):
         func.patchings[:] = []
@@ -217,6 +224,7 @@
 
     return result
 
+
 def short_repr(x, n=64):
     """ A shortened repr of ``x`` which is guaranteed to be ``unicode``::
 
@@ -236,6 +244,7 @@
         x_repr = x_repr[:n//2] + "..." + x_repr[len(x_repr) - n//2:]
     return x_repr
 
+
 def default_doc_func(func, num, p):
     if func.__doc__ is None:
         return None
@@ -256,9 +265,11 @@
     args = "%s[with %s]" %(len(first) and " " or "", ", ".join(descs))
     return "".join([first.rstrip(), args, suffix, nl, rest])
 
+
 def default_name_func(func, num, p):
     base_name = func.__name__
     name_suffix = "_%s" %(num, )
+
     if len(p.args) > 0 and isinstance(p.args[0], string_types):
         name_suffix += "_" + parameterized.to_safe_name(p.args[0])
     return base_name + name_suffix
@@ -271,6 +282,7 @@
     "_pytest": "pytest",
 }
 
+
 def set_test_runner(name):
     global _test_runner_override
     if name not in _test_runners:
@@ -280,6 +292,7 @@
         )
     _test_runner_override = name
 
+
 def detect_runner():
     """ Guess which test runner we're using by traversing the stack and looking
         for the first matching module. This *should* be reasonably safe, as
@@ -536,7 +549,7 @@
         return str(re.sub("[^a-zA-Z0-9_]+", "_", s))
 
 
-def parameterized_class(attrs, input_values=None):
+def parameterized_class(attrs, input_values=None, classname_func=None):
     """ Parameterizes a test class by setting attributes on the class.
 
         Can be used in two ways:
@@ -569,25 +582,15 @@
         [dict(zip(attrs, vals)) for vals in input_values]
     )
 
+    classname_func = classname_func or default_classname_func
+
     def decorator(base_class):
         test_class_module = sys.modules[base_class.__module__].__dict__
         for idx, input_dict in enumerate(input_dicts):
             test_class_dict = dict(base_class.__dict__)
             test_class_dict.update(input_dict)
 
-            name_suffix = input_values and input_values[idx]
-            if isinstance(name_suffix, (list, tuple)) and len(input_values) > 0:
-                name_suffix = name_suffix[0]
-            name_suffix = (
-                "_%s" %(name_suffix, ) if isinstance(name_suffix, string_types) else
-                ""
-            )
-
-            name = "%s_%s%s" %(
-                base_class.__name__,
-                idx,
-                name_suffix,
-            )
+            name = classname_func(base_class, idx, input_dicts)
 
             test_class_module[name] = type(name, (base_class, ), test_class_dict)
 
@@ -603,3 +606,22 @@
         return base_class
 
     return decorator
+
+
+def default_classname_func(cls, num, p):
+
+    name_suffix = p and p[num]
+    if isinstance(name_suffix, (list, tuple)) and len(p) > 0:
+        name_suffix = name_suffix[0]
+    name_suffix = (
+        "_%s" %(name_suffix, ) if isinstance(name_suffix, string_types) else
+        ""
+    )
+
+    name = "%s_%s%s" %(
+        cls.__name__,
+        num,
+        name_suffix,
+    )
+
+    return name
\ No newline at end of file
diff --git a/parameterized/test.py b/parameterized/test.py
index 0621b6a..1b0abb6 100644
--- a/parameterized/test.py
+++ b/parameterized/test.py
@@ -449,10 +449,10 @@
 ])
 class TestParameterizedClass(TestCase):
     expect([
-        "TestParameterizedClass_0_foo:test_method_a('foo', 1, 2)",
-        "TestParameterizedClass_0_foo:test_method_b('foo', 1, 2)",
-        "TestParameterizedClass_1_bar:test_method_a('bar', 3, 0)",
-        "TestParameterizedClass_1_bar:test_method_b('bar', 3, 0)",
+        "TestParameterizedClass_0:test_method_a('foo', 1, 2)",
+        "TestParameterizedClass_0:test_method_b('foo', 1, 2)",
+        "TestParameterizedClass_1:test_method_a('bar', 3, 0)",
+        "TestParameterizedClass_1:test_method_b('bar', 3, 0)",
         "TestParameterizedClass_2:test_method_a(0, 1, 2)",
         "TestParameterizedClass_2:test_method_b(0, 1, 2)",
     ])
@@ -475,6 +475,51 @@
         self._assertions("test_method_b")
 
 
+def custom_cls_naming_func(clsname, idx, attrs):
+    """
+    Custom class naming function for the form of parameterized_class that
+    takes a tuple of attributes, then a list of tuples of values
+    :param clsname: The original class that the decorator specialises
+    :param idx: the test index (starts at 0)
+    :param attrs: A list of dicts of attribute values
+    :return:
+    """
+    return "%s_%s_%s_%s" % (clsname.__name__, str(attrs[idx]['a']), str(attrs[idx]['b']), str(attrs[idx]['c']))
+
+
+@parameterized_class(("a", "b", "c"), [
+    ("foo", 1, 2),
+    ("bar", 3, 0),
+    (0, 1, 2),
+], classname_func=custom_cls_naming_func)
+class TestNamedParameterizedClass(TestCase):
+    expect([
+        "TestNamedParameterizedClass_foo_1_2:test_method_a('foo', 1, 2)",
+        "TestNamedParameterizedClass_foo_1_2:test_method_b('foo', 1, 2)",
+        "TestNamedParameterizedClass_bar_3_0:test_method_a('bar', 3, 0)",
+        "TestNamedParameterizedClass_bar_3_0:test_method_b('bar', 3, 0)",
+        "TestNamedParameterizedClass_0_1_2:test_method_a(0, 1, 2)",
+        "TestNamedParameterizedClass_0_1_2:test_method_b(0, 1, 2)",
+    ])
+
+    def _assertions(self, test_name):
+        assert hasattr(self, "a")
+        assert_equal(self.b + self.c, 3)
+        missing_tests.remove("%s:%s(%r, %r, %r)" %(
+            self.__class__.__name__,
+            test_name,
+            self.a,
+            self.b,
+            self.c,
+        ))
+
+    def test_method_a(self):
+        self._assertions("test_method_a")
+
+    def test_method_b(self):
+        self._assertions("test_method_b")
+
+
 @parameterized_class([
     {"foo": 1},
     {"bar": 1},
@@ -516,3 +561,37 @@
             self.foo,
             self.bar,
         ))
+
+
+def custom_cls_naming_dict_func(clsname, idx, attrs):
+    """
+    Custom class naming function for the form of parameterized_class that
+    takes a list of dictionaries containing attributes to override.
+    :param clsname: The original class that the decorator specialises
+    :param idx: the test index (starts at 0)
+    :param attrs: A list of dicts of attribute values
+    :return:
+    """
+    return "%s_%s_%s" % (clsname.__name__, str(attrs[idx].get('foo', 0)), str(attrs[idx].get('bar', 0)))
+
+
+@parameterized_class([
+    {"foo": 1},
+    {"bar": 1},
+], classname_func=custom_cls_naming_dict_func
+)
+class TestNamedParameterizedClassDict(TestCase):
+    expect([
+        "TestNamedParameterizedClassDict_1_0:test_method(1, 0)",
+        "TestNamedParameterizedClassDict_0_1:test_method(0, 1)",
+    ])
+
+    foo = 0
+    bar = 0
+
+    def test_method(self):
+        missing_tests.remove("%s:test_method(%r, %r)" %(
+            self.__class__.__name__,
+            self.foo,
+            self.bar,
+        ))
diff --git a/rebuild-travis-yaml b/rebuild-travis-yaml
index 18564b7..372ec84 100755
--- a/rebuild-travis-yaml
+++ b/rebuild-travis-yaml
@@ -15,7 +15,7 @@
 matrix:
   include:
 {env_list}
-install: pip install tox
+install: pip install tox==3.7.0
 script: tox
 # !!! WARNING !!!
 # This file is automatically generated by ./rebuild-travis-yaml