diff --git a/python/ray/actor.py b/python/ray/actor.py index 691cafd81..0223c2ef7 100644 --- a/python/ray/actor.py +++ b/python/ray/actor.py @@ -211,12 +211,19 @@ class ActorClassMetadata: self.method_signatures = {} self.actor_method_num_return_vals = {} for method_name, method in self.actor_methods: + # Whether or not this method requires binding of its first + # argument. For class and static methods, we do not want to bind + # the first argument, but we do for instance methods + is_bound = (ray.utils.is_class_method(method) + or ray.utils.is_static_method(self.modified_class, + method_name)) + # Print a warning message if the method signature is not # supported. We don't raise an exception because if the actor # inherits from a class that has a method whose signature we # don't support, there may not be much the user can do about it. self.method_signatures[method_name] = signature.extract_signature( - method, ignore_first=not ray.utils.is_class_method(method)) + method, ignore_first=not is_bound) # Set the default number of return values for this method. if hasattr(method, "__ray_num_return_vals__"): self.actor_method_num_return_vals[method_name] = ( diff --git a/python/ray/function_manager.py b/python/ray/function_manager.py index 6d9fe540c..1d8372e94 100644 --- a/python/ray/function_manager.py +++ b/python/ray/function_manager.py @@ -21,6 +21,7 @@ from ray.utils import ( binary_to_hex, is_function_or_method, is_class_method, + is_static_method, check_oversized_pickle, decode, ensure_str, @@ -753,7 +754,9 @@ class FunctionActorManager: # Execute the assigned method and save a checkpoint if necessary. try: - if is_class_method(method): + is_bound = (is_class_method(method) + or is_static_method(type(actor), method_name)) + if is_bound: method_returns = method(*args, **kwargs) else: method_returns = method(actor, *args, **kwargs) diff --git a/python/ray/tests/test_actor.py b/python/ray/tests/test_actor.py index 2f5abe95d..f0d59c154 100644 --- a/python/ray/tests/test_actor.py +++ b/python/ray/tests/test_actor.py @@ -208,6 +208,55 @@ def test_actor_class_attributes(ray_start_regular): assert ray.get(t.g.remote()) == 3 +def test_actor_static_attributes(ray_start_regular): + class Grandparent: + GRANDPARENT = 2 + + @staticmethod + def grandparent_static(): + assert Grandparent.GRANDPARENT == 2 + return 1 + + class Parent1(Grandparent): + PARENT1 = 6 + + @staticmethod + def parent1_static(): + assert Parent1.PARENT1 == 6 + return 2 + + def parent1(self): + assert Parent1.PARENT1 == 6 + + class Parent2: + PARENT2 = 7 + + def parent2(self): + assert Parent2.PARENT2 == 7 + + @ray.remote + class TestActor(Parent1, Parent2): + X = 3 + + @staticmethod + def f(): + assert TestActor.GRANDPARENT == 2 + assert TestActor.PARENT1 == 6 + assert TestActor.PARENT2 == 7 + assert TestActor.X == 3 + return 4 + + def g(self): + assert TestActor.GRANDPARENT == 2 + assert TestActor.PARENT1 == 6 + assert TestActor.PARENT2 == 7 + assert TestActor.f() == 4 + return TestActor.X + + t = TestActor.remote() + assert ray.get(t.g.remote()) == 3 + + def test_caching_actors(shutdown_only): # Test defining actors before ray.init() has been called. diff --git a/python/ray/utils.py b/python/ray/utils.py index 4be0af6f8..53e6a5adf 100644 --- a/python/ray/utils.py +++ b/python/ray/utils.py @@ -127,6 +127,21 @@ def is_class_method(f): return hasattr(f, "__self__") and f.__self__ is not None +def is_static_method(cls, f_name): + """Returns whether the class has a static method with the given name. + + Args: + cls: The Python class (i.e. object of type `type`) to + search for the method in. + f_name: The name of the method to look up in this class + and check whether or not it is static. + """ + for cls in inspect.getmro(cls): + if f_name in cls.__dict__: + return isinstance(cls.__dict__[f_name], staticmethod) + return False + + def random_string(): """Generate a random string to use as an ID.