ionelmc / python-lazy-object-proxy

A fast and thorough lazy object proxy.
BSD 2-Clause "Simplified" License
247 stars 36 forks source link

inheritance way to implement lazy objects #11

Open felixcarmona opened 9 years ago

felixcarmona commented 9 years ago

I want to implement a new more unattended way to proxy objects, inheriting a LazyObject class instead of doing a = Proxy(lambda: A(5))

I'm doing this:

from lazy_object_proxy import Proxy

class LazyObject:
    def __new__(cls, *args, **kwargs):
        class_ = type(object.__class__.__name__, (cls,), {})
        class_.__new__ = lambda cls_, *args_, **kwargs_: object.__new__(cls_)
        return Proxy(lambda: class_(*args, **kwargs))

class A(LazyObject):
    def __init__(self, x):
        print("Init")
        self.x = 5

a = A(5)
print('GO')
print(a.x)

result:

GO
Init
5

What do you think about this implementation? do you think I've missed any concern? any way to implement this in C?

Regards

ionelmc commented 9 years ago

object.__class__.__name__ will always be "type" - what did you meant to do there?

Is your intention roughly "delay object initialization" or "delay object creation"? It's not clear what's wrong with the regular way (lambda *args, **kwargs: Proxy(lambda: A(*args, **kwargs))) ...

ionelmc commented 9 years ago

Doing something special in constructor (__new__) only makes sense if you want subclasses. Do you really need that?

Otherwise just use a class decorator:

def lazy(klass):
  def lazy_class(*args, **kwargs):
    return Proxy(lambda: klass(*args, **kwargs))
  lazy_class.__name__ = "lazy_" + klass.__name__
  return lazy_class

@lazy
class A(object):
  ....
ionelmc commented 9 years ago

Now that I look again, your example seems fine (I didn't test it so it's a loose measure of "fine" :grin:).

You should replace class_ = type(object.__class__.__name__, (cls,), {}) with class_ = type(cls.__name__, (cls,), {}) for better __repr__ output.

felixcarmona commented 9 years ago

thank you for your quick answer, which way is better? (I implemented my own because I didn't find your alternative in the doc or in tests, is there any reason to not include it?) regards!

felixcarmona commented 9 years ago

I'm doing some tests: with the decorator's way, the returned object is an instance of "function" and not of the object, so, you cannot be abled to use it in some builtins like isinstance(a, A) With my first proposal, it works:

from lazy_object_proxy import Proxy

class LazyObject:
    def __new__(cls, *args, **kwargs):
        class_ = type(object.__name__, (cls,), {})
        class_.__new__ = lambda cls_, *args_, **kwargs_: object.__new__(cls_)
        return Proxy(lambda: class_(*args, **kwargs))

class B(LazyObject):
    def __init__(self, x):
        print("Init")
        self.x = x

b = B(5)
print('GO')
print(b.x)
print(isinstance(b, B))

returns:

GO
Init
5
True

and

from lazy_object_proxy import Proxy

def lazy(klass):
    def lazy_class(*args, **kwargs):
        return Proxy(lambda: klass(*args, **kwargs))
    lazy_class.__name__ = "lazy_" + klass.__name__
    return lazy_class

@lazy
class A:
    def __init__(self, x):
        print("Init")
        self.x = x

a = A(5)
print('GO')
print(a.x)
print(isinstance(a, A))

returns:

Traceback (most recent call last):
GO
  File "test.py", line 19, in <module>
Init
    print(isinstance(a, A))
5
TypeError: isinstance() arg 2 must be a type or tuple of types
ionelmc commented 9 years ago

You're right. I suppose we could have a metaclass that roughly does what you do in your example in a lazy_object_proxy.contrib module (as opposed to just giving the complicated example in the readme).

felixcarmona commented 9 years ago

Sounds good, thanks for your time, it's great to have contributors like you. You can close this issue if you want, Regards

edit: btw: You can still using the decorator:

from lazy_object_proxy import Proxy

def lazy(cls):
    class Lazy:
        def __new__(cls, *args, **kwargs):
            original_class = type(object.__name__, (cls,), {})
            original_class.__new__ = lambda cls_, *args_, **kwargs_: object.__new__(cls_)
            return Proxy(lambda: original_class(*args, **kwargs))
    lazy_class = type(cls.__name__, (cls, Lazy), {})
    return lazy_class

@lazy
class B:
    def __init__(self, x):
        print("Init")
        self.x = x

b = B(5)
print('GO')
print(b.x)
print(isinstance(b, B))

output:

GO
Init
5
True