Changeset 894 for trunk/pg.py


Ignore:
Timestamp:
Sep 23, 2016, 10:04:06 AM (3 years ago)
Author:
cito
Message:

Cache the namedtuple classes used for query result rows

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/pg.py

    r886 r894  
    5757except NameError:  # Python >= 3.0
    5858    basestring = (str, bytes)
     59
     60try:
     61    from functools import lru_cache
     62except ImportError:  # Python < 3.2
     63    from functools import update_wrapper
     64    try:
     65        from _thread import RLock
     66    except ImportError:
     67        class RLock:  # for builds without threads
     68            def __enter__(self): pass
     69
     70            def __exit__(self, exctype, excinst, exctb): pass
     71
     72    def lru_cache(maxsize=128):
     73        """Simplified functools.lru_cache decorator for one argument."""
     74
     75        def decorator(function):
     76            sentinel = object()
     77            cache = {}
     78            get = cache.get
     79            lock = RLock()
     80            root = []
     81            root_full = [root, False]
     82            root[:] = [root, root, None, None]
     83
     84            if maxsize == 0:
     85
     86                def wrapper(arg):
     87                    res = function(arg)
     88                    return res
     89
     90            elif maxsize is None:
     91
     92                def wrapper(arg):
     93                    res = get(arg, sentinel)
     94                    if res is not sentinel:
     95                        return res
     96                    res = function(arg)
     97                    cache[arg] = res
     98                    return res
     99
     100            else:
     101
     102                def wrapper(arg):
     103                    with lock:
     104                        link = get(arg)
     105                        if link is not None:
     106                            root = root_full[0]
     107                            prev, next, _arg, res = link
     108                            prev[1] = next
     109                            next[0] = prev
     110                            last = root[0]
     111                            last[1] = root[0] = link
     112                            link[0] = last
     113                            link[1] = root
     114                            return res
     115                    res = function(arg)
     116                    with lock:
     117                        root, full = root_full
     118                        if arg in cache:
     119                            pass
     120                        elif full:
     121                            oldroot = root
     122                            oldroot[2] = arg
     123                            oldroot[3] = res
     124                            root = root_full[0] = oldroot[1]
     125                            oldarg = root[2]
     126                            oldres = root[3]  # keep reference
     127                            root[2] = root[3] = None
     128                            del cache[oldarg]
     129                            cache[arg] = oldroot
     130                        else:
     131                            last = root[0]
     132                            link = [last, root, arg, res]
     133                            last[1] = root[0] = cache[arg] = link
     134                            if len(cache) >= maxsize:
     135                                root_full[1] = True
     136                    return res
     137
     138            wrapper.__wrapped__ = function
     139            return update_wrapper(wrapper, function)
     140
     141        return decorator
    59142
    60143
     
    10221105    def create_array_cast(self, basecast):
    10231106        """Create an array typecast for the given base cast."""
     1107        cast_array = self['anyarray']
    10241108        def cast(v):
    10251109            return cast_array(v, basecast)
     
    10281112    def create_record_cast(self, name, fields, casts):
    10291113        """Create a named record typecast for the given fields and casts."""
     1114        cast_record = self['record']
    10301115        record = namedtuple(name, fields)
    10311116        def cast(v):
     
    11771262
    11781263
     1264# The result rows for database operations are returned as named tuples
     1265# by default. Since creating namedtuple classes is a somewhat expensive
     1266# operation, we cache up to 1024 of these classes by default.
     1267
     1268@lru_cache(maxsize=1024)
     1269def _row_factory(names):
     1270    """Get a namedtuple factory for row results with the given names."""
     1271    try:
     1272        try:
     1273            return namedtuple('Row', names, rename=True)._make
     1274        except TypeError:  # Python 2.6 and 3.0 do not support rename
     1275            names = [v if v.isalnum() else 'column_%d' % (n,)
     1276                     for n, v in enumerate(names)]
     1277            return namedtuple('Row', names)._make
     1278    except ValueError:  # there is still a problem with the field names
     1279        names = ['column_%d' % (n,) for n in range(len(names))]
     1280        return namedtuple('Row', names)._make
     1281
     1282
     1283def set_row_factory_size(maxsize):
     1284    """Change the size of the namedtuple factory cache.
     1285
     1286    If maxsize is set to None, the cache can grow without bound.
     1287    """
     1288    global _row_factory
     1289    _row_factory = lru_cache(maxsize)(_row_factory.__wrapped__)
     1290
     1291
    11791292def _namedresult(q):
    11801293    """Get query result as named tuples."""
    1181     row = namedtuple('Row', q.listfields())
    1182     return [row(*r) for r in q.getresult()]
     1294    row = _row_factory(q.listfields())
     1295    return [row(r) for r in q.getresult()]
    11831296
    11841297
     
    11891302        """Create query from given result rows and field names."""
    11901303        self.result = result
    1191         self.fields = fields
     1304        self.fields = tuple(fields)
    11921305
    11931306    def listfields(self):
Note: See TracChangeset for help on using the changeset viewer.