Knack
Knack

Reputation: 1124

Pattern for lazy loading with Python

I have data in form of a dict (coming from a MongoDB database via PyMongo), like:

car = {_id: "1", "color": "silver", "steering_wheel":"2"}

where the value of "steering_wheel" is the id of another document in my database which represents an instance of a class SteeringWheel. Loaded from the DB to Python would result in:

steering_wheel = {_id: "2", "cover": "plastic"}

To work with the data I use Python classes. Now, my question is about lazy loading. I can think of two ways:

1) Keep the referenced id and create another runtime only attribut for accessing the referenced objects directly:

class Car(object):
    def __init__(self, _id, color, steering_wheel_ref_id, session):
        self._id = _id
        self.color = color
        self.steering_wheel_ref_id = steering_wheel_ref_id
        self.session = session

    @property
    def steering_wheel(self):
        try:
            return self.steering_wheel
        except AttributeError:
            # Get the referecend object from the session 
            self.steering_wheel = self.session.resolve_item_refs(self.steering_wheel_ref_id)
            return self.steering_wheel

2) The other option would be to perform a type check:

class Car(object):
    def __init__(self, _id, color, steering_wheel, session):
        self._id = _id
        self.color = color
        self.steering_wheel = steering_wheel
        self.session = session

    @property
    def steering_wheel(self):
        if isinstance(self.steering_wheel, SteeringWheel):
            return self.steering_wheel
        else:
            # Get the referecend object from the session 
            self.steering_wheel = self.session.resolve_item_refs(self.steering_wheel_ref_id)
            return self.steering_wheel

Which way would you prefer? Or are there better ways / best practices for resolving access to references by id?

Upvotes: 2

Views: 2673

Answers (4)

Rumple Stiltskin
Rumple Stiltskin

Reputation: 10415

How about this?

class Car(object):
    def __init__(self, _id, color, steering_wheel_ref_id, session):
        self._id = _id
        self.color = color
        self.steering_wheel_ref_id = steering_wheel_ref_id
        self.session = session
        self._steering_wheel = None

    @property
    def steering_wheel(self):
        if self._steering_wheel is None:
            # Get the referecend object from the session 
            self._steering_wheel = self.session.resolve_item_refs(self.steering_wheel_ref_id)
        return self._steering_wheel

Upvotes: 4

Karl Knechtel
Karl Knechtel

Reputation: 61607

While in general it's EAFP, this doesn't apply where it creates redundancy, IMO.

So:

@property
def steering_wheel(self):
if not hasattr(self, 'steering_wheel'):
    self.steering_wheel = self.session.resolve_item_refs(self.steering_wheel_ref_id)
    # And while we're at it, maybe a bit of housekeeping?
    del self.steering_wheel_ref_id
return self.steering_wheel

Speaking of redundancy... if we're going to be doing this a lot, maybe we should encapsulate this logic in its own Proxy class:

class DatabaseProxy(object):
    def __init__(self, session, id):
        self.session = session
        self.id = id

    def __getattr__(self, what):
        if what == 'value':
            self.value = self.session.resolve_item_refs(self.id) # cache for next time
            return self.value
        raise AttributeError

class Car(object):
    def __init__(self, _id, color, steering_wheel_ref_id, session):
        self._id = _id
        self.color = color
        self.steering_wheel_proxy = DatabaseProxy(session, steering_wheel_ref_id)
        self.session = session

    @property
    def steering_wheel(self): return self.steering_wheel_proxy.value

Something like that.

Upvotes: 1

jathanism
jathanism

Reputation: 33726

The first option which uses duck typing and exception handling is preferred. This follows the "Easier to ask for forgiveness than permission" (aka EAFP) methodology that is favored in Python.

Part of the reasoning for this is that instead of baking in the name of the type/class into your code, you're just trying to perform an operation and handling an expected error. This allows you to code for BEHAVIOR over IDENTITY.

Further reading:

Upvotes: 0

João Neves
João Neves

Reputation: 957

If I had to choose between one of your two approaches, I'd go with the first. It's more pythonic in the sense you make use of duck-typing, which is "the Python way".

The second one is harder to read and understand.

As for other suggestions, sorry but I got nothing. :-)

Upvotes: 1

Related Questions