Changeset 894 for trunk/pgdb.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/pgdb.py

    r893 r894  
    7575from uuid import UUID as Uuid
    7676from math import isnan, isinf
    77 from collections import namedtuple
     77from collections import namedtuple, Iterable
    7878from functools import partial
    7979from re import compile as regex
     
    9595    basestring = (str, bytes)
    9696
    97 from collections import Iterable
     97try:
     98    from functools import lru_cache
     99except ImportError:  # Python < 3.2
     100    from functools import update_wrapper
     101    try:
     102        from _thread import RLock
     103    except ImportError:
     104        class RLock:  # for builds without threads
     105            def __enter__(self): pass
     106
     107            def __exit__(self, exctype, excinst, exctb): pass
     108
     109    def lru_cache(maxsize=128):
     110        """Simplified functools.lru_cache decorator for one argument."""
     111
     112        def decorator(function):
     113            sentinel = object()
     114            cache = {}
     115            get = cache.get
     116            lock = RLock()
     117            root = []
     118            root_full = [root, False]
     119            root[:] = [root, root, None, None]
     120
     121            if maxsize == 0:
     122
     123                def wrapper(arg):
     124                    res = function(arg)
     125                    return res
     126
     127            elif maxsize is None:
     128
     129                def wrapper(arg):
     130                    res = get(arg, sentinel)
     131                    if res is not sentinel:
     132                        return res
     133                    res = function(arg)
     134                    cache[arg] = res
     135                    return res
     136
     137            else:
     138
     139                def wrapper(arg):
     140                    with lock:
     141                        link = get(arg)
     142                        if link is not None:
     143                            root = root_full[0]
     144                            prev, next, _arg, res = link
     145                            prev[1] = next
     146                            next[0] = prev
     147                            last = root[0]
     148                            last[1] = root[0] = link
     149                            link[0] = last
     150                            link[1] = root
     151                            return res
     152                    res = function(arg)
     153                    with lock:
     154                        root, full = root_full
     155                        if arg in cache:
     156                            pass
     157                        elif full:
     158                            oldroot = root
     159                            oldroot[2] = arg
     160                            oldroot[3] = res
     161                            root = root_full[0] = oldroot[1]
     162                            oldarg = root[2]
     163                            oldres = root[3]  # keep reference
     164                            root[2] = root[3] = None
     165                            del cache[oldarg]
     166                            cache[arg] = oldroot
     167                        else:
     168                            last = root[0]
     169                            link = [last, root, arg, res]
     170                            last[1] = root[0] = cache[arg] = link
     171                            if len(cache) >= maxsize:
     172                                root_full[1] = True
     173                    return res
     174
     175            wrapper.__wrapped__ = function
     176            return update_wrapper(wrapper, function)
     177
     178        return decorator
    98179
    99180
     
    721802
    722803
    723 ### Error messages
     804### Error Messages
    724805
    725806def _db_error(msg, cls=DatabaseError):
     
    733814    """Return OperationalError."""
    734815    return _db_error(msg, OperationalError)
     816
     817
     818### Row Tuples
     819
     820# The result rows for database operations are returned as named tuples
     821# by default. Since creating namedtuple classes is a somewhat expensive
     822# operation, we cache up to 1024 of these classes by default.
     823
     824@lru_cache(maxsize=1024)
     825def _row_factory(names):
     826    """Get a namedtuple factory for row results with the given names."""
     827    try:
     828        try:
     829            return namedtuple('Row', names, rename=True)._make
     830        except TypeError:  # Python 2.6 and 3.0 do not support rename
     831            names = [v if v.isalnum() else 'column_%d' % (n,)
     832                     for n, v in enumerate(names)]
     833            return namedtuple('Row', names)._make
     834    except ValueError:  # there is still a problem with the field names
     835        names = ['column_%d' % (n,) for n in range(len(names))]
     836        return namedtuple('Row', names)._make
     837
     838
     839def set_row_factory_size(maxsize):
     840    """Change the size of the namedtuple factory cache.
     841
     842    If maxsize is set to None, the cache can grow without bound.
     843    """
     844    global _row_factory
     845    _row_factory = lru_cache(maxsize)(_row_factory.__wrapped__)
    735846
    736847
     
    13091420        different row factories whenever the column description changes.
    13101421        """
    1311         colnames = self.colnames
    1312         if colnames:
    1313             try:
    1314                 try:
    1315                     return namedtuple('Row', colnames, rename=True)._make
    1316                 except TypeError:  # Python 2.6 and 3.0 do not support rename
    1317                     colnames = [v if v.isalnum() else 'column_%d' % (n,)
    1318                              for n, v in enumerate(colnames)]
    1319                     return namedtuple('Row', colnames)._make
    1320             except ValueError:  # there is still a problem with the field names
    1321                 colnames = ['column_%d' % (n,) for n in range(len(colnames))]
    1322                 return namedtuple('Row', colnames)._make
     1422        names = self.colnames
     1423        if names:
     1424            return _row_factory(tuple(names))
     1425
    13231426
    13241427
Note: See TracChangeset for help on using the changeset viewer.