Opened 4 years ago

Closed 3 years ago

#16945 closed New feature (wontfix)

Extend LazyObject to accept constructor parameters

Reported by: mitar Owned by: nobody
Component: Core (Other) Version: 1.3
Severity: Normal Keywords:
Cc: mmitar@… Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

It would be very useful to extend LazyObject to accept constructor parameters. Currently it seems it is only possible to use it for parameter-less classes.

Something like this seems to work:

class LazyObject(object):
    """
    A wrapper for another class that can be used to delay instantiation of the
    wrapped class.
    """
    def __init__(self, *args, **kwargs):
        self._wrapped = None
        self._wrapped_args = args
        self._wrapped_kwargs = kwargs

    def __getattr__(self, name):
        if self._wrapped is None:
            self._setup()
        return getattr(self._wrapped, name)

    def __setattr__(self, name, value):
        if name in ["_wrapped", "_wrapped_args", "_wrapped_kwargs"]:
            # Assign to __dict__ to avoid infinite __setattr__ loops.
            self.__dict__[name] = value
        else:
            if self._wrapped is None:
                self._setup()
            setattr(self._wrapped, name, value)

    def __delattr__(self, name):
        if name == ["_wrapped", "_wrapped_args", "_wrapped_kwargs"]:
            raise TypeError("can't delete %s." % (name,))
        if self._wrapped is None:
            self._setup()
        delattr(self._wrapped, name)

    def _setup(self):
        """
        Must be implemented by subclasses to initialise the wrapped object.
        """
        raise NotImplementedError

    # introspection support:
    __members__ = property(lambda self: self.__dir__())

    def __dir__(self):
        if self._wrapped is None:
            self._setup()
        return dir(self._wrapped)

    def __call__(self, *args, **kwargs):
        if self._wrapped is None:
            self._setup()
        return self._wrapped(*args, **kwargs)

It is based on the code from Django 1.2. I can update it to newer version if needed.

Change History (6)

comment:1 Changed 4 years ago by jezdez

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset
  • Resolution set to needsinfo
  • Status changed from new to closed

I don't exactly understand what this is useful for as the code example doesn't show how _wrapped_args and _wrapped_kwargs are used. Please provide a specific use case this would be useful for.

comment:2 Changed 4 years ago by mitar

Oh, sorry. Currently it is possible only to use LazyObject on classes where constructors do not take arguments. But if constructors do take arguments, then above version of LazyObject is needed.

I have come above this problem in my Django project where I have one class for URL dispatching/manipulating and it cannot yet be created at load time (because URLs are not loaded). So using LazyObject was an easy way to solve the problem. But my class requires some constructor arguments. So I had to extend LazyObject.

So this addition is not directly needed for Django, but if we ship such utility class, we can make it a bit more generic for other its uses when developing with Django (so that you do not have to reimplement).

Example is then:

class LazyHref(lazy.LazyObject):
    def _setup(self):
        self._wrapped = web.href.Href(*self._wrapped_args, **self._wrapped_kwargs)


comment:3 Changed 4 years ago by mitar

  • Resolution needsinfo deleted
  • Status changed from closed to reopened

comment:4 Changed 4 years ago by Alex

Is LazyObject even part of the public API?

comment:5 Changed 4 years ago by mitar

It is in utils.functional? But probably it was not meant to be used outside. But it is one "batter included" which developers could use/reuse.

comment:6 Changed 3 years ago by Alex

  • Resolution set to wontfix
  • Status changed from reopened to closed

Marking as wontfix, this is not part of the public API, it isn't needed in Django, and if you insist on using it in your own code anyways, you can easily add this functionality in your own subclass.

Note: See TracTickets for help on using tickets.
Back to Top