DataFusion 已提供大量表达式和内置函数,但有些计算仍需要接入自己的逻辑。Python 自定义函数可以承担这项工作:标量函数处理每行的值,聚合函数合并一组输入,窗口函数则在窗口分区上计算。这里最容易混淆的一点是,标量 UDF 的语义虽然逐行独立,调用接口却一次接收整批 Arrow 数组。
本文完整译写 Apache DataFusion Python 官方 User-Defined Functions,核对日期为 2026 年 10 月 5 日。原页未署个人作者,归属 Apache Software Foundation 及 DataFusion 贡献者。文中保留示例、性能边界与扩展入口;发现的指南与当前源码差异单独注明。所有代码仅静态审阅,示例输出引用原文或明确标为推算,没有进行本地运行。

标量函数:一批数组进,一批数组出
调用 udf() 可以把 Python 函数定义为标量 UDF。函数接收一个或多个 PyArrow 数组,并返回一个数组;批内各行仍应按逐行逻辑计算。下面检查列 a 中哪些位置是空值:
import pyarrow
import datafusion
from datafusion import udf, col
def is_null(array: pyarrow.Array) -> pyarrow.Array:
return array.is_null()
is_null_arr = udf(is_null, [pyarrow.int64()], pyarrow.bool_(), "stable")
ctx = datafusion.SessionContext()
batch = pyarrow.RecordBatch.from_arrays(
[pyarrow.array([1, None, 3]), pyarrow.array([4, 5, 6])],
names=["a", "b"],
)
df = ctx.create_dataframe([[batch]], name="batch_array")
df.select(col("a"), is_null_arr(col("a")).alias("is_null")).show()
原文展示的结果如下:空值位置返回 true,其他位置返回 false。
+---+---------+
| a | is_null |
+---+---------+
| 1 | false |
| | true |
| 3 | false |
+---+---------+
示例直接使用数组的 is_null()。PyArrow 还有许多 compute 函数;能用它们实现时,应优先让计算留在 Arrow 数据表示中,避免先逐项转成 Python 对象。原文强调,这能减少数组到 Python 值往返转换和相关复制开销。具体算子仍可能分配输出缓冲区,不能把这理解成所有 Arrow 运算都“零分配”。
如果目标操作没有对应的 Arrow compute 函数,就可能需要取出 Python 值、执行自己的计算,再构造输出数组。下面与前一例含义相同,但逐项调用 as_py():
import pyarrow
import datafusion
from datafusion import udf, col
def is_null(array: pyarrow.Array) -> pyarrow.Array:
return pyarrow.array([value.as_py() is None for value in array])
is_null_arr = udf(is_null, [pyarrow.int64()], pyarrow.bool_(), "stable")
ctx = datafusion.SessionContext()
batch = pyarrow.RecordBatch.from_arrays(
[pyarrow.array([1, None, 3]), pyarrow.array([4, 5, 6])],
names=["a", "b"],
)
df = ctx.create_dataframe([[batch]], name="batch_array")
df.select(col("a"), is_null_arr(col("a")).alias("is_null")).show()
原文给出的输出与上一表一致,但执行路径不同。把 Arrow 值转成 Python 对象可能成为 DataFusion 工作负载中最慢的环节之一,因此应尽量减少。批量调用并不自动意味着批内计算已经向量化。
上面注册时传入的是 PyArrow DataType。需要控制元数据、输入或输出的可空性时,可改用 Field。函数的波动性也应真实反映语义:immutable 表示相同输入始终得到相同结果;stable 表示同一查询内稳定;volatile 则可能在各次调用间变化。不能为了优化随意把外部查询或随机计算声明成不可变。
如果仍需要自定义计算,又希望避免 Python 对象往返转换,可用 Rust 实现并暴露给 Python。原文链接了 DataFusion 的 Rust/Python UDF 比较文章。这是进一步阅读入口,不在本文建立一套不完整的 Rust 编译流程。
什么时候不应该写 UDF
适合 UDF 的,是确实无法用内置表达式树描述的逐行计算。如果筛选条件本来可以由列、常量和布尔运算表达,只是写成 Python 循环更顺手,UDF 可能让查询变慢。优化器看不到 UDF 内部的规则,因此会失去原生表达式可用的一些改写机会,尤其是向表提供器下推可分析的过滤条件。
以 Parquet 为例,原生条件可以借助文件尾部的最小值、最大值及其他统计信息排除不可能匹配的行组。类似机制也适用于声明支持过滤的其他表提供器。下面先创建一个小 Parquet 文件,然后比较两种写法的计划。这个例子关注计划结构,不是性能基准:
import tempfile, os
import pyarrow as pa
import pyarrow.parquet as pq
from datafusion import SessionContext, col, lit, udf
tmpdir = tempfile.mkdtemp()
parquet_path = os.path.join(tmpdir, "items.parquet")
pq.write_table(
pa.table({
"id": list(range(100)),
"brand": ["A", "B", "C", "D"] * 25,
"qty": [i * 10 for i in range(100)],
}),
parquet_path,
)
ctx = SessionContext()
items = ctx.read_parquet(parquet_path)
native_filtered = items.filter(
(col("brand") == lit("A")) & (col("qty") >= lit(150))
)
print(native_filtered.execution_plan().display_indent())
原文计划包含 FilterExec、RepartitionExec 和 DataSourceExec。扫描节点上最值得关注的是三个字段:
predicate=brand@1 = A AND qty@2 >= 150:扫描器收到过滤条件。pruning_predicate=… brand_min@0 <= A AND A <= brand_max@1 … qty_max@4 >= 150:可用行组统计信息排除不匹配的行组,原文中还带有空值计数判断。required_guarantees=[brand in (A)]:在具备相应 Bloom filter 或字典信息时,可用于进一步跳过数据。
这些字段证明优化器能理解条件,但不保证任意文件一定少读。100 行的小文件可能只有一个行组;若统计范围同时覆盖 A 和 B、数量范围也与阈值重叠,就未必能剪掉它。实际收益取决于文件布局、统计信息、引擎版本与配置。
再把同样的规则包装为 Python UDF:
def brand_qty_filter(brand_arr: pa.Array, qty_arr: pa.Array) -> pa.Array:
return pa.array([
b.as_py() == "A" and q.as_py() >= 150
for b, q in zip(brand_arr, qty_arr)
])
pred_udf = udf(
brand_qty_filter, [pa.string(), pa.int64()], pa.bool_(), "stable",
)
udf_filtered = items.filter(pred_udf(col("brand"), col("qty")))
print(udf_filtered.execution_plan().display_indent())
原文展示的扫描节点仍可能带 predicate=brand_qty_filter(...),但没有 pruning_predicate 或 required_guarantees。所以“UDF 不能下推”更精确的含义是:优化器无法把函数内部规则变成可用于统计剪枝的原生条件;并非任何版本的扫描节点都绝不会出现 UDF 谓词。对于本例中的这条 UDF 规则,引擎需要读出候选行组并交给回调判断。
编辑核对:原始数据没有空值。如果品牌为 A 而数量为 null,原样回调会尝试比较 None >= 150,产生异常。生产版本必须定义空值语义并补充处理,不能仅凭无空值样例推定与 SQL 的三值逻辑完全等价。临时目录由 mkdtemp() 创建,示例未清理;应在数据读取和实际执行完成后再清理,避免惰性查询尚未执行就删除文件。
原文的完整执行计划对照
以下两段为原文展示的计划,依次对应原生表达式和 Python UDF;路径、并行分区数及格式属于该次上游示例,不是本稿运行所得。
FilterExec: brand@1 = A AND qty@2 >= 150
RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1
DataSourceExec: file_groups={1 group: [[tmp/tmpsywpupmx/items.parquet]]}, projection=[id, brand, qty], file_type=parquet, predicate=brand@1 = A AND qty@2 >= 150, pruning_predicate=brand_null_count@2 != row_count@3 AND brand_min@0 <= A AND A <= brand_max@1 AND qty_null_count@5 != row_count@3 AND qty_max@4 >= 150, required_guarantees=[brand in (A)]
FilterExec: brand_qty_filter(CAST(brand@1 AS Utf8), qty@2)
RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1
DataSourceExec: file_groups={1 group: [[tmp/tmpsywpupmx/items.parquet]]}, projection=[id, brand, qty], file_type=parquet, predicate=brand_qty_filter(CAST(brand@1 AS Utf8), qty@2)
多组业务规则仍然可以组合成表达式
如果条件是“满足任意一组品牌规则即可”,可先为每组构建一个 AND 子句,再用 | 合成 OR。下面保留原文规则:
from functools import reduce
from operator import or_
from datafusion import col, lit, functions as f
buckets = {
"Brand#12": {"containers": ["SM CASE", "SM BOX"], "min_qty": 1, "max_size": 5},
"Brand#23": {"containers": ["MED BAG", "MED BOX"], "min_qty": 10, "max_size": 10},
}
def bucket_clause(brand, spec):
return (
(col("brand") == lit(brand))
& f.in_list(col("container"), [lit(c) for c in spec["containers"]])
& (col("quantity") >= lit(spec["min_qty"]))
& (col("quantity") <= lit(spec["min_qty"] + 10))
& (col("size") >= lit(1))
& (col("size") <= lit(spec["max_size"]))
)
predicate = reduce(or_, (bucket_clause(b, s) for b, s in buckets.items()))
df = df.filter(predicate)
这是表达式拼接片段,不可直接接在前面的两列 df 或 items 后运行:输入需包含 brand、container、quantity、size。原文的 buckets 非空;若配置允许空规则集,应先决定它表示“保留全部”还是“不保留任何行”,再提供相应恒真或恒假初值,避免 reduce() 在空序列上失败。
聚合函数:把更新、状态和合并分开
udaf() 用于注册自定义聚合函数,需要实现 Accumulator。它包含四个核心方法:update 接收输入数组并更新局部状态;state 返回表示当前状态的标量列表;merge 合并多个局部状态;evaluate 给出最终标量结果。聚合可能跨多个批次和分区,因而“能更新一个批次”还不够,状态必须可以正确合并。
下面的原文示例计算两列总和之差,内部状态只需一个数:
import pyarrow as pa
import pyarrow.compute
import datafusion
from datafusion import col, udaf, Accumulator
class MyAccumulator(Accumulator):
def __init__(self):
self._sum = 0.0
def update(self, values_a: pa.Array, values_b: pa.Array) -> None:
self._sum = (self._sum
+ pyarrow.compute.sum(values_a).as_py()
- pyarrow.compute.sum(values_b).as_py())
def merge(self, states: list[pa.Array]) -> None:
self._sum = self._sum + pyarrow.compute.sum(states[0]).as_py()
def state(self) -> list[pa.Scalar]:
return [pyarrow.scalar(self._sum)]
def evaluate(self) -> pa.Scalar:
return pyarrow.scalar(self._sum)
ctx = datafusion.SessionContext()
df = ctx.from_pydict({"a": [4, 5, 6], "b": [1, 2, 3]})
my_udaf = udaf(
MyAccumulator, [pa.float64(), pa.float64()], pa.float64(),
[pa.float64()], "stable",
)
df.aggregate([], [my_udaf(col("a"), col("b")).alias("col_diff")])
样例中的结果按算术可推得为 9,但原文最后一行只构造 DataFrame;若要观察运行结果,还需调用 show() 或其他收集方法。这里没有把推算值伪装成本次实测输出。
编辑核对:原实现没有空批次或全空值保护,Arrow sum 的结果可能是 null,转成 Python 后为 None,再参与加减会失败。若业务选择“空批次与全空值贡献为零”,可将每个求和结果显式转换成 0.0 if total is None else total;这是新增业务约定,会改变“全空值返回 null”的语义,不应不加说明地替换原代码。浮点状态还可能因分区合并顺序出现舍入差异。
聚合函数怎样返回列表
evaluate 返回标量,state 返回标量列表;如果想让一个标量本身包含列表,应使用 Arrow 列表标量。例如时间戳列表可写成 pa.scalar([...], type=pa.list_(pa.timestamp("ms"))),相应注册类型为 return_type=pa.list_(pa.timestamp("ms")),或 state_type=[pa.list_(pa.timestamp("ms"))]。不要把“状态的标量列表”与“列表类型的单个标量”混为一谈。
原文说明,自 DataFusion 52.0.0 起,这些返回位置还可接收普通 Python 对象或 PyArrow 数组,由 DataFusion 尝试转成标量,已覆盖 PyArrow、nanoarrow、arro3 和常见基础类型。这个兼容说明有明确版本下限;显式构造正确类型的标量仍更容易核对。
窗口函数:选择合适的求值接口
自定义窗口函数通过 udwf() 注册实现 WindowEvaluator 的求值器。evaluate 针对单行所需范围求值,最直接但调用开销较高;evaluate_all 一次计算整个输入分区;evaluate_all_with_rank 则只依赖行的排名信息。
uses_window_frame 表示是否使用指定窗口框架,supports_bounded_execution 表示能否以有界内存增量计算,include_rank 表示能否只用排名求值。下表按核验时的 WindowEvaluator 源码整理:
| uses_window_frame | supports_bounded_execution | include_rank | 应实现 |
|---|---|---|---|
| False | False | False | evaluate_all |
| False | True | False | evaluate |
| False | True 或 False | True | evaluate_all_with_rank |
| True | True 或 False | True 或 False | evaluate |
与原文的差异:指南第三行误列为 False / True / False,与前一行冲突。当前源码将排名求值行写为 False / True或False / True,上表使用后者。版本升级时仍需核对安装版本的 API。
原文的指数平滑从首值开始,后续按“当前输入 × α + 上次结果 × (1 − α)”递推。以下保留计算主体,同时把注册改成当前接口要求的求值器工厂:
import pyarrow as pa
from datafusion import udwf, col, SessionContext
from datafusion.user_defined import WindowEvaluator
class ExponentialSmooth(WindowEvaluator):
def __init__(self, alpha: float) -> None:
self.alpha = alpha
def evaluate_all(self, values: list[pa.Array], num_rows: int) -> pa.Array:
results = []
curr_value = 0.0
values = values[0]
for idx in range(num_rows):
if idx == 0:
curr_value = values[idx].as_py()
else:
curr_value = values[idx].as_py() * self.alpha + curr_value * (
1.0 - self.alpha
)
results.append(curr_value)
return pa.array(results)
def make_exponential_smooth():
return ExponentialSmooth(0.9)
exp_smooth = udwf(
make_exponential_smooth, pa.float64(), pa.float64(),
volatility="immutable",
)
ctx = SessionContext()
df = ctx.from_pydict({"a": [1.0, 2.1, 2.9, 4.0, 5.1, 6.0, 6.9, 8.0]})
df.select("a", exp_smooth(col("a")).alias("smooth_a")).show()
注册修正依据:原文传入 ExponentialSmooth(0.9) 实例;当前 WindowUDF._create_window_udf 要求参数可调用,并调用它来创建 WindowEvaluator。因此这里使用具名工厂函数。此修正只做了接口级静态核对,未实测。样例没有空值、没有显式排序,也没有验证 α 的范围;真实时间序列应明确排序及分区、定义空值策略,并按业务约束验证参数。空输入返回数组的显式类型也需要在完整实现中处理。
表函数与调用会话
自定义表函数 UDTF 与前面的函数有所不同:它接收任意数量的 Expr 参数,但当前仅支持字面量表达式;返回值必须是 自定义表提供器。定义后可通过 SessionContext.register_udtf() 注册。仓库 examples 中包含 Python 与 Rust 实现。
Rust 表函数通过 PyO3 暴露时,原文展示了下面的 PyCapsule 接口片段。它缺少类型定义、依赖及构建配置,仅说明接口形状,不能单独复制运行:
#[pymethods]
impl MyTableFunction {
fn __datafusion_table_function__<'py>(
&self,
py: Python<'py>,
) -> PyResult<Bound<'py, PyCapsule>> {
let name = cr"datafusion_table_function".into();
let func = self.clone();
let provider = FFI_TableFunction::new(Arc::new(func), None);
PyCapsule::new(py, provider, Some(name))
}
}
纯 Python 表函数还可以通过 with_session=True 接收调用方的 SessionContext。每次调用时,框架会把它作为 session 关键字参数传入,函数即可查询注册表、UDF 或会话配置:
from datafusion import SessionContext, Table, udtf
from datafusion.context import TableProviderExportable
import pyarrow as pa
import pyarrow.dataset as ds
@udtf("list_tables", with_session=True)
def list_tables(*, session: SessionContext) -> TableProviderExportable:
names = sorted(session.catalog().schema().names())
batch = pa.RecordBatch.from_pydict({"name": names})
return Table(ds.dataset([batch]))
ctx = SessionContext()
ctx.register_batch("t1", pa.RecordBatch.from_pydict({"x": [1]}))
ctx.register_udtf(list_tables)
ctx.sql("SELECT * FROM list_tables()").show()
不启用 with_session 时,回调只接收位置表达式参数,原有 UDTF 行为保持不变。注入的对象是一个新的包装器,但底层注册表与调用方共享:注册新表或新 UDF 会传播到活动会话;配置则是克隆,修改该包装器中的会话配置不会回传。当前源码还限制这个选项只能用于纯 Python 回调,不能直接与 FFI 表函数混用。空目录时,例子中 name 列会由空列表推断类型,正式实现可以明确指定字符串类型。
选择 UDF 时,可以先检查两个问题:内置表达式是否已经能表示这项计算,输入是否必须离开 Arrow 表示。原生表达式有利于优化器分析,Arrow 内核有利于避免 Python 对象转换;确实需要自定义计算时,再选择相应的标量、聚合、窗口或表函数接口,并把状态、空值、排序和版本边界一起定义清楚。
许可与修改声明:项目源码按 Apache License 2.0 提供,许可全文保留在下方 LICENSE.txt。本稿于 2026-10-05 翻译、重排并加入静态审阅说明;未修改原项目。Apache Arrow DataFusion、Arrow DataFusion、Apache 及相关羽毛与项目标志是 Apache Software Foundation 在美国及其他国家的注册商标或商标。本文未使用项目标志。原许可按“现状”提供,不作保证。
版权与许可全文
以下保留本页涉及的来源材料或示例代码的版权、许可条件与免责声明;各自适用范围依原声明。中文翻译及编辑标注:未完纪,2026-10-05。
LICENSE.txt
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed 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.













暂无评论内容