microsoft/qlib · error · TypeError

operator must be subclass of ExpressionOps, not {}

Error message

operator must be subclass of ExpressionOps, not {}

What it means

Raised by Operators.register_custom_ops (qlib/data/ops.py, OpsWrapper) when an entry in the ops_list is not a subclass of qlib's Expression class. Custom operators must extend Expression (or a subclass like ElemOperator/Rolling) so they can participate in expression trees; passing a plain function, an instance instead of a class, or a config whose 'class' resolves to a non-Expression callable triggers this TypeError. (The message text says 'ExpressionOps', an older name; the check is against Expression.)

Source

Thrown at qlib/data/ops.py:1653

            - if type(ops_list) is List[dict], each element of ops_list represents the config of operator, which has the following format:

                .. code-block:: text

                    {
                        "class": class_name,
                        "module_path": path,
                    }

                Note: `class` should be the class name of operator, `module_path` should be a python module or path of file.
        """
        for _operator in ops_list:
            if isinstance(_operator, dict):
                _ops_class, _ = get_callable_kwargs(_operator)
            else:
                _ops_class = _operator

            if not issubclass(_ops_class, (Expression,)):
                raise TypeError("operator must be subclass of ExpressionOps, not {}".format(_ops_class))

            if _ops_class.__name__ in self._ops:
                get_module_logger(self.__class__.__name__).warning(
                    "The custom operator [{}] will override the qlib default definition".format(_ops_class.__name__)
                )
            self._ops[_ops_class.__name__] = _ops_class

    def __getattr__(self, key):
        if key not in self._ops:
            raise AttributeError("The operator [{0}] is not registered".format(key))
        return self._ops[key]


Operators = OpsWrapper()


def register_all_ops(C):
    """register all operator"""

View on GitHub (pinned to 79633dd950)

Solutions

  1. Make the custom operator a class inheriting from qlib.data.ops.Expression (or a fitting subclass such as Rolling or PairOperator) and implement _load_internal.
  2. Register the class, not an instance: Operators.register_custom_ops([MyOp]).
  3. If using a dict config, ensure "class" names the operator class and "module_path" points to the module/file that defines that Expression subclass.

Example fix

# before
def double(x):
    return x * 2
Operators.register_custom_ops([double])  # TypeError

# after
from qlib.data.ops import Expression

class Double(Expression):
    def __init__(self, feature):
        self.feature = feature
    def _load_internal(self, instrument, start_index, end_index, freq):
        series = self.feature.load(instrument, start_index, end_index, freq)
        return series * 2
    def get_extended_window_size(self):
        return self.feature.get_extended_window_size()

Operators.register_custom_ops([Double])
Defensive patterns

Strategy: type-guard

Validate before calling

from qlib.data.ops import Expression
for op in ops_list:
    cls = op["class"] if isinstance(op, dict) else op
    if not (isinstance(cls, type) and issubclass(cls, Expression)):
        raise TypeError(f"{cls} is not an Expression subclass")

Type guard

def is_expression_subclass(obj) -> bool:
    return isinstance(obj, type) and issubclass(obj, Expression)

Prevention

When it happens

Trigger: Operators.register_custom_ops([my_func]) where my_func is a def; registering a class that inherits only from object; passing a dict config {"class": "MyOp", "module_path": ...} where MyOp does not subclass Expression; passing an instance MyOp() instead of the class MyOp.

Common situations: Extending qlib with user-defined operators in notebooks or plugins; following outdated docs that referenced the old ExpressionOps base class name; accidentally passing an instance rather than the class object.

Related errors


AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15). Data as JSON: /api/errors/f6a413bab3ab2a06. Report an issue: GitHub.