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
- 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.
- Register the class, not an instance: Operators.register_custom_ops([MyOp]).
- 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
- Always define custom operators as classes deriving from qlib.data.ops.Expression.
- Register the class object, never an instance.
- Unit-test registration with a trivial expression evaluation right after register_custom_ops.
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
- This type of `limit_threshold` is not supported
- stock data from resam_ts_data must be a number, pd.Series or
- provider_uri does not support {type(provider_uri)}
- Unsupported data type: {type(data)}.
- Unsupported filter types: {type(filter_t)}! Filter only supp
AI-assisted analysis of microsoft/qlib@79633dd950 (2026-08-15).
Data as JSON: /api/errors/f6a413bab3ab2a06.
Report an issue: GitHub.