@ray.remote with decorators

from functools import wraps
import ray

ray.init()


def say_hey(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('hey')
        return func(*args, **kwargs)
    return wrapper


@say_hey
@ray.remote
def print_hello():
    print('hellooo')


print_hello.remote()

can someone explain why the output is:

hellooo

instead of raising TypeError: Remote functions cannot be called directly. Instead of running '__main__.print_hello()', try '__main__.print_hello.remote()'.?

this is what I believe happens, in order:

  1. the print_hello function is defined
  2. print_hello becomes a RemoteFunction
  3. the RemoteFunction becomes decorated by say_hey

so when print_hello is called, say_hey should print hey and then try to call func, which is a RemoteFunction, which should raise an error

it turns out that decorators are applied from the top to the bottom. e.g.:

from functools import wraps


def say_hey1(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('hey1')
        return func(*args, **kwargs)
    return wrapper


def say_hey2(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('hey2')
        return func(*args, **kwargs)
    return wrapper

@say_hey1
@say_hey2
def print_hello():
    print('hellooo')


print_hello()

outputs:

hey1
hey2
hellooo

so this is what happens with the original script:

  1. the print_hello function is defined
  2. print_hello becomes decorated by say_hey
  3. the decorated print_hello becomes a RemoteFunction