Django 中auth中间件中的SimpleLazyObject(懒加载机制)

先看auth中间件的代码,按照惯例,先执行process_request函数的代码。assert断言语句主要是判断,session中间件是不是存在,而且是不是在auth中间件的前面(谁在前面谁先执行,auth中间要凭借是否有之前登录的session去判断是否是登录过的用户)。session中间件一般都会默认打开的,所以下一步执行request.user = SimpleLazyObject(lambda: get_user(request))这段代码,“(lambda: get_user(request)”这个参数可以先理解为实际的 User 对象(相当于一个func,而不是func(),是一个地址,见下面第一小案例,相当于调用函数后,可以获取当前用户的模型对象),暂时忽略。我们之前访问一个登录用户的信息就是通过“request.user.要访问的属性名”去访问登录用户的信息的。

#这段代码和本文关系不大,只是举例
#这个p只是一个函数地址
p = lambda x,y:x+y
print(p(4,6))
def get_user(request):
    #如果缓存中没有这个请求用户的信息
    if not hasattr(request, '_cached_user'):
        request._cached_user = auth.get_user(request)
    return request._cached_user


class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Django authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        )
        request.user = SimpleLazyObject(lambda: get_user(request))

事实上,request.user只是SimpleLazyObject的一个实例对象而已,那SimpleLazyObject做了什么操作呢?上代码:

class SimpleLazyObject(LazyObject):
    def __init__(self, func):
        self.__dict__['_setupfunc'] = func
        super().__init__()

    def _setup(self):
        self._wrapped = self._setupfunc()
    #其他函数的代码先暂时忽略。。。

从上图可以看到,先是继承了LazyObject类,先不管它,实例化了SimpleLazyObject类后,会自动调用 init__()函数,并传入一个参数func(这里可以先理解为当前登录的对象,或者说是地址),然后把这个参数赋值给了LazyObject类的一个变量_setupfunc,然后调用父类的__init__保证不会漏掉父类的某些操作导致程序不能运行。
从上面的分析中,似乎没发现调用_setup()函数的操作,那_setup()是什么时候调用的呢?
当我们通过request.user.某个属性的时候调用(相当于SimpleLazyObject(lambda: get_user(request)).某个属性)。那是怎么调用的呢?
当访问一个类的属性的时候,会触发_getattr_
()方法,在此代码中即SimpleLazyObject.__getattr__()。很显然SimpleLazyObject类中没有定义这个方法,那就去父类LazyObject中寻找:

class LazyObject:
    _wrapped = None

    def __init__(self):
        self._wrapped = empty   #empty = object()

    __getattr__ = new_method_proxy(getattr)

    def __setattr__(self, name, value):
        if name == "_wrapped":
            self.__dict__["_wrapped"] = value
        else:
            if self._wrapped is empty:
                self._setup()
            setattr(self._wrapped, name, value)

    def __delattr__(self, name):
        if name == "_wrapped":
            raise TypeError("can't delete _wrapped.")
        if self._wrapped is empty:
            self._setup()
        delattr(self._wrapped, name)

    def _setup(self):
        """
        Must be implemented by subclasses to initialize the wrapped object.
        """
        raise NotImplementedError('subclasses of LazyObject must provide a _setup() method')

    def __reduce__(self):
        if self._wrapped is empty:
            self._setup()
        return (unpickle_lazyobject, (self._wrapped,))

    def __copy__(self):
        if self._wrapped is empty:
            # If uninitialized, copy the wrapper. Use type(self), not
            # self.__class__, because the latter is proxied.
            return type(self)()
        else:
            # If initialized, return a copy of the wrapped object.
            return copy.copy(self._wrapped)

    def __deepcopy__(self, memo):
        if self._wrapped is empty:
            result = type(self)()
            memo[id(self)] = result
            return result
        return copy.deepcopy(self._wrapped, memo)

    __bytes__ = new_method_proxy(bytes)
    __str__ = new_method_proxy(str)
    __bool__ = new_method_proxy(bool)

    __dir__ = new_method_proxy(dir)

    __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
    __eq__ = new_method_proxy(operator.eq)
    __lt__ = new_method_proxy(operator.lt)
    __gt__ = new_method_proxy(operator.gt)
    __ne__ = new_method_proxy(operator.ne)
    __hash__ = new_method_proxy(hash)

    __getitem__ = new_method_proxy(operator.getitem)
    __setitem__ = new_method_proxy(operator.setitem)
    __delitem__ = new_method_proxy(operator.delitem)
    __iter__ = new_method_proxy(iter)
    __len__ = new_method_proxy(len)
    __contains__ = new_method_proxy(operator.contains)

限定为到 getattr = new_method_proxy(getattr)这句代码,可以看出并没有直接写成函数的形式,这里展现的只是一个函数变量,猜测应该和闭包有关。点进去看一下:


empty = object()
def new_method_proxy(func):
    def inner(self, *args):
        if self._wrapped is empty:
            self._setup()
        return func(self._wrapped, *args)
    return inner

从上图代码中可以看出,确实是这样,此函数返回的是inner(内层函数的函数变量,只是函数的一个地址),当我们调用 getattr()函数时,对应的才能把return inner(),去调用内层函数。
前面说过,访问一个类中的实例变量会调用 getattr(),根据闭包的原理,又会去调inner()函数,接下来判断self._wrapped is empty是否为空,如果时通过reques.user第一次访问某个变量时,self._wrapped确实为empty,见下面代码:

class LazyObject:
    _wrapped = None

    def __init__(self):
        self._wrapped = empty   #empty = object()

如果当第一次通过request.user.属性去访问当前用户的某个属性的时候, self._wrapped is empty的条件是成立的,接下来就接着执行self._setup()这句代码。_setup()函数子类中是有的,所以直接从子类中找:

 def _setup(self):
        self._wrapped = self._setupfunc()

从代码中可以看出,先执行_setupfunc()函数再赋值给self._wrapped,然后就改变了self._wrapped了的状态,self._setupfunc其实就是前面的func,即lambda: get_user(request)这个函数变量(也可以理解为user的模型对象,通过它就可以访问当前用户的属性信息了)。赋值完成后,self._wrapped就不再是empty了。
下面的代码第二次再通过request.user.属性取值的时候就不会进行调用self._setup(),原理很简单,再次通过request.user.属性取值时,代码执行过程依然会和之前一样,先走到if self._wrapped is empty这句代码:

def new_method_proxy(func):
    def inner(self, *args):
        if self._wrapped is empty:
            self._setup()
        return func(self._wrapped, *args)
    return inner

会进行判断 self._wrapped 是不是 empty,很明显self._wrapped = self._setupfunc()了,
然后执行传入的func(self._wrapped, *args)方法,这个func(self._wrapped, *args)其实就是getattr()方法,然后你想获取当前用户的那个属性,getattr就可以帮你获取当前用户的哪个属性了,然后再由函数一层层返回,返回到request.user.你要访问的属性。

参考:https://blog.csdn.net/u011019726/article/details/79174251

参考:https://www.codercto.com/a/77863.html

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值