Args:
        name_scope(str): the default scope to use. If None, will use the name of the function.

    Returns:
        A decorator which makes the function run under a name scope.
        The name scope is obtained by the following:
        1. The 'name_scope' keyword argument when the decorated function is called.
        2. The 'name_scope' argument of the decorator.
        3. (default) The name of the decorated function itself.

    Example:

    .. code-block:: python

        @under_name_scope()
        def rms(x):
            return tf.sqrt(
                tf.reduce_mean(tf.square(x)))

        rms(tensor)  # will be called under name scope 'rms'
        rms(tensor, name_scope='scope')  # will be called under name scope 'scope'


    Todo:
        Add a reuse option.

def under_name_scope(name_scope=None):
    """
    Args:
        name_scope(str): the default scope to use. If None, will use the name of the function.

    Returns:
        A decorator which makes the function run under a name scope.
        The name scope is obtained by the following:
        1. The 'name_scope' keyword argument when the decorated function is called.
        2. The 'name_scope' argument of the decorator.
        3. (default) The name of the decorated function itself.

    Example:

    .. code-block:: python

        @under_name_scope()
        def rms(x):
            return tf.sqrt(
                tf.reduce_mean(tf.square(x)))

        rms(tensor)  # will be called under name scope 'rms'
        rms(tensor, name_scope='scope')  # will be called under name scope 'scope'


    Todo:
        Add a reuse option.
    """

    def _impl(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            scopename = kwargs.pop('name_scope', name_scope)
            if scopename is None:
                scopename = func.__name__

            with tf.name_scope(scopename):
                return func(*args, **kwargs)
        return wrapper
    return _impl