Repository: ray-project/ray

Issue:
[tune[ partial function cannot be registered as trainable
### System information
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: Ubuntu 18.04
- **Ray installed from (source or binary)**: binary
- **Ray version**: 0.6.1
- **Python version**: 3.7
- **Exact command to reproduce**:

The following code fails:
```
def dummy_fn(c, a, b):
    print("Called")

from functools import partial
from ray.tune import register_trainable
register_trainable("test", partial(dummy_fn, c=None))

```

while the following code works:
```
def dummy_fn(a, b):
    print("Called")

from functools import partial
from ray.tune import register_trainable
register_trainable("test", dummy_fn)

```
### Describe the problem
The first code sample does not work, despite the function (after the `partial`) fullfills all requirements to be properly registered.

### Source code / logs
Traceback:

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/temp/schock/conda/envs/delira_new/lib/python3.7/site-packages/ray/tune/registry.py", line 35, in register_trainable
    if not issubclass(trainable, Trainable):
TypeError: issubclass() arg 1 must be a class
```



Discussion:

Resolving patch:
diff --git a/python/ray/tune/registry.py b/python/ray/tune/registry.py
--- a/python/ray/tune/registry.py
+++ b/python/ray/tune/registry.py
@@ -2,6 +2,7 @@
 from __future__ import division
 from __future__ import print_function
 
+import logging
 from types import FunctionType
 
 import ray
@@ -17,6 +18,8 @@
     TRAINABLE_CLASS, ENV_CREATOR, RLLIB_MODEL, RLLIB_PREPROCESSOR
 ]
 
+logger = logging.getLogger(__name__)
+
 
 def register_trainable(name, trainable):
     """Register a trainable function or class.
@@ -30,8 +33,16 @@ def register_trainable(name, trainable):
 
     from ray.tune.trainable import Trainable, wrap_function
 
-    if isinstance(trainable, FunctionType):
+    if isinstance(trainable, type):
+        logger.debug("Detected class for trainable.")
+    elif isinstance(trainable, FunctionType):
+        logger.debug("Detected function for trainable.")
+        trainable = wrap_function(trainable)
+    elif callable(trainable):
+        logger.warning(
+            "Detected unknown callable for trainable. Converting to class.")
         trainable = wrap_function(trainable)
+
     if not issubclass(trainable, Trainable):
         raise TypeError("Second argument must be convertable to Trainable",
                         trainable)


Test patch:
diff --git a/python/ray/tune/test/trial_runner_test.py b/python/ray/tune/test/trial_runner_test.py
--- a/python/ray/tune/test/trial_runner_test.py
+++ b/python/ray/tune/test/trial_runner_test.py
@@ -112,6 +112,24 @@ class B(Trainable):
         self.assertRaises(TypeError, lambda: register_trainable("foo", B()))
         self.assertRaises(TypeError, lambda: register_trainable("foo", A))
 
+    def testRegisterTrainableCallable(self):
+        def dummy_fn(config, reporter, steps):
+            reporter(timesteps_total=steps, done=True)
+
+        from functools import partial
+        steps = 500
+        register_trainable("test", partial(dummy_fn, steps=steps))
+        [trial] = run_experiments({
+            "foo": {
+                "run": "test",
+                "config": {
+                    "script_min_iter_time_s": 0,
+                },
+            }
+        })
+        self.assertEqual(trial.status, Trial.TERMINATED)
+        self.assertEqual(trial.last_result[TIMESTEPS_TOTAL], steps)
+
     def testBuiltInTrainableResources(self):
         class B(Trainable):
             @classmethod