Retrieves matching objects from the table, based on given
        named parameters.  If multiple named parameters are given, then
        only objects that satisfy all of the query criteria will be returned.
        
        @param wherefn: a method or lambda that returns a boolean result, as in::
           
           lambda ob : ob.unitprice > 10
           
        @type wherefn: callable(object) returning boolean

        @param kwargs: attributes for selecting records, given as additional 
          named arguments of the form C{attrname="attrvalue"}.

        @return: a new Table containing the matching objects

def where(self, wherefn=None, **kwargs):
        """
        Retrieves matching objects from the table, based on given
        named parameters.  If multiple named parameters are given, then
        only objects that satisfy all of the query criteria will be returned.
        
        @param wherefn: a method or lambda that returns a boolean result, as in::
           
           lambda ob : ob.unitprice > 10
           
        @type wherefn: callable(object) returning boolean

        @param kwargs: attributes for selecting records, given as additional 
          named arguments of the form C{attrname="attrvalue"}.

        @return: a new Table containing the matching objects
        """
        if kwargs:
            # order query criteria in ascending order of number of matching items
            # for each individual given attribute; this will minimize the number 
            # of filtering records that each subsequent attribute will have to
            # handle
            kwargs = list(kwargs.items())
            if len(kwargs) > 1 and len(self) > 100:
                kwargs = sorted(kwargs, key=self._query_attr_sort_fn)
                
            ret = self
            NO_SUCH_ATTR = object()
            for k, v in kwargs:
                newret = ret.copy_template()
                if k in ret._indexes:
                    newret.insert_many(ret._indexes[k][v])
                else:
                    newret.insert_many(r for r in ret.obs if getattr(r, k, NO_SUCH_ATTR) == v)
                ret = newret
                if not ret:
                    break
        else:
            ret = self.clone()

        if ret and wherefn is not None:
            newret = ret.copy_template()
            newret.insert_many(filter(wherefn, ret.obs))
            ret = newret

        return ret