简体   繁体   中英

Python decorators implementation

I am trying to understand python decorators so I decided to check the inner workings of django @login_required decorator. After looking at the source code, I got here:

def user_passes_test(test_func, login_url=None, 
    redirect_field_name=REDIRECT_FIELD_NAME):
    """
    Decorator for views that checks that the user passes the given test,
    redirecting to the log-in page if necessary. The test should be a callable
    that takes the user object and returns True if the user passes.
    """

From what I understand, the above function is supposed to use the return value of test_func to determine whether to redirect user to login page or allow user to continue. My problem is that i can't find anywhere this test_func is called. How does the above function user_passes_test work exactly. Any help will be appreciated.

edit:

I have realized that I am the problem since I was looking at the source code on django documentation and I have just noticed that there is an indentation error. Am all good now.

It's called in this line .

To explain what's going on, let's use a simpler example. Assume you have various functions (corresponding to the views) that need to have their arguments checked (corresponding to the user check) before being executed; if the arguments fail the check, the result should be None (corresponding to the login redirect).

Here's example code, the structure of which matches Django's user_passes_test :

def arguments_pass_test(test_func):
    def decorator(func):
        def _wrapped_func(*args, **kwargs):
            if test_func(*args, **kwargs):
                return func(*args, **kwargs)
            else:
                return None
        return _wrapped_func
    return decorator

@arguments_pass_test(lambda x: x != 0)
def inverse(x):
    return 1.0 / x

print(inverse(2)) # 0.5
print(inverse(0)) # None

So let's look at how the decorator magic happens, that is this part:

@arguments_pass_test(lambda x: x != 0)
def inverse(x):
    return 1.0 / x

First off, arguments_pass_test(lambda x: x != 0) is just a function call; the @ doesn't come into play yet. The return value from that function call to arguments_pass_test is the inner function called decorator , and the naming makes it clear that this function is the actual decorator.

So now we have this:

@decorator
def inverse(x):
    return 1.0 / x

The decorator syntax gets translated by Python into something roughly equivalent to this:

def inverse(x):
    return 1.0 / x

inverse = decorator(inverse)

So inverse gets replaced with the the result of calling the decorator with the original function inverse as its argument func . The result of calling the decorator is what's called _wrapped_func . So what's happening is similar to this:

def _original_inverse(x):
    return 1.0 / x

def _wrapped_func(x):
    if test_func(x): # this doesn't exist here, but bear with me
        return _original_inverse(x)
    else
        return None

inverse = _wrapped_func

which is finally equivalent(ish) to

def inverse(x):
    if x != 0:
        return 1.0 / x
    else
        return None

which is exactly what we were going for.

I'm not sure where you got that function, but I just freshly install django through pip on my Python 3.6, and here is what I found in django.contrib.auth.decorators . The following is accessible here as well.

def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
    """
    Decorator for views that checks that the user passes the given test,
    redirecting to the log-in page if necessary. The test should be a callable
    that takes the user object and returns True if the user passes.
    """

    def decorator(view_func):
        @wraps(view_func, assigned=available_attrs(view_func))
        def _wrapped_view(request, *args, **kwargs):
            if test_func(request.user):
                return view_func(request, *args, **kwargs)
            path = request.build_absolute_uri()
            resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
            # If the login url is the same scheme and net location then just
            # use the path as the "next" url.
            login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
            current_scheme, current_netloc = urlparse(path)[:2]
            if ((not login_scheme or login_scheme == current_scheme) and
                    (not login_netloc or login_netloc == current_netloc)):
                path = request.get_full_path()
            from django.contrib.auth.views import redirect_to_login
            return redirect_to_login(
                path, resolved_login_url, redirect_field_name)
        return _wrapped_view
    return decorator

This makes much more sense, since test_func is effectively called inside of the decorator.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM