Opened 5 years ago

Closed 5 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 5 years ago by Jannis Leidel

Needs documentation: unset
Needs tests: unset
Patch needs improvement: unset
Resolution: needsinfo
Status: newclosed

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 5 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 5 years ago by Mitar

Resolution: needsinfo
Status: closedreopened

comment:4 Changed 5 years ago by Alex Gaynor

Is LazyObject even part of the public API?

comment:5 Changed 5 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 5 years ago by Alex Gaynor

Resolution: wontfix
Status: reopenedclosed

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