On Apr 28, 2020, at 12:02, Alex Hall <alex.mojaki@gmail.com> wrote:

Some libraries implement a 'lazy object' which forwards all operations to a wrapped object, which gets lazily initialised once:

https://github.com/ionelmc/python-lazy-object-proxy
https://docs.djangoproject.com/en/3.0/_modules/django/utils/functional/

There's also a more general concept of proxying everything to some target. wrapt provides ObjectProxy which is the simplest case, the idea being that you override specific operations:

https://wrapt.readthedocs.io/en/latest/wrappers.html

Flask and werkzeug provide proxies which forward based on the request being handled, e.g. which thread or greenlet you're in, which allows magic like the global request object:

https://flask.palletsprojects.com/en/1.1.x/api/#flask.request

All of these have messy looking implementations and hairy edge cases. I imagine the language could be changed to make this kind of thing easier, more robust, and more performant. But I'm struggling to formulate what exactly "this kind of thing is", i.e. what feature the language could use.

For the case where you’re trying to do the “singleton pattern” for a complex object whose behavior is all about calling specific methods, a proxy might work, and the only thing Python might need, if anything, is ways to make it possible/easier to write a GenericProxy that just delegates everything in some clean way, but even that isn’t really needed if you’re willing to make the proxy specific to the type you’re singleton-ing.

But often what you want to lazily initialize is a simple object—a str, a small integer, a list of str, etc.

Guido’s example lazily initialized by calling getcwd(), and the first example given for the Swift feature is usually a fullname string built on demand from firstname and lastname. And if you look for examples of @cachedproperty (which really is exactly what you want for @lazy except that it only works for instance attributes, and you want it for class attributes or globals), the singleton pattern seems to be a notable exception, not the usual case; mostly you lazily initialize either simple objects like a str, a pair of floats, a list of int, etc., or numpy/pandas objects.

And you can’t proxy either of those in Python.

Especially str. Proxies work by duck-typing as the target, but you can’t duck-type as a str, because most builtin and extension functions that want a str ignore its methods and use the PyUnicode API to get directly at its array of characters. Numbers, lists, numpy arrays, etc. aren’t quite as bad as str, but they still have problems.

Also, even when it works, the performance cost of a proxy would often be prohibitive. If you write this:

    @lazy
    def fullname():
        return firstname + " " + lastname

… presumably it’s because you need to eliminate the cost of string concatenation every time you need the fullname. But if it then requires every operation on that fullname to go through a dynamic proxy, you’ve probably added more overhead than you saved.

So I don’t think proxies are the answer here.

Really, we either need descriptors that can somehow work for globals and class attributes (which is probably not solveable), or some brand new language semantics that aren’t built on what’s already there. The latter sounds like probably way more work than this feature deserves, but maybe the experience of Swift argues otherwise.