Skip to content

Commit

Permalink
perf: introduce quicker abstract base classes
Browse files Browse the repository at this point in the history
  • Loading branch information
kszucs committed Sep 26, 2023
1 parent f1c4277 commit 47822c6
Show file tree
Hide file tree
Showing 16 changed files with 563 additions and 75 deletions.
11 changes: 4 additions & 7 deletions ibis/backends/base/sql/compiler/base.py
@@ -1,6 +1,5 @@
from __future__ import annotations

import abc
from itertools import chain

import toolz
Expand All @@ -10,16 +9,14 @@
from ibis import util


class DML(abc.ABC):
@abc.abstractmethod
class DML:
def compile(self):
pass
raise NotImplementedError()


class DDL(abc.ABC):
@abc.abstractmethod
class DDL:
def compile(self):
pass
raise NotImplementedError()


class QueryAST:
Expand Down
56 changes: 48 additions & 8 deletions ibis/common/bases.py
@@ -1,20 +1,19 @@
from __future__ import annotations

from abc import ABCMeta, abstractmethod
from collections.abc import Hashable
import collections.abc
from abc import abstractmethod
from typing import TYPE_CHECKING, Any
from weakref import WeakValueDictionary

from ibis.common.caching import WeakCache
from ibis.common.collections import FrozenDict

if TYPE_CHECKING:
from collections.abc import Mapping

from typing_extensions import Self


class BaseMeta(ABCMeta):
class BaseMeta(type):
"""Base metaclass for many of the ibis core classes.
This metaclass enforces the subclasses to define a `__slots__` attribute and
Expand Down Expand Up @@ -51,6 +50,35 @@ def __call__(cls, *args, **kwargs):
return cls.__create__(*args, **kwargs)


class AbstractMeta(BaseMeta):
"""Metaclass to support abstract methods without extending `abc.ABCMeta`.
Provide a reduced feature set compared to `abc.ABCMeta` (no way to register
virtual subclasses) but avoids expensive instance checks by enforcing explicit
subclassing.
"""

__slots__ = ()

def __new__(metacls, clsname, bases, dct, **kwargs):
cls = super().__new__(metacls, clsname, bases, dct, **kwargs)

# calculate abstract methods
abstracts = {
name
for name, value in dct.items()
if getattr(value, "__isabstractmethod__", False)
}
for parent in bases:
for name in getattr(parent, "__abstractmethods__", set()):
value = getattr(cls, name, None)
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)

cls.__abstractmethods__ = frozenset(abstracts)
return cls


class Base(metaclass=BaseMeta):
"""Base class for many of the ibis core classes.
Expand All @@ -63,6 +91,13 @@ class Base(metaclass=BaseMeta):
__create__ = classmethod(type.__call__) # type: ignore


class Abstract(Base, metaclass=AbstractMeta):
"""Base class for abstract classes.
Extend the `Base` class with support for abstract methods and properties.
"""


class Immutable(Base):
"""Prohibit attribute assignment on the instance."""

Expand All @@ -86,7 +121,7 @@ class Singleton(Base):

@classmethod
def __create__(cls, *args, **kwargs):
key = (cls, args, FrozenDict(kwargs))
key = (cls, args, tuple(kwargs.items()))
try:
return cls.__instances__[key]
except KeyError:
Expand All @@ -106,7 +141,14 @@ def __prohibit_inheritance__(cls, **kwargs):
raise TypeError(f"Cannot inherit from final class {cls}")


class Comparable(Base):
@collections.abc.Hashable.register
class Hashable(Abstract):
@abstractmethod
def __hash__(self) -> int:
...


class Comparable(Abstract):
"""Enable quick equality comparisons.
The subclasses must implement the `__equals__` method that returns a boolean
Expand Down Expand Up @@ -160,8 +202,6 @@ class Slotted(Base):
The class is mostly used to reduce boilerplate code.
"""

__slots__ = ()

def __init__(self, **kwargs) -> None:
for name, value in kwargs.items():
object.__setattr__(self, name, value)
Expand Down
3 changes: 1 addition & 2 deletions ibis/common/caching.py
Expand Up @@ -8,7 +8,6 @@

from bidict import bidict

from ibis.common.collections import FrozenDict
from ibis.common.exceptions import IbisError

if TYPE_CHECKING:
Expand All @@ -21,7 +20,7 @@ def memoize(func: Callable) -> Callable:

@functools.wraps(func)
def wrapper(*args, **kwargs):
key = (args, FrozenDict(kwargs))
key = (args, tuple(kwargs.items()))
try:
return cache[key]
except KeyError:
Expand Down

0 comments on commit 47822c6

Please sign in to comment.