/
githubmirror
/
spark
Обзор
Документация
Войти
/
githubmirror
/
spark
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
python/pyspark/sql/classic/column.py
699 строк
23 KB
Holden Karau
[SPARK-55206][PYTHON][SQL] Transpilation minimal functional implementation with python
03 авг 2026, 19:13
03 авг 2026, 19:13
1680141
Код
Авторство
О чём код?
# # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import sys import json import warnings from typing import ( cast, overload, Any, Callable, Iterable, List, Optional, Tuple, TYPE_CHECKING, Union, ) from pyspark.sql.column import Column as ParentColumn from pyspark.errors import PySparkAttributeError, PySparkTypeError, PySparkValueError from pyspark.errors.utils import with_origin_to_class from pyspark.sql.types import DataType from pyspark.sql.utils import get_active_spark_context, enum_to_value if TYPE_CHECKING: from py4j.java_gateway import JavaObject from pyspark.core.context import SparkContext from pyspark.sql._typing import ColumnOrName, LiteralType, DecimalLiteral, DateTimeLiteral from pyspark.sql.window import WindowSpec __all__ = ["Column"] def _create_column_from_literal( literal: Union["LiteralType", "DecimalLiteral", "DateTimeLiteral", "ParentColumn"], ) -> "JavaObject": from py4j.java_gateway import JVMView sc = get_active_spark_context() return cast(JVMView, sc._jvm).functions.lit(enum_to_value(literal)) def _create_column_from_name(name: str) -> "JavaObject": from py4j.java_gateway import JVMView sc = get_active_spark_context() return cast(JVMView, sc._jvm).functions.col(name) def _to_java_column_opt(col: Optional["ColumnOrName"]) -> Optional["JavaObject"]: if col is None: return None else: return _to_java_column(col) def _to_java_column(col: "ColumnOrName") -> "JavaObject": if isinstance(col, Column): jcol = col._jc elif isinstance(col, str): jcol = _create_column_from_name(col) else: raise PySparkTypeError( errorClass="NOT_EXPECTED_TYPE", messageParameters={ "expected_type": "Column or str", "arg_name": "col", "arg_type": type(col).__name__, }, ) return jcol @overload def _to_seq(sc: "SparkContext", cols: Iterable["JavaObject"]) -> "JavaObject": ... @overload def _to_seq( sc: "SparkContext", cols: Iterable["ColumnOrName"], converter: Optional[Callable[["ColumnOrName"], "JavaObject"]], ) -> "JavaObject": ... def _to_seq( sc: "SparkContext", cols: Union[Iterable["ColumnOrName"], Iterable["JavaObject"]], converter: Optional[Callable[["ColumnOrName"], "JavaObject"]] = None, ) -> "JavaObject": """ Convert a list of Columns (or names) into a JVM Seq of Column. An optional `converter` could be used to convert items in `cols` into JVM Column objects. """ if converter: cols = [converter(c) for c in cols] assert sc._jvm is not None return sc._jvm.PythonUtils.toSeq(cols) def _to_list( sc: "SparkContext", cols: List["ColumnOrName"], converter: Optional[Callable[["ColumnOrName"], "JavaObject"]] = None, ) -> "JavaObject": """ Convert a list of Columns (or names) into a JVM (Scala) List of Columns. An optional `converter` could be used to convert items in `cols` into JVM Column objects. """ if converter: cols = [converter(c) for c in cols] assert sc._jvm is not None return sc._jvm.PythonUtils.toList(cols) def _unary_op(name: str, self: ParentColumn) -> ParentColumn: """Create a method for given unary operator""" jc = getattr(self._jc, name)() return Column(jc) def _func_op(name: str, self: ParentColumn) -> ParentColumn: from py4j.java_gateway import JVMView sc = get_active_spark_context() jc = getattr(cast(JVMView, sc._jvm).functions, name)(self._jc) return Column(jc) def _bin_func_op( name: str, self: ParentColumn, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"], reverse: bool = False, ) -> ParentColumn: from py4j.java_gateway import JVMView sc = get_active_spark_context() fn = getattr(cast(JVMView, sc._jvm).functions, name) jc = other._jc if isinstance(other, ParentColumn) else _create_column_from_literal(other) njc = fn(self._jc, jc) if not reverse else fn(jc, self._jc) return Column(njc) def _bin_op( name: str, self: ParentColumn, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"], ) -> ParentColumn: """Create a method for given binary operator""" jc = other._jc if isinstance(other, ParentColumn) else enum_to_value(other) njc = getattr(self._jc, name)(jc) return Column(njc) def _reverse_op( name: str, self: ParentColumn, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"], ) -> ParentColumn: """Create a method for binary operator (this object is on right side)""" jother = _create_column_from_literal(other) jc = getattr(jother, name)(self._jc) return Column(jc) @with_origin_to_class class Column(ParentColumn): def __new__(cls, *args: Any, **kwargs: Any) -> "Column": return object.__new__(cls) def __getnewargs__(self) -> Tuple[Any, ...]: return (self._jc,) def __init__(self, jc: "JavaObject") -> None: self._jc = jc # arithmetic operators def __neg__(self) -> ParentColumn: return _func_op("negate", self) def __add__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("plus", self, other) def __sub__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("minus", self, other) def __mul__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("multiply", self, other) def __div__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("divide", self, other) def __truediv__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("divide", self, other) def __mod__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("mod", self, other) def __radd__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("plus", self, other) def __rsub__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _reverse_op("minus", self, other) def __rmul__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("multiply", self, other) def __rdiv__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _reverse_op("divide", self, other) def __rtruediv__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _reverse_op("divide", self, other) def __rmod__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _reverse_op("mod", self, other) def __pow__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_func_op("pow", self, other) def __rpow__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_func_op("pow", self, other, reverse=True) # logistic operators def __eq__( # type: ignore[override] self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"], ) -> ParentColumn: return _bin_op("equalTo", self, other) def __ne__( # type: ignore[override] self, other: Any, ) -> ParentColumn: return _bin_op("notEqual", self, other) def __lt__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("lt", self, other) def __le__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("leq", self, other) def __ge__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("geq", self, other) def __gt__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("gt", self, other) def eqNullSafe( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("eqNullSafe", self, other) # `and`, `or`, `not` cannot be overloaded in Python, # so use bitwise operators as boolean operators def __and__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: from pyspark.sql.functions import lit return _bin_op("and", self, lit(other)) def __or__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: from pyspark.sql.functions import lit return _bin_op("or", self, lit(other)) def __invert__(self) -> ParentColumn: return _func_op("not", self) def __rand__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: from pyspark.sql.functions import lit return _bin_op("and", self, lit(other)) def __ror__( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: from pyspark.sql.functions import lit return _bin_op("or", self, lit(other)) # container operators def __contains__(self, item: Any) -> None: raise PySparkValueError( errorClass="CANNOT_APPLY_IN_FOR_COLUMN", messageParameters={}, ) # bitwise operators def bitwiseOR( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("bitwiseOR", self, other) def bitwiseAND( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("bitwiseAND", self, other) def bitwiseXOR( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("bitwiseXOR", self, other) def getItem(self, key: Any) -> ParentColumn: if isinstance(key, Column): warnings.warn( "A column as 'key' in getItem is deprecated as of Spark 3.0, and will not " "be supported in the future release. Use `column[key]` or `column.key` syntax " "instead.", FutureWarning, ) return self[key] def getField(self, name: Any) -> ParentColumn: if isinstance(name, Column): warnings.warn( "A column as 'name' in getField is deprecated as of Spark 3.0, and will not " "be supported in the future release. Use `column[name]` or `column.name` syntax " "instead.", FutureWarning, ) return self[name] def withField(self, fieldName: str, col: ParentColumn) -> ParentColumn: if not isinstance(fieldName, str): raise PySparkTypeError( errorClass="NOT_EXPECTED_TYPE", messageParameters={ "arg_name": "fieldName", "expected_type": "str", "arg_type": type(fieldName).__name__, }, ) if not isinstance(col, Column): raise PySparkTypeError( errorClass="NOT_EXPECTED_TYPE", messageParameters={ "expected_type": "Column", "arg_name": "col", "arg_type": type(col).__name__, }, ) return Column(self._jc.withField(fieldName, col._jc)) def dropFields(self, *fieldNames: str) -> ParentColumn: sc = get_active_spark_context() jc = self._jc.dropFields(_to_seq(sc, fieldNames)) return Column(jc) def __getattr__(self, item: Any) -> ParentColumn: if item.startswith("__"): raise PySparkAttributeError( errorClass="CANNOT_ACCESS_TO_DUNDER", messageParameters={}, ) return self[item] def __getitem__(self, k: Any) -> ParentColumn: if isinstance(k, slice): if k.step is not None: raise PySparkValueError( errorClass="SLICE_WITH_STEP", messageParameters={}, ) return self.substr(k.start, k.stop) else: return _bin_op("apply", self, k) def __iter__(self) -> None: raise PySparkTypeError( errorClass="NOT_ITERABLE", messageParameters={"objectName": "Column"} ) # string methods def contains( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("contains", self, other) def startswith( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("startsWith", self, other) def endswith( self, other: Union[ParentColumn, "LiteralType", "DecimalLiteral", "DateTimeLiteral"] ) -> ParentColumn: return _bin_op("endsWith", self, other) def like(self: ParentColumn, other: str) -> ParentColumn: njc = getattr(self._jc, "like")(enum_to_value(other)) return Column(njc) def rlike(self: ParentColumn, other: str) -> ParentColumn: njc = getattr(self._jc, "rlike")(enum_to_value(other)) return Column(njc) def ilike(self: ParentColumn, other: str) -> ParentColumn: njc = getattr(self._jc, "ilike")(enum_to_value(other)) return Column(njc) def substr( self, startPos: Union[int, ParentColumn], length: Union[int, ParentColumn] ) -> ParentColumn: startPos = enum_to_value(startPos) length = enum_to_value(length) if type(startPos) is not type(length): raise PySparkTypeError( errorClass="NOT_SAME_TYPE", messageParameters={ "arg_name1": "startPos", "arg_name2": "length", "arg_type1": type(startPos).__name__, "arg_type2": type(length).__name__, }, ) if isinstance(startPos, int): jc = self._jc.substr(startPos, length) elif isinstance(startPos, Column): jc = self._jc.substr(startPos._jc, cast(ParentColumn, length)._jc) else: raise PySparkTypeError( errorClass="NOT_EXPECTED_TYPE", messageParameters={ "expected_type": "Column or int", "arg_name": "startPos", "arg_type": type(startPos).__name__, }, ) return Column(jc) def isin(self, *cols: Any) -> ParentColumn: from pyspark.sql.classic.dataframe import DataFrame if len(cols) == 1 and isinstance(cols[0], DataFrame): df: DataFrame = cols[0] jc = self._jc.isin(df._jdf) return Column(jc) if len(cols) == 1 and isinstance(cols[0], (list, set)): cols = cast(Tuple, cols[0]) cols = cast( Tuple, [c._jc if isinstance(c, Column) else _create_column_from_literal(c) for c in cols], ) sc = get_active_spark_context() jc = getattr(self._jc, "isin")(_to_seq(sc, cols)) return Column(jc) # order def asc(self) -> ParentColumn: return _unary_op("asc", self) def asc_nulls_first(self) -> ParentColumn: return _unary_op("asc_nulls_first", self) def asc_nulls_last(self) -> ParentColumn: return _unary_op("asc_nulls_last", self) def desc(self) -> ParentColumn: return _unary_op("desc", self) def desc_nulls_first(self) -> ParentColumn: return _unary_op("desc_nulls_first", self) def desc_nulls_last(self) -> ParentColumn: return _unary_op("desc_nulls_last", self) def isNull(self) -> ParentColumn: return _unary_op("isNull", self) def isNotNull(self) -> ParentColumn: return _unary_op("isNotNull", self) def isNaN(self) -> ParentColumn: return _unary_op("isNaN", self) def alias(self, *alias: str, **kwargs: Any) -> ParentColumn: metadata = kwargs.pop("metadata", None) assert not kwargs, "Unexpected kwargs where passed: %s" % kwargs sc = get_active_spark_context() if len(alias) == 1: if metadata is not None: assert sc._jvm is not None jmeta = getattr(sc._jvm, "org.apache.spark.sql.types.Metadata").fromJson( json.dumps(metadata) ) return Column(getattr(self._jc, "as")(alias[0], jmeta)) else: return Column(getattr(self._jc, "as")(alias[0])) else: if metadata is not None: raise PySparkValueError( errorClass="ONLY_ALLOWED_FOR_SINGLE_COLUMN", messageParameters={"arg_name": "metadata"}, ) return Column(getattr(self._jc, "as")(_to_seq(sc, list(alias)))) def name(self, *alias: str, **kwargs: Any) -> ParentColumn: return self.alias(*alias, **kwargs) def cast(self, dataType: Union[DataType, str]) -> ParentColumn: if isinstance(dataType, str): jc = self._jc.cast(dataType) elif isinstance(dataType, DataType): from pyspark.sql import SparkSession spark = SparkSession._getActiveSessionOrCreate() jdt = spark._jsparkSession.parseDataType(dataType.json()) jc = self._jc.cast(jdt) else: raise PySparkTypeError( errorClass="NOT_EXPECTED_TYPE", messageParameters={ "expected_type": "DataType or str", "arg_name": "dataType", "arg_type": type(dataType).__name__, }, ) return Column(jc) def try_cast(self, dataType: Union[DataType, str]) -> ParentColumn: if isinstance(dataType, str): jc = self._jc.try_cast(dataType) elif isinstance(dataType, DataType): from pyspark.sql import SparkSession spark = SparkSession._getActiveSessionOrCreate() jdt = spark._jsparkSession.parseDataType(dataType.json()) jc = self._jc.try_cast(jdt) else: raise PySparkTypeError( errorClass="NOT_EXPECTED_TYPE", messageParameters={ "expected_type": "DataType or str", "arg_name": "dataType", "arg_type": type(dataType).__name__, }, ) return Column(jc) def astype(self, dataType: Union[DataType, str]) -> ParentColumn: return self.cast(dataType) def between( self, lowerBound: Union[ParentColumn, "LiteralType", "DateTimeLiteral", "DecimalLiteral"], upperBound: Union[ParentColumn, "LiteralType", "DateTimeLiteral", "DecimalLiteral"], ) -> ParentColumn: return (self >= lowerBound) & (self <= upperBound) def when(self, condition: ParentColumn, value: Any) -> ParentColumn: if not isinstance(condition, Column): raise PySparkTypeError( errorClass="NOT_EXPECTED_TYPE", messageParameters={ "expected_type": "Column", "arg_name": "condition", "arg_type": type(condition).__name__, }, ) v = value._jc if isinstance(value, Column) else enum_to_value(value) jc = self._jc.when(condition._jc, v) return Column(jc) def otherwise(self, value: Any) -> ParentColumn: v = value._jc if isinstance(value, Column) else enum_to_value(value) jc = self._jc.otherwise(v) return Column(jc) def over(self, window: "WindowSpec") -> ParentColumn: from pyspark.sql.classic.window import WindowSpec if not isinstance(window, WindowSpec): raise PySparkTypeError( errorClass="NOT_EXPECTED_TYPE", messageParameters={ "arg_name": "window", "expected_type": "WindowSpec", "arg_type": type(window).__name__, }, ) jc = self._jc.over(window._jspec) return Column(jc) def transform(self, f: Callable[[ParentColumn], ParentColumn]) -> ParentColumn: return f(self) def outer(self) -> ParentColumn: jc = self._jc.outer() return Column(jc) def __nonzero__(self) -> None: try: column_repr = self._jc.toString() except Exception: column_repr = "<unknown>" raise PySparkValueError( errorClass="CANNOT_CONVERT_COLUMN_INTO_BOOL", messageParameters={"column": column_repr}, ) __bool__ = __nonzero__ def __repr__(self) -> str: return "Column<'%s'>" % self._jc.toString() def _test() -> None: import doctest from pyspark.sql import SparkSession import pyspark.sql.column # It inherits docstrings but doctests cannot detect them so we run # the parent classe's doctests here directly. globs = pyspark.sql.column.__dict__.copy() spark = ( SparkSession.builder.master("local[4]").appName("sql.classic.column tests").getOrCreate() ) globs["spark"] = spark failure_count, test_count = doctest.testmod( pyspark.sql.column, globs=globs, optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF, ) spark.stop() if failure_count: sys.exit(-1) if __name__ == "__main__": _test()