Repository files navigation

AnyFrame

AnyFrame 是一个面向 Python 的运行时特性组合框架。

它的核心思路不是“先造对象、再把对象注册到容器里”,而是:

  • Feature 子类声明能力
  • 用继承表达“这个能力是另一个能力的特化版本”
  • requires 表达“我不继承它,但我运行时依赖它的能力”
  • use() 把多个 Feature 组合成最终可实例化的类

安装

AnyFrame 目前要求 Python 3.12 及以上版本。

如果你在另一个 uv 管理的项目里通过 Git 引入它,推荐使用:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git"

如果你想固定到某个 tag 或 commit,也可以写成:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git@79639e2c4bc812b9c4cf0fc87105f51e847f67c5"

如果你只是临时安装到当前虚拟环境,而不修改项目依赖声明,可以使用:

uv pip install "git+https://github.com/ncatbot/AnyFrame.git"

心智模型

在 AnyFrame 里,Feature 的类对象本身就是身份。

你可以把一个 Feature 理解成“能力片段”或“运行时 trait”:

  • 它可以自己提供方法和属性
  • 它可以依赖别的 Feature
  • 它可以被别的 Feature 继承
  • 它也可以在最终运行时被组合进一个新的类

框架目前支持三种最重要的 Feature 形态。

三类 Feature

1. 独立 Feature

独立 Feature 不依赖其他 Feature,自己就是一个完整能力单元。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")

这种类型通常适合作为最底层的基础能力。

2. 扩展型 Feature

如果一个 Feature 需要保留父类的大部分能力,并重写或补充一部分行为,那么直接继承父类即可。

这种场景下不需要重复写 requires,因为框架会把继承关系视为隐式依赖。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classJsonLogging(Logging):
deflog(self, message: str) ->None:
super().log(f'{{"message": "{message}"}}')

这里的语义是:

  • JsonLoggingLogging 的特化版本
  • 它沿用父类的大部分能力
  • 它可以通过 super() 复用父类实现

3. 依赖型 Feature

依赖型 Feature 不继承别的 Feature,但它运行时需要别的能力,因此通过 requires 声明依赖。

它的内部可以直接通过 self.xxx 使用依赖提供的成员。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(f"audit: {message}")

这个 Audit 在语义上不是一个推荐单独使用的最终类。 它更像一个“等待被组合”的能力片段:只有在最终类里确实混入了 Loggingself.log(...) 才有意义。

这也是 AnyFrame 的一个重要哲学:

  • 继承表达“我是某个 Feature 的特化版本”
  • requires 表达“我不是它的子类,但我运行时需要它的能力”

一个完整例子

下面这个例子把三种思路串起来。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAuth(Feature):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
classAPI(Feature):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")
App=Feature.use(API, name="App")
app=App()
app.call("alice")

这里发生了几件事:

  • API 直接使用了 AuthLogging 的能力,所以显式依赖它们两者
  • Auth 依赖 Logging
  • Feature.use(API, name="App") 会解析整条依赖链
  • 框架生成一个新的最终类 App
  • App 的实例可以直接使用 APIAuthLogging 提供的能力

组合是怎么工作的

use() 是框架最核心的入口。

App=Feature.use(API, Logging)

或:

App=API.use(Logging)

它们都会返回一个新的类,而不是实例。

你可以把 use() 理解成“类组合器”:

  • 输入是若干个 Feature
  • 框架先解析依赖关系
  • 自动去重
  • 检查冲突
  • 最后构造一个新的合成类

匿名组合还会被缓存复用,因此:

Feature.use(A).use(B)

会和:

Feature.use(A, B)

表现一致。

requiresconflicts 的写法

requiresconflicts 现在都支持下面这些写法:

classA(Feature):
passclassB(Feature):
requires= (A)
classC(Feature):
requires= (A,)
classD(Feature):
conflicts= (A)

注意:

  • requires = (A) 在 Python 语法里其实等价于 requires = A
  • 框架会把它归一化成单元素依赖声明
  • 多个依赖时请写成真正的 tuple,例如 requires = (A, B)

公开 API

框架当前公开的 API 很小,主要就是下面这些。

Feature

所有能力单元的基类。

可用的类级接口:

  • requires 声明依赖的 Feature。可以是单个 Feature,也可以是多个 Feature 的 tuple。
  • conflicts 声明互斥的 Feature。当冲突双方同时出现在最终组合中时,会报错。
  • Feature.use(*features, name=None) 组合多个 Feature,返回一个新的类。
  • Feature.dependency_closure() 返回当前 Feature 的完整依赖闭包,包含它自己。
  • Feature.installed_features() 返回当前 Feature 实际安装的能力列表,不包含它自己,只包含依赖和被组合进去的其他 Feature

resolve_features(*features)

只做依赖解析,不创建新类。

它会返回去重后的安装顺序,顺序保证:

  • 依赖总是在依赖者之前
  • 每个 Feature 只出现一次

如果你只是想知道最终会装入哪些 Feature,或者想调试依赖图,resolve_features() 很有用。

异常类型

  • FeatureCompositionError 所有组合错误的基类。
  • FeatureConflictError 当最终组合包含显式互斥的 Feature 时抛出。
  • FeatureDependencyCycleError 当依赖图中存在环时抛出。
  • FeatureMemberConflictError 当两个互不相关的 Feature 声明了同名运行时成员时抛出。

框架保证了什么

AnyFrame 在组合前会做几类检查。

1. 依赖去重

如果多个 Feature 依赖同一个基础能力,它只会安装一次。

2. 依赖顺序正确

依赖解析使用后序 DFS,因此安装顺序总是“依赖在前,功能在后”。

3. 依赖环检测

如果 A 依赖 B,B 又依赖 A,会抛出 FeatureDependencyCycleError

4. 显式冲突检测

如果某个 Featureconflicts 里声明了另一个 Feature,而两者同时出现在最终组合中,会抛出 FeatureConflictError

5. 成员名冲突检测

如果两个没有依赖关系的 Feature 都声明了同名成员,会抛出 FeatureMemberConflictError

这个规则的目的,是避免两个互不相关的能力片段无意间抢同一个运行时名字。

但如果它们本来就在依赖链上,那么后者有意覆盖前者是允许的。

关于类型检查和 LSP

依赖型 Feature 很可能会写出下面这样的代码:

classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(message)

这在运行时是可行的,因为最终组合类会提供 log()。 但静态分析器并不知道 self 将来一定会被组合进什么样的最终类,所以 LSP 或类型检查工具可能会报警。

这是这个框架目前的已知特性,不是运行时 bug。

如果你希望既保留 requires 的运行时语义,又让 IDE 认识依赖成员,一个比较实用的办法是配合 TYPE_CHECKING 提供一个“只给类型系统看”的基类:

fromtypingimportTYPE_CHECKINGfromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
ifTYPE_CHECKING:
class_AuthBase(Logging):
passelse:
_AuthBase=FeatureclassAuth(_AuthBase):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
ifTYPE_CHECKING:
class_APIBase(Auth, Logging):
passelse:
_APIBase=FeatureclassAPI(_APIBase):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")

这个写法的关键点是:

  • TYPE_CHECKING 分支只参与静态分析,不参与运行时执行
  • 每个直接使用依赖成员的 Feature 都可以声明一个只给 IDE 看的辅助基类
  • IDE 会把 Auth 看成拥有 Logging 的成员,也会把 API 看成同时拥有 AuthLogging 提供的成员
  • 运行时里 API 仍然只是继承 Feature,真实依赖关系依然由 requires 决定

使用这个技巧时,建议注意三点:

  • else 分支最好写成 _AuthBase = Feature_APIBase = Feature 这样的别名,而不是再定义新的 Feature 子类。这样不会把这些辅助基类误带入运行时语义。
  • requires 继续表达“真实依赖”,也就是这个 Feature 自己直接使用到的能力。上面的 API 里既调用了 self.login(),也调用了 self.log(),所以应该显式声明 AuthLogging
  • 这个技巧通常需要逐层使用。API 的辅助基类不会自动让 Auth 内部的 self.log(...) 获得类型提示,因此 Auth 自己也需要对应的 _AuthBase

如果你不想引入这种双轨写法,也可以考虑:

  • 在项目内接受这类动态组合带来的局部告警
  • Protocolcast() 给依赖成员补充静态提示
  • 把依赖访问收敛到少量辅助方法里,减少裸 self.xxx 的分布范围

命名组合与匿名组合

use() 支持两种模式。

匿名组合

App=Feature.use(API, Logging)

这种方式适合临时组合。相同输入的匿名组合会被缓存复用。

命名组合

App=Feature.use(API, Logging, name="App")

这种方式适合你想要一个稳定、可复用、可导入的最终类。

什么时候该用继承,什么时候该用 requires

可以用下面这条经验法则。

  • 如果你想表达“这是另一个 Feature 的特化版本”,用继承
  • 如果你想表达“我不是它的子类,但我运行时需要它提供的方法/属性”,用 requires
  • 如果你想表达“这是最终可运行的组合结果”,用 use()

当前状态

AnyFrame 现在的公开 API 很小,重点在:

  • 保持组合模型足够直接
  • 把依赖解析和错误边界做清楚
  • 让运行时类组合比手写多重继承更可控

如果你在阅读源码,建议先从 FeatureFeature.use()resolve_features() 开始看。

About

用于开发类似FastApi语法框架的元框架

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

AnyFrame

AnyFrame 是一个面向 Python 的运行时特性组合框架。

它的核心思路不是“先造对象、再把对象注册到容器里”,而是:

  • Feature 子类声明能力
  • 用继承表达“这个能力是另一个能力的特化版本”
  • requires 表达“我不继承它,但我运行时依赖它的能力”
  • use() 把多个 Feature 组合成最终可实例化的类

安装

AnyFrame 目前要求 Python 3.12 及以上版本。

如果你在另一个 uv 管理的项目里通过 Git 引入它,推荐使用:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git"

如果你想固定到某个 tag 或 commit,也可以写成:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git@79639e2c4bc812b9c4cf0fc87105f51e847f67c5"

如果你只是临时安装到当前虚拟环境,而不修改项目依赖声明,可以使用:

uv pip install "git+https://github.com/ncatbot/AnyFrame.git"

心智模型

在 AnyFrame 里,Feature 的类对象本身就是身份。

你可以把一个 Feature 理解成“能力片段”或“运行时 trait”:

  • 它可以自己提供方法和属性
  • 它可以依赖别的 Feature
  • 它可以被别的 Feature 继承
  • 它也可以在最终运行时被组合进一个新的类

框架目前支持三种最重要的 Feature 形态。

三类 Feature

1. 独立 Feature

独立 Feature 不依赖其他 Feature,自己就是一个完整能力单元。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")

这种类型通常适合作为最底层的基础能力。

2. 扩展型 Feature

如果一个 Feature 需要保留父类的大部分能力,并重写或补充一部分行为,那么直接继承父类即可。

这种场景下不需要重复写 requires,因为框架会把继承关系视为隐式依赖。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classJsonLogging(Logging):
deflog(self, message: str) ->None:
super().log(f'{{"message": "{message}"}}')

这里的语义是:

  • JsonLoggingLogging 的特化版本
  • 它沿用父类的大部分能力
  • 它可以通过 super() 复用父类实现

3. 依赖型 Feature

依赖型 Feature 不继承别的 Feature,但它运行时需要别的能力,因此通过 requires 声明依赖。

它的内部可以直接通过 self.xxx 使用依赖提供的成员。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(f"audit: {message}")

这个 Audit 在语义上不是一个推荐单独使用的最终类。 它更像一个“等待被组合”的能力片段:只有在最终类里确实混入了 Loggingself.log(...) 才有意义。

这也是 AnyFrame 的一个重要哲学:

  • 继承表达“我是某个 Feature 的特化版本”
  • requires 表达“我不是它的子类,但我运行时需要它的能力”

一个完整例子

下面这个例子把三种思路串起来。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAuth(Feature):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
classAPI(Feature):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")
App=Feature.use(API, name="App")
app=App()
app.call("alice")

这里发生了几件事:

  • API 直接使用了 AuthLogging 的能力,所以显式依赖它们两者
  • Auth 依赖 Logging
  • Feature.use(API, name="App") 会解析整条依赖链
  • 框架生成一个新的最终类 App
  • App 的实例可以直接使用 APIAuthLogging 提供的能力

组合是怎么工作的

use() 是框架最核心的入口。

App=Feature.use(API, Logging)

或:

App=API.use(Logging)

它们都会返回一个新的类,而不是实例。

你可以把 use() 理解成“类组合器”:

  • 输入是若干个 Feature
  • 框架先解析依赖关系
  • 自动去重
  • 检查冲突
  • 最后构造一个新的合成类

匿名组合还会被缓存复用,因此:

Feature.use(A).use(B)

会和:

Feature.use(A, B)

表现一致。

requiresconflicts 的写法

requiresconflicts 现在都支持下面这些写法:

classA(Feature):
passclassB(Feature):
requires= (A)
classC(Feature):
requires= (A,)
classD(Feature):
conflicts= (A)

注意:

  • requires = (A) 在 Python 语法里其实等价于 requires = A
  • 框架会把它归一化成单元素依赖声明
  • 多个依赖时请写成真正的 tuple,例如 requires = (A, B)

公开 API

框架当前公开的 API 很小,主要就是下面这些。

Feature

所有能力单元的基类。

可用的类级接口:

  • requires 声明依赖的 Feature。可以是单个 Feature,也可以是多个 Feature 的 tuple。
  • conflicts 声明互斥的 Feature。当冲突双方同时出现在最终组合中时,会报错。
  • Feature.use(*features, name=None) 组合多个 Feature,返回一个新的类。
  • Feature.dependency_closure() 返回当前 Feature 的完整依赖闭包,包含它自己。
  • Feature.installed_features() 返回当前 Feature 实际安装的能力列表,不包含它自己,只包含依赖和被组合进去的其他 Feature

resolve_features(*features)

只做依赖解析,不创建新类。

它会返回去重后的安装顺序,顺序保证:

  • 依赖总是在依赖者之前
  • 每个 Feature 只出现一次

如果你只是想知道最终会装入哪些 Feature,或者想调试依赖图,resolve_features() 很有用。

异常类型

  • FeatureCompositionError 所有组合错误的基类。
  • FeatureConflictError 当最终组合包含显式互斥的 Feature 时抛出。
  • FeatureDependencyCycleError 当依赖图中存在环时抛出。
  • FeatureMemberConflictError 当两个互不相关的 Feature 声明了同名运行时成员时抛出。

框架保证了什么

AnyFrame 在组合前会做几类检查。

1. 依赖去重

如果多个 Feature 依赖同一个基础能力,它只会安装一次。

2. 依赖顺序正确

依赖解析使用后序 DFS,因此安装顺序总是“依赖在前,功能在后”。

3. 依赖环检测

如果 A 依赖 B,B 又依赖 A,会抛出 FeatureDependencyCycleError

4. 显式冲突检测

如果某个 Featureconflicts 里声明了另一个 Feature,而两者同时出现在最终组合中,会抛出 FeatureConflictError

5. 成员名冲突检测

如果两个没有依赖关系的 Feature 都声明了同名成员,会抛出 FeatureMemberConflictError

这个规则的目的,是避免两个互不相关的能力片段无意间抢同一个运行时名字。

但如果它们本来就在依赖链上,那么后者有意覆盖前者是允许的。

关于类型检查和 LSP

依赖型 Feature 很可能会写出下面这样的代码:

classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(message)

这在运行时是可行的,因为最终组合类会提供 log()。 但静态分析器并不知道 self 将来一定会被组合进什么样的最终类,所以 LSP 或类型检查工具可能会报警。

这是这个框架目前的已知特性,不是运行时 bug。

如果你希望既保留 requires 的运行时语义,又让 IDE 认识依赖成员,一个比较实用的办法是配合 TYPE_CHECKING 提供一个“只给类型系统看”的基类:

fromtypingimportTYPE_CHECKINGfromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
ifTYPE_CHECKING:
class_AuthBase(Logging):
passelse:
_AuthBase=FeatureclassAuth(_AuthBase):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
ifTYPE_CHECKING:
class_APIBase(Auth, Logging):
passelse:
_APIBase=FeatureclassAPI(_APIBase):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")

这个写法的关键点是:

  • TYPE_CHECKING 分支只参与静态分析,不参与运行时执行
  • 每个直接使用依赖成员的 Feature 都可以声明一个只给 IDE 看的辅助基类
  • IDE 会把 Auth 看成拥有 Logging 的成员,也会把 API 看成同时拥有 AuthLogging 提供的成员
  • 运行时里 API 仍然只是继承 Feature,真实依赖关系依然由 requires 决定

使用这个技巧时,建议注意三点:

  • else 分支最好写成 _AuthBase = Feature_APIBase = Feature 这样的别名,而不是再定义新的 Feature 子类。这样不会把这些辅助基类误带入运行时语义。
  • requires 继续表达“真实依赖”,也就是这个 Feature 自己直接使用到的能力。上面的 API 里既调用了 self.login(),也调用了 self.log(),所以应该显式声明 AuthLogging
  • 这个技巧通常需要逐层使用。API 的辅助基类不会自动让 Auth 内部的 self.log(...) 获得类型提示,因此 Auth 自己也需要对应的 _AuthBase

如果你不想引入这种双轨写法,也可以考虑:

  • 在项目内接受这类动态组合带来的局部告警
  • Protocolcast() 给依赖成员补充静态提示
  • 把依赖访问收敛到少量辅助方法里,减少裸 self.xxx 的分布范围

命名组合与匿名组合

use() 支持两种模式。

匿名组合

App=Feature.use(API, Logging)

这种方式适合临时组合。相同输入的匿名组合会被缓存复用。

命名组合

App=Feature.use(API, Logging, name="App")

这种方式适合你想要一个稳定、可复用、可导入的最终类。

什么时候该用继承,什么时候该用 requires

可以用下面这条经验法则。

  • 如果你想表达“这是另一个 Feature 的特化版本”,用继承
  • 如果你想表达“我不是它的子类,但我运行时需要它提供的方法/属性”,用 requires
  • 如果你想表达“这是最终可运行的组合结果”,用 use()

当前状态

AnyFrame 现在的公开 API 很小,重点在:

  • 保持组合模型足够直接
  • 把依赖解析和错误边界做清楚
  • 让运行时类组合比手写多重继承更可控

如果你在阅读源码,建议先从 FeatureFeature.use()resolve_features() 开始看。

About

用于开发类似FastApi语法框架的元框架

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

AnyFrame

AnyFrame 是一个面向 Python 的运行时特性组合框架。

它的核心思路不是“先造对象、再把对象注册到容器里”,而是:

  • Feature 子类声明能力
  • 用继承表达“这个能力是另一个能力的特化版本”
  • requires 表达“我不继承它,但我运行时依赖它的能力”
  • use() 把多个 Feature 组合成最终可实例化的类

安装

AnyFrame 目前要求 Python 3.12 及以上版本。

如果你在另一个 uv 管理的项目里通过 Git 引入它,推荐使用:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git"

如果你想固定到某个 tag 或 commit,也可以写成:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git@79639e2c4bc812b9c4cf0fc87105f51e847f67c5"

如果你只是临时安装到当前虚拟环境,而不修改项目依赖声明,可以使用:

uv pip install "git+https://github.com/ncatbot/AnyFrame.git"

心智模型

在 AnyFrame 里,Feature 的类对象本身就是身份。

你可以把一个 Feature 理解成“能力片段”或“运行时 trait”:

  • 它可以自己提供方法和属性
  • 它可以依赖别的 Feature
  • 它可以被别的 Feature 继承
  • 它也可以在最终运行时被组合进一个新的类

框架目前支持三种最重要的 Feature 形态。

三类 Feature

1. 独立 Feature

独立 Feature 不依赖其他 Feature,自己就是一个完整能力单元。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")

这种类型通常适合作为最底层的基础能力。

2. 扩展型 Feature

如果一个 Feature 需要保留父类的大部分能力,并重写或补充一部分行为,那么直接继承父类即可。

这种场景下不需要重复写 requires,因为框架会把继承关系视为隐式依赖。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classJsonLogging(Logging):
deflog(self, message: str) ->None:
super().log(f'{{"message": "{message}"}}')

这里的语义是:

  • JsonLoggingLogging 的特化版本
  • 它沿用父类的大部分能力
  • 它可以通过 super() 复用父类实现

3. 依赖型 Feature

依赖型 Feature 不继承别的 Feature,但它运行时需要别的能力,因此通过 requires 声明依赖。

它的内部可以直接通过 self.xxx 使用依赖提供的成员。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(f"audit: {message}")

这个 Audit 在语义上不是一个推荐单独使用的最终类。 它更像一个“等待被组合”的能力片段:只有在最终类里确实混入了 Loggingself.log(...) 才有意义。

这也是 AnyFrame 的一个重要哲学:

  • 继承表达“我是某个 Feature 的特化版本”
  • requires 表达“我不是它的子类,但我运行时需要它的能力”

一个完整例子

下面这个例子把三种思路串起来。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAuth(Feature):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
classAPI(Feature):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")
App=Feature.use(API, name="App")
app=App()
app.call("alice")

这里发生了几件事:

  • API 直接使用了 AuthLogging 的能力,所以显式依赖它们两者
  • Auth 依赖 Logging
  • Feature.use(API, name="App") 会解析整条依赖链
  • 框架生成一个新的最终类 App
  • App 的实例可以直接使用 APIAuthLogging 提供的能力

组合是怎么工作的

use() 是框架最核心的入口。

App=Feature.use(API, Logging)

或:

App=API.use(Logging)

它们都会返回一个新的类,而不是实例。

你可以把 use() 理解成“类组合器”:

  • 输入是若干个 Feature
  • 框架先解析依赖关系
  • 自动去重
  • 检查冲突
  • 最后构造一个新的合成类

匿名组合还会被缓存复用,因此:

Feature.use(A).use(B)

会和:

Feature.use(A, B)

表现一致。

requiresconflicts 的写法

requiresconflicts 现在都支持下面这些写法:

classA(Feature):
passclassB(Feature):
requires= (A)
classC(Feature):
requires= (A,)
classD(Feature):
conflicts= (A)

注意:

  • requires = (A) 在 Python 语法里其实等价于 requires = A
  • 框架会把它归一化成单元素依赖声明
  • 多个依赖时请写成真正的 tuple,例如 requires = (A, B)

公开 API

框架当前公开的 API 很小,主要就是下面这些。

Feature

所有能力单元的基类。

可用的类级接口:

  • requires 声明依赖的 Feature。可以是单个 Feature,也可以是多个 Feature 的 tuple。
  • conflicts 声明互斥的 Feature。当冲突双方同时出现在最终组合中时,会报错。
  • Feature.use(*features, name=None) 组合多个 Feature,返回一个新的类。
  • Feature.dependency_closure() 返回当前 Feature 的完整依赖闭包,包含它自己。
  • Feature.installed_features() 返回当前 Feature 实际安装的能力列表,不包含它自己,只包含依赖和被组合进去的其他 Feature

resolve_features(*features)

只做依赖解析,不创建新类。

它会返回去重后的安装顺序,顺序保证:

  • 依赖总是在依赖者之前
  • 每个 Feature 只出现一次

如果你只是想知道最终会装入哪些 Feature,或者想调试依赖图,resolve_features() 很有用。

异常类型

  • FeatureCompositionError 所有组合错误的基类。
  • FeatureConflictError 当最终组合包含显式互斥的 Feature 时抛出。
  • FeatureDependencyCycleError 当依赖图中存在环时抛出。
  • FeatureMemberConflictError 当两个互不相关的 Feature 声明了同名运行时成员时抛出。

框架保证了什么

AnyFrame 在组合前会做几类检查。

1. 依赖去重

如果多个 Feature 依赖同一个基础能力,它只会安装一次。

2. 依赖顺序正确

依赖解析使用后序 DFS,因此安装顺序总是“依赖在前,功能在后”。

3. 依赖环检测

如果 A 依赖 B,B 又依赖 A,会抛出 FeatureDependencyCycleError

4. 显式冲突检测

如果某个 Featureconflicts 里声明了另一个 Feature,而两者同时出现在最终组合中,会抛出 FeatureConflictError

5. 成员名冲突检测

如果两个没有依赖关系的 Feature 都声明了同名成员,会抛出 FeatureMemberConflictError

这个规则的目的,是避免两个互不相关的能力片段无意间抢同一个运行时名字。

但如果它们本来就在依赖链上,那么后者有意覆盖前者是允许的。

关于类型检查和 LSP

依赖型 Feature 很可能会写出下面这样的代码:

classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(message)

这在运行时是可行的,因为最终组合类会提供 log()。 但静态分析器并不知道 self 将来一定会被组合进什么样的最终类,所以 LSP 或类型检查工具可能会报警。

这是这个框架目前的已知特性,不是运行时 bug。

如果你希望既保留 requires 的运行时语义,又让 IDE 认识依赖成员,一个比较实用的办法是配合 TYPE_CHECKING 提供一个“只给类型系统看”的基类:

fromtypingimportTYPE_CHECKINGfromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
ifTYPE_CHECKING:
class_AuthBase(Logging):
passelse:
_AuthBase=FeatureclassAuth(_AuthBase):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
ifTYPE_CHECKING:
class_APIBase(Auth, Logging):
passelse:
_APIBase=FeatureclassAPI(_APIBase):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")

这个写法的关键点是:

  • TYPE_CHECKING 分支只参与静态分析,不参与运行时执行
  • 每个直接使用依赖成员的 Feature 都可以声明一个只给 IDE 看的辅助基类
  • IDE 会把 Auth 看成拥有 Logging 的成员,也会把 API 看成同时拥有 AuthLogging 提供的成员
  • 运行时里 API 仍然只是继承 Feature,真实依赖关系依然由 requires 决定

使用这个技巧时,建议注意三点:

  • else 分支最好写成 _AuthBase = Feature_APIBase = Feature 这样的别名,而不是再定义新的 Feature 子类。这样不会把这些辅助基类误带入运行时语义。
  • requires 继续表达“真实依赖”,也就是这个 Feature 自己直接使用到的能力。上面的 API 里既调用了 self.login(),也调用了 self.log(),所以应该显式声明 AuthLogging
  • 这个技巧通常需要逐层使用。API 的辅助基类不会自动让 Auth 内部的 self.log(...) 获得类型提示,因此 Auth 自己也需要对应的 _AuthBase

如果你不想引入这种双轨写法,也可以考虑:

  • 在项目内接受这类动态组合带来的局部告警
  • Protocolcast() 给依赖成员补充静态提示
  • 把依赖访问收敛到少量辅助方法里,减少裸 self.xxx 的分布范围

命名组合与匿名组合

use() 支持两种模式。

匿名组合

App=Feature.use(API, Logging)

这种方式适合临时组合。相同输入的匿名组合会被缓存复用。

命名组合

App=Feature.use(API, Logging, name="App")

这种方式适合你想要一个稳定、可复用、可导入的最终类。

什么时候该用继承,什么时候该用 requires

可以用下面这条经验法则。

  • 如果你想表达“这是另一个 Feature 的特化版本”,用继承
  • 如果你想表达“我不是它的子类,但我运行时需要它提供的方法/属性”,用 requires
  • 如果你想表达“这是最终可运行的组合结果”,用 use()

当前状态

AnyFrame 现在的公开 API 很小,重点在:

  • 保持组合模型足够直接
  • 把依赖解析和错误边界做清楚
  • 让运行时类组合比手写多重继承更可控

如果你在阅读源码,建议先从 FeatureFeature.use()resolve_features() 开始看。

About

用于开发类似FastApi语法框架的元框架

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

AnyFrame

AnyFrame 是一个面向 Python 的运行时特性组合框架。

它的核心思路不是“先造对象、再把对象注册到容器里”,而是:

  • Feature 子类声明能力
  • 用继承表达“这个能力是另一个能力的特化版本”
  • requires 表达“我不继承它,但我运行时依赖它的能力”
  • use() 把多个 Feature 组合成最终可实例化的类

安装

AnyFrame 目前要求 Python 3.12 及以上版本。

如果你在另一个 uv 管理的项目里通过 Git 引入它,推荐使用:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git"

如果你想固定到某个 tag 或 commit,也可以写成:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git@79639e2c4bc812b9c4cf0fc87105f51e847f67c5"

如果你只是临时安装到当前虚拟环境,而不修改项目依赖声明,可以使用:

uv pip install "git+https://github.com/ncatbot/AnyFrame.git"

心智模型

在 AnyFrame 里,Feature 的类对象本身就是身份。

你可以把一个 Feature 理解成“能力片段”或“运行时 trait”:

  • 它可以自己提供方法和属性
  • 它可以依赖别的 Feature
  • 它可以被别的 Feature 继承
  • 它也可以在最终运行时被组合进一个新的类

框架目前支持三种最重要的 Feature 形态。

三类 Feature

1. 独立 Feature

独立 Feature 不依赖其他 Feature,自己就是一个完整能力单元。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")

这种类型通常适合作为最底层的基础能力。

2. 扩展型 Feature

如果一个 Feature 需要保留父类的大部分能力,并重写或补充一部分行为,那么直接继承父类即可。

这种场景下不需要重复写 requires,因为框架会把继承关系视为隐式依赖。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classJsonLogging(Logging):
deflog(self, message: str) ->None:
super().log(f'{{"message": "{message}"}}')

这里的语义是:

  • JsonLoggingLogging 的特化版本
  • 它沿用父类的大部分能力
  • 它可以通过 super() 复用父类实现

3. 依赖型 Feature

依赖型 Feature 不继承别的 Feature,但它运行时需要别的能力,因此通过 requires 声明依赖。

它的内部可以直接通过 self.xxx 使用依赖提供的成员。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(f"audit: {message}")

这个 Audit 在语义上不是一个推荐单独使用的最终类。 它更像一个“等待被组合”的能力片段:只有在最终类里确实混入了 Loggingself.log(...) 才有意义。

这也是 AnyFrame 的一个重要哲学:

  • 继承表达“我是某个 Feature 的特化版本”
  • requires 表达“我不是它的子类,但我运行时需要它的能力”

一个完整例子

下面这个例子把三种思路串起来。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAuth(Feature):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
classAPI(Feature):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")
App=Feature.use(API, name="App")
app=App()
app.call("alice")

这里发生了几件事:

  • API 直接使用了 AuthLogging 的能力,所以显式依赖它们两者
  • Auth 依赖 Logging
  • Feature.use(API, name="App") 会解析整条依赖链
  • 框架生成一个新的最终类 App
  • App 的实例可以直接使用 APIAuthLogging 提供的能力

组合是怎么工作的

use() 是框架最核心的入口。

App=Feature.use(API, Logging)

或:

App=API.use(Logging)

它们都会返回一个新的类,而不是实例。

你可以把 use() 理解成“类组合器”:

  • 输入是若干个 Feature
  • 框架先解析依赖关系
  • 自动去重
  • 检查冲突
  • 最后构造一个新的合成类

匿名组合还会被缓存复用,因此:

Feature.use(A).use(B)

会和:

Feature.use(A, B)

表现一致。

requiresconflicts 的写法

requiresconflicts 现在都支持下面这些写法:

classA(Feature):
passclassB(Feature):
requires= (A)
classC(Feature):
requires= (A,)
classD(Feature):
conflicts= (A)

注意:

  • requires = (A) 在 Python 语法里其实等价于 requires = A
  • 框架会把它归一化成单元素依赖声明
  • 多个依赖时请写成真正的 tuple,例如 requires = (A, B)

公开 API

框架当前公开的 API 很小,主要就是下面这些。

Feature

所有能力单元的基类。

可用的类级接口:

  • requires 声明依赖的 Feature。可以是单个 Feature,也可以是多个 Feature 的 tuple。
  • conflicts 声明互斥的 Feature。当冲突双方同时出现在最终组合中时,会报错。
  • Feature.use(*features, name=None) 组合多个 Feature,返回一个新的类。
  • Feature.dependency_closure() 返回当前 Feature 的完整依赖闭包,包含它自己。
  • Feature.installed_features() 返回当前 Feature 实际安装的能力列表,不包含它自己,只包含依赖和被组合进去的其他 Feature

resolve_features(*features)

只做依赖解析,不创建新类。

它会返回去重后的安装顺序,顺序保证:

  • 依赖总是在依赖者之前
  • 每个 Feature 只出现一次

如果你只是想知道最终会装入哪些 Feature,或者想调试依赖图,resolve_features() 很有用。

异常类型

  • FeatureCompositionError 所有组合错误的基类。
  • FeatureConflictError 当最终组合包含显式互斥的 Feature 时抛出。
  • FeatureDependencyCycleError 当依赖图中存在环时抛出。
  • FeatureMemberConflictError 当两个互不相关的 Feature 声明了同名运行时成员时抛出。

框架保证了什么

AnyFrame 在组合前会做几类检查。

1. 依赖去重

如果多个 Feature 依赖同一个基础能力,它只会安装一次。

2. 依赖顺序正确

依赖解析使用后序 DFS,因此安装顺序总是“依赖在前,功能在后”。

3. 依赖环检测

如果 A 依赖 B,B 又依赖 A,会抛出 FeatureDependencyCycleError

4. 显式冲突检测

如果某个 Featureconflicts 里声明了另一个 Feature,而两者同时出现在最终组合中,会抛出 FeatureConflictError

5. 成员名冲突检测

如果两个没有依赖关系的 Feature 都声明了同名成员,会抛出 FeatureMemberConflictError

这个规则的目的,是避免两个互不相关的能力片段无意间抢同一个运行时名字。

但如果它们本来就在依赖链上,那么后者有意覆盖前者是允许的。

关于类型检查和 LSP

依赖型 Feature 很可能会写出下面这样的代码:

classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(message)

这在运行时是可行的,因为最终组合类会提供 log()。 但静态分析器并不知道 self 将来一定会被组合进什么样的最终类,所以 LSP 或类型检查工具可能会报警。

这是这个框架目前的已知特性,不是运行时 bug。

如果你希望既保留 requires 的运行时语义,又让 IDE 认识依赖成员,一个比较实用的办法是配合 TYPE_CHECKING 提供一个“只给类型系统看”的基类:

fromtypingimportTYPE_CHECKINGfromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
ifTYPE_CHECKING:
class_AuthBase(Logging):
passelse:
_AuthBase=FeatureclassAuth(_AuthBase):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
ifTYPE_CHECKING:
class_APIBase(Auth, Logging):
passelse:
_APIBase=FeatureclassAPI(_APIBase):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")

这个写法的关键点是:

  • TYPE_CHECKING 分支只参与静态分析,不参与运行时执行
  • 每个直接使用依赖成员的 Feature 都可以声明一个只给 IDE 看的辅助基类
  • IDE 会把 Auth 看成拥有 Logging 的成员,也会把 API 看成同时拥有 AuthLogging 提供的成员
  • 运行时里 API 仍然只是继承 Feature,真实依赖关系依然由 requires 决定

使用这个技巧时,建议注意三点:

  • else 分支最好写成 _AuthBase = Feature_APIBase = Feature 这样的别名,而不是再定义新的 Feature 子类。这样不会把这些辅助基类误带入运行时语义。
  • requires 继续表达“真实依赖”,也就是这个 Feature 自己直接使用到的能力。上面的 API 里既调用了 self.login(),也调用了 self.log(),所以应该显式声明 AuthLogging
  • 这个技巧通常需要逐层使用。API 的辅助基类不会自动让 Auth 内部的 self.log(...) 获得类型提示,因此 Auth 自己也需要对应的 _AuthBase

如果你不想引入这种双轨写法,也可以考虑:

  • 在项目内接受这类动态组合带来的局部告警
  • Protocolcast() 给依赖成员补充静态提示
  • 把依赖访问收敛到少量辅助方法里,减少裸 self.xxx 的分布范围

命名组合与匿名组合

use() 支持两种模式。

匿名组合

App=Feature.use(API, Logging)

这种方式适合临时组合。相同输入的匿名组合会被缓存复用。

命名组合

App=Feature.use(API, Logging, name="App")

这种方式适合你想要一个稳定、可复用、可导入的最终类。

什么时候该用继承,什么时候该用 requires

可以用下面这条经验法则。

  • 如果你想表达“这是另一个 Feature 的特化版本”,用继承
  • 如果你想表达“我不是它的子类,但我运行时需要它提供的方法/属性”,用 requires
  • 如果你想表达“这是最终可运行的组合结果”,用 use()

当前状态

AnyFrame 现在的公开 API 很小,重点在:

  • 保持组合模型足够直接
  • 把依赖解析和错误边界做清楚
  • 让运行时类组合比手写多重继承更可控

如果你在阅读源码,建议先从 FeatureFeature.use()resolve_features() 开始看。

About

用于开发类似FastApi语法框架的元框架

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

AnyFrame

AnyFrame 是一个面向 Python 的运行时特性组合框架。

它的核心思路不是“先造对象、再把对象注册到容器里”,而是:

  • Feature 子类声明能力
  • 用继承表达“这个能力是另一个能力的特化版本”
  • requires 表达“我不继承它,但我运行时依赖它的能力”
  • use() 把多个 Feature 组合成最终可实例化的类

安装

AnyFrame 目前要求 Python 3.12 及以上版本。

如果你在另一个 uv 管理的项目里通过 Git 引入它,推荐使用:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git"

如果你想固定到某个 tag 或 commit,也可以写成:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git@79639e2c4bc812b9c4cf0fc87105f51e847f67c5"

如果你只是临时安装到当前虚拟环境,而不修改项目依赖声明,可以使用:

uv pip install "git+https://github.com/ncatbot/AnyFrame.git"

心智模型

在 AnyFrame 里,Feature 的类对象本身就是身份。

你可以把一个 Feature 理解成“能力片段”或“运行时 trait”:

  • 它可以自己提供方法和属性
  • 它可以依赖别的 Feature
  • 它可以被别的 Feature 继承
  • 它也可以在最终运行时被组合进一个新的类

框架目前支持三种最重要的 Feature 形态。

三类 Feature

1. 独立 Feature

独立 Feature 不依赖其他 Feature,自己就是一个完整能力单元。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")

这种类型通常适合作为最底层的基础能力。

2. 扩展型 Feature

如果一个 Feature 需要保留父类的大部分能力,并重写或补充一部分行为,那么直接继承父类即可。

这种场景下不需要重复写 requires,因为框架会把继承关系视为隐式依赖。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classJsonLogging(Logging):
deflog(self, message: str) ->None:
super().log(f'{{"message": "{message}"}}')

这里的语义是:

  • JsonLoggingLogging 的特化版本
  • 它沿用父类的大部分能力
  • 它可以通过 super() 复用父类实现

3. 依赖型 Feature

依赖型 Feature 不继承别的 Feature,但它运行时需要别的能力,因此通过 requires 声明依赖。

它的内部可以直接通过 self.xxx 使用依赖提供的成员。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(f"audit: {message}")

这个 Audit 在语义上不是一个推荐单独使用的最终类。 它更像一个“等待被组合”的能力片段:只有在最终类里确实混入了 Loggingself.log(...) 才有意义。

这也是 AnyFrame 的一个重要哲学:

  • 继承表达“我是某个 Feature 的特化版本”
  • requires 表达“我不是它的子类,但我运行时需要它的能力”

一个完整例子

下面这个例子把三种思路串起来。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAuth(Feature):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
classAPI(Feature):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")
App=Feature.use(API, name="App")
app=App()
app.call("alice")

这里发生了几件事:

  • API 直接使用了 AuthLogging 的能力,所以显式依赖它们两者
  • Auth 依赖 Logging
  • Feature.use(API, name="App") 会解析整条依赖链
  • 框架生成一个新的最终类 App
  • App 的实例可以直接使用 APIAuthLogging 提供的能力

组合是怎么工作的

use() 是框架最核心的入口。

App=Feature.use(API, Logging)

或:

App=API.use(Logging)

它们都会返回一个新的类,而不是实例。

你可以把 use() 理解成“类组合器”:

  • 输入是若干个 Feature
  • 框架先解析依赖关系
  • 自动去重
  • 检查冲突
  • 最后构造一个新的合成类

匿名组合还会被缓存复用,因此:

Feature.use(A).use(B)

会和:

Feature.use(A, B)

表现一致。

requiresconflicts 的写法

requiresconflicts 现在都支持下面这些写法:

classA(Feature):
passclassB(Feature):
requires= (A)
classC(Feature):
requires= (A,)
classD(Feature):
conflicts= (A)

注意:

  • requires = (A) 在 Python 语法里其实等价于 requires = A
  • 框架会把它归一化成单元素依赖声明
  • 多个依赖时请写成真正的 tuple,例如 requires = (A, B)

公开 API

框架当前公开的 API 很小,主要就是下面这些。

Feature

所有能力单元的基类。

可用的类级接口:

  • requires 声明依赖的 Feature。可以是单个 Feature,也可以是多个 Feature 的 tuple。
  • conflicts 声明互斥的 Feature。当冲突双方同时出现在最终组合中时,会报错。
  • Feature.use(*features, name=None) 组合多个 Feature,返回一个新的类。
  • Feature.dependency_closure() 返回当前 Feature 的完整依赖闭包,包含它自己。
  • Feature.installed_features() 返回当前 Feature 实际安装的能力列表,不包含它自己,只包含依赖和被组合进去的其他 Feature

resolve_features(*features)

只做依赖解析,不创建新类。

它会返回去重后的安装顺序,顺序保证:

  • 依赖总是在依赖者之前
  • 每个 Feature 只出现一次

如果你只是想知道最终会装入哪些 Feature,或者想调试依赖图,resolve_features() 很有用。

异常类型

  • FeatureCompositionError 所有组合错误的基类。
  • FeatureConflictError 当最终组合包含显式互斥的 Feature 时抛出。
  • FeatureDependencyCycleError 当依赖图中存在环时抛出。
  • FeatureMemberConflictError 当两个互不相关的 Feature 声明了同名运行时成员时抛出。

框架保证了什么

AnyFrame 在组合前会做几类检查。

1. 依赖去重

如果多个 Feature 依赖同一个基础能力,它只会安装一次。

2. 依赖顺序正确

依赖解析使用后序 DFS,因此安装顺序总是“依赖在前,功能在后”。

3. 依赖环检测

如果 A 依赖 B,B 又依赖 A,会抛出 FeatureDependencyCycleError

4. 显式冲突检测

如果某个 Featureconflicts 里声明了另一个 Feature,而两者同时出现在最终组合中,会抛出 FeatureConflictError

5. 成员名冲突检测

如果两个没有依赖关系的 Feature 都声明了同名成员,会抛出 FeatureMemberConflictError

这个规则的目的,是避免两个互不相关的能力片段无意间抢同一个运行时名字。

但如果它们本来就在依赖链上,那么后者有意覆盖前者是允许的。

关于类型检查和 LSP

依赖型 Feature 很可能会写出下面这样的代码:

classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(message)

这在运行时是可行的,因为最终组合类会提供 log()。 但静态分析器并不知道 self 将来一定会被组合进什么样的最终类,所以 LSP 或类型检查工具可能会报警。

这是这个框架目前的已知特性,不是运行时 bug。

如果你希望既保留 requires 的运行时语义,又让 IDE 认识依赖成员,一个比较实用的办法是配合 TYPE_CHECKING 提供一个“只给类型系统看”的基类:

fromtypingimportTYPE_CHECKINGfromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
ifTYPE_CHECKING:
class_AuthBase(Logging):
passelse:
_AuthBase=FeatureclassAuth(_AuthBase):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
ifTYPE_CHECKING:
class_APIBase(Auth, Logging):
passelse:
_APIBase=FeatureclassAPI(_APIBase):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")

这个写法的关键点是:

  • TYPE_CHECKING 分支只参与静态分析,不参与运行时执行
  • 每个直接使用依赖成员的 Feature 都可以声明一个只给 IDE 看的辅助基类
  • IDE 会把 Auth 看成拥有 Logging 的成员,也会把 API 看成同时拥有 AuthLogging 提供的成员
  • 运行时里 API 仍然只是继承 Feature,真实依赖关系依然由 requires 决定

使用这个技巧时,建议注意三点:

  • else 分支最好写成 _AuthBase = Feature_APIBase = Feature 这样的别名,而不是再定义新的 Feature 子类。这样不会把这些辅助基类误带入运行时语义。
  • requires 继续表达“真实依赖”,也就是这个 Feature 自己直接使用到的能力。上面的 API 里既调用了 self.login(),也调用了 self.log(),所以应该显式声明 AuthLogging
  • 这个技巧通常需要逐层使用。API 的辅助基类不会自动让 Auth 内部的 self.log(...) 获得类型提示,因此 Auth 自己也需要对应的 _AuthBase

如果你不想引入这种双轨写法,也可以考虑:

  • 在项目内接受这类动态组合带来的局部告警
  • Protocolcast() 给依赖成员补充静态提示
  • 把依赖访问收敛到少量辅助方法里,减少裸 self.xxx 的分布范围

命名组合与匿名组合

use() 支持两种模式。

匿名组合

App=Feature.use(API, Logging)

这种方式适合临时组合。相同输入的匿名组合会被缓存复用。

命名组合

App=Feature.use(API, Logging, name="App")

这种方式适合你想要一个稳定、可复用、可导入的最终类。

什么时候该用继承,什么时候该用 requires

可以用下面这条经验法则。

  • 如果你想表达“这是另一个 Feature 的特化版本”,用继承
  • 如果你想表达“我不是它的子类,但我运行时需要它提供的方法/属性”,用 requires
  • 如果你想表达“这是最终可运行的组合结果”,用 use()

当前状态

AnyFrame 现在的公开 API 很小,重点在:

  • 保持组合模型足够直接
  • 把依赖解析和错误边界做清楚
  • 让运行时类组合比手写多重继承更可控

如果你在阅读源码,建议先从 FeatureFeature.use()resolve_features() 开始看。

About

用于开发类似FastApi语法框架的元框架

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

AnyFrame

AnyFrame 是一个面向 Python 的运行时特性组合框架。

它的核心思路不是“先造对象、再把对象注册到容器里”,而是:

  • Feature 子类声明能力
  • 用继承表达“这个能力是另一个能力的特化版本”
  • requires 表达“我不继承它,但我运行时依赖它的能力”
  • use() 把多个 Feature 组合成最终可实例化的类

安装

AnyFrame 目前要求 Python 3.12 及以上版本。

如果你在另一个 uv 管理的项目里通过 Git 引入它,推荐使用:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git"

如果你想固定到某个 tag 或 commit,也可以写成:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git@79639e2c4bc812b9c4cf0fc87105f51e847f67c5"

如果你只是临时安装到当前虚拟环境,而不修改项目依赖声明,可以使用:

uv pip install "git+https://github.com/ncatbot/AnyFrame.git"

心智模型

在 AnyFrame 里,Feature 的类对象本身就是身份。

你可以把一个 Feature 理解成“能力片段”或“运行时 trait”:

  • 它可以自己提供方法和属性
  • 它可以依赖别的 Feature
  • 它可以被别的 Feature 继承
  • 它也可以在最终运行时被组合进一个新的类

框架目前支持三种最重要的 Feature 形态。

三类 Feature

1. 独立 Feature

独立 Feature 不依赖其他 Feature,自己就是一个完整能力单元。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")

这种类型通常适合作为最底层的基础能力。

2. 扩展型 Feature

如果一个 Feature 需要保留父类的大部分能力,并重写或补充一部分行为,那么直接继承父类即可。

这种场景下不需要重复写 requires,因为框架会把继承关系视为隐式依赖。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classJsonLogging(Logging):
deflog(self, message: str) ->None:
super().log(f'{{"message": "{message}"}}')

这里的语义是:

  • JsonLoggingLogging 的特化版本
  • 它沿用父类的大部分能力
  • 它可以通过 super() 复用父类实现

3. 依赖型 Feature

依赖型 Feature 不继承别的 Feature,但它运行时需要别的能力,因此通过 requires 声明依赖。

它的内部可以直接通过 self.xxx 使用依赖提供的成员。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(f"audit: {message}")

这个 Audit 在语义上不是一个推荐单独使用的最终类。 它更像一个“等待被组合”的能力片段:只有在最终类里确实混入了 Loggingself.log(...) 才有意义。

这也是 AnyFrame 的一个重要哲学:

  • 继承表达“我是某个 Feature 的特化版本”
  • requires 表达“我不是它的子类,但我运行时需要它的能力”

一个完整例子

下面这个例子把三种思路串起来。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAuth(Feature):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
classAPI(Feature):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")
App=Feature.use(API, name="App")
app=App()
app.call("alice")

这里发生了几件事:

  • API 直接使用了 AuthLogging 的能力,所以显式依赖它们两者
  • Auth 依赖 Logging
  • Feature.use(API, name="App") 会解析整条依赖链
  • 框架生成一个新的最终类 App
  • App 的实例可以直接使用 APIAuthLogging 提供的能力

组合是怎么工作的

use() 是框架最核心的入口。

App=Feature.use(API, Logging)

或:

App=API.use(Logging)

它们都会返回一个新的类,而不是实例。

你可以把 use() 理解成“类组合器”:

  • 输入是若干个 Feature
  • 框架先解析依赖关系
  • 自动去重
  • 检查冲突
  • 最后构造一个新的合成类

匿名组合还会被缓存复用,因此:

Feature.use(A).use(B)

会和:

Feature.use(A, B)

表现一致。

requiresconflicts 的写法

requiresconflicts 现在都支持下面这些写法:

classA(Feature):
passclassB(Feature):
requires= (A)
classC(Feature):
requires= (A,)
classD(Feature):
conflicts= (A)

注意:

  • requires = (A) 在 Python 语法里其实等价于 requires = A
  • 框架会把它归一化成单元素依赖声明
  • 多个依赖时请写成真正的 tuple,例如 requires = (A, B)

公开 API

框架当前公开的 API 很小,主要就是下面这些。

Feature

所有能力单元的基类。

可用的类级接口:

  • requires 声明依赖的 Feature。可以是单个 Feature,也可以是多个 Feature 的 tuple。
  • conflicts 声明互斥的 Feature。当冲突双方同时出现在最终组合中时,会报错。
  • Feature.use(*features, name=None) 组合多个 Feature,返回一个新的类。
  • Feature.dependency_closure() 返回当前 Feature 的完整依赖闭包,包含它自己。
  • Feature.installed_features() 返回当前 Feature 实际安装的能力列表,不包含它自己,只包含依赖和被组合进去的其他 Feature

resolve_features(*features)

只做依赖解析,不创建新类。

它会返回去重后的安装顺序,顺序保证:

  • 依赖总是在依赖者之前
  • 每个 Feature 只出现一次

如果你只是想知道最终会装入哪些 Feature,或者想调试依赖图,resolve_features() 很有用。

异常类型

  • FeatureCompositionError 所有组合错误的基类。
  • FeatureConflictError 当最终组合包含显式互斥的 Feature 时抛出。
  • FeatureDependencyCycleError 当依赖图中存在环时抛出。
  • FeatureMemberConflictError 当两个互不相关的 Feature 声明了同名运行时成员时抛出。

框架保证了什么

AnyFrame 在组合前会做几类检查。

1. 依赖去重

如果多个 Feature 依赖同一个基础能力,它只会安装一次。

2. 依赖顺序正确

依赖解析使用后序 DFS,因此安装顺序总是“依赖在前,功能在后”。

3. 依赖环检测

如果 A 依赖 B,B 又依赖 A,会抛出 FeatureDependencyCycleError

4. 显式冲突检测

如果某个 Featureconflicts 里声明了另一个 Feature,而两者同时出现在最终组合中,会抛出 FeatureConflictError

5. 成员名冲突检测

如果两个没有依赖关系的 Feature 都声明了同名成员,会抛出 FeatureMemberConflictError

这个规则的目的,是避免两个互不相关的能力片段无意间抢同一个运行时名字。

但如果它们本来就在依赖链上,那么后者有意覆盖前者是允许的。

关于类型检查和 LSP

依赖型 Feature 很可能会写出下面这样的代码:

classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(message)

这在运行时是可行的,因为最终组合类会提供 log()。 但静态分析器并不知道 self 将来一定会被组合进什么样的最终类,所以 LSP 或类型检查工具可能会报警。

这是这个框架目前的已知特性,不是运行时 bug。

如果你希望既保留 requires 的运行时语义,又让 IDE 认识依赖成员,一个比较实用的办法是配合 TYPE_CHECKING 提供一个“只给类型系统看”的基类:

fromtypingimportTYPE_CHECKINGfromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
ifTYPE_CHECKING:
class_AuthBase(Logging):
passelse:
_AuthBase=FeatureclassAuth(_AuthBase):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
ifTYPE_CHECKING:
class_APIBase(Auth, Logging):
passelse:
_APIBase=FeatureclassAPI(_APIBase):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")

这个写法的关键点是:

  • TYPE_CHECKING 分支只参与静态分析,不参与运行时执行
  • 每个直接使用依赖成员的 Feature 都可以声明一个只给 IDE 看的辅助基类
  • IDE 会把 Auth 看成拥有 Logging 的成员,也会把 API 看成同时拥有 AuthLogging 提供的成员
  • 运行时里 API 仍然只是继承 Feature,真实依赖关系依然由 requires 决定

使用这个技巧时,建议注意三点:

  • else 分支最好写成 _AuthBase = Feature_APIBase = Feature 这样的别名,而不是再定义新的 Feature 子类。这样不会把这些辅助基类误带入运行时语义。
  • requires 继续表达“真实依赖”,也就是这个 Feature 自己直接使用到的能力。上面的 API 里既调用了 self.login(),也调用了 self.log(),所以应该显式声明 AuthLogging
  • 这个技巧通常需要逐层使用。API 的辅助基类不会自动让 Auth 内部的 self.log(...) 获得类型提示,因此 Auth 自己也需要对应的 _AuthBase

如果你不想引入这种双轨写法,也可以考虑:

  • 在项目内接受这类动态组合带来的局部告警
  • Protocolcast() 给依赖成员补充静态提示
  • 把依赖访问收敛到少量辅助方法里,减少裸 self.xxx 的分布范围

命名组合与匿名组合

use() 支持两种模式。

匿名组合

App=Feature.use(API, Logging)

这种方式适合临时组合。相同输入的匿名组合会被缓存复用。

命名组合

App=Feature.use(API, Logging, name="App")

这种方式适合你想要一个稳定、可复用、可导入的最终类。

什么时候该用继承,什么时候该用 requires

可以用下面这条经验法则。

  • 如果你想表达“这是另一个 Feature 的特化版本”,用继承
  • 如果你想表达“我不是它的子类,但我运行时需要它提供的方法/属性”,用 requires
  • 如果你想表达“这是最终可运行的组合结果”,用 use()

当前状态

AnyFrame 现在的公开 API 很小,重点在:

  • 保持组合模型足够直接
  • 把依赖解析和错误边界做清楚
  • 让运行时类组合比手写多重继承更可控

如果你在阅读源码,建议先从 FeatureFeature.use()resolve_features() 开始看。

About

用于开发类似FastApi语法框架的元框架

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

AnyFrame

AnyFrame 是一个面向 Python 的运行时特性组合框架。

它的核心思路不是“先造对象、再把对象注册到容器里”,而是:

  • Feature 子类声明能力
  • 用继承表达“这个能力是另一个能力的特化版本”
  • requires 表达“我不继承它,但我运行时依赖它的能力”
  • use() 把多个 Feature 组合成最终可实例化的类

安装

AnyFrame 目前要求 Python 3.12 及以上版本。

如果你在另一个 uv 管理的项目里通过 Git 引入它,推荐使用:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git"

如果你想固定到某个 tag 或 commit,也可以写成:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git@79639e2c4bc812b9c4cf0fc87105f51e847f67c5"

如果你只是临时安装到当前虚拟环境,而不修改项目依赖声明,可以使用:

uv pip install "git+https://github.com/ncatbot/AnyFrame.git"

心智模型

在 AnyFrame 里,Feature 的类对象本身就是身份。

你可以把一个 Feature 理解成“能力片段”或“运行时 trait”:

  • 它可以自己提供方法和属性
  • 它可以依赖别的 Feature
  • 它可以被别的 Feature 继承
  • 它也可以在最终运行时被组合进一个新的类

框架目前支持三种最重要的 Feature 形态。

三类 Feature

1. 独立 Feature

独立 Feature 不依赖其他 Feature,自己就是一个完整能力单元。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")

这种类型通常适合作为最底层的基础能力。

2. 扩展型 Feature

如果一个 Feature 需要保留父类的大部分能力,并重写或补充一部分行为,那么直接继承父类即可。

这种场景下不需要重复写 requires,因为框架会把继承关系视为隐式依赖。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classJsonLogging(Logging):
deflog(self, message: str) ->None:
super().log(f'{{"message": "{message}"}}')

这里的语义是:

  • JsonLoggingLogging 的特化版本
  • 它沿用父类的大部分能力
  • 它可以通过 super() 复用父类实现

3. 依赖型 Feature

依赖型 Feature 不继承别的 Feature,但它运行时需要别的能力,因此通过 requires 声明依赖。

它的内部可以直接通过 self.xxx 使用依赖提供的成员。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(f"audit: {message}")

这个 Audit 在语义上不是一个推荐单独使用的最终类。 它更像一个“等待被组合”的能力片段:只有在最终类里确实混入了 Loggingself.log(...) 才有意义。

这也是 AnyFrame 的一个重要哲学:

  • 继承表达“我是某个 Feature 的特化版本”
  • requires 表达“我不是它的子类,但我运行时需要它的能力”

一个完整例子

下面这个例子把三种思路串起来。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAuth(Feature):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
classAPI(Feature):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")
App=Feature.use(API, name="App")
app=App()
app.call("alice")

这里发生了几件事:

  • API 直接使用了 AuthLogging 的能力,所以显式依赖它们两者
  • Auth 依赖 Logging
  • Feature.use(API, name="App") 会解析整条依赖链
  • 框架生成一个新的最终类 App
  • App 的实例可以直接使用 APIAuthLogging 提供的能力

组合是怎么工作的

use() 是框架最核心的入口。

App=Feature.use(API, Logging)

或:

App=API.use(Logging)

它们都会返回一个新的类,而不是实例。

你可以把 use() 理解成“类组合器”:

  • 输入是若干个 Feature
  • 框架先解析依赖关系
  • 自动去重
  • 检查冲突
  • 最后构造一个新的合成类

匿名组合还会被缓存复用,因此:

Feature.use(A).use(B)

会和:

Feature.use(A, B)

表现一致。

requiresconflicts 的写法

requiresconflicts 现在都支持下面这些写法:

classA(Feature):
passclassB(Feature):
requires= (A)
classC(Feature):
requires= (A,)
classD(Feature):
conflicts= (A)

注意:

  • requires = (A) 在 Python 语法里其实等价于 requires = A
  • 框架会把它归一化成单元素依赖声明
  • 多个依赖时请写成真正的 tuple,例如 requires = (A, B)

公开 API

框架当前公开的 API 很小,主要就是下面这些。

Feature

所有能力单元的基类。

可用的类级接口:

  • requires 声明依赖的 Feature。可以是单个 Feature,也可以是多个 Feature 的 tuple。
  • conflicts 声明互斥的 Feature。当冲突双方同时出现在最终组合中时,会报错。
  • Feature.use(*features, name=None) 组合多个 Feature,返回一个新的类。
  • Feature.dependency_closure() 返回当前 Feature 的完整依赖闭包,包含它自己。
  • Feature.installed_features() 返回当前 Feature 实际安装的能力列表,不包含它自己,只包含依赖和被组合进去的其他 Feature

resolve_features(*features)

只做依赖解析,不创建新类。

它会返回去重后的安装顺序,顺序保证:

  • 依赖总是在依赖者之前
  • 每个 Feature 只出现一次

如果你只是想知道最终会装入哪些 Feature,或者想调试依赖图,resolve_features() 很有用。

异常类型

  • FeatureCompositionError 所有组合错误的基类。
  • FeatureConflictError 当最终组合包含显式互斥的 Feature 时抛出。
  • FeatureDependencyCycleError 当依赖图中存在环时抛出。
  • FeatureMemberConflictError 当两个互不相关的 Feature 声明了同名运行时成员时抛出。

框架保证了什么

AnyFrame 在组合前会做几类检查。

1. 依赖去重

如果多个 Feature 依赖同一个基础能力,它只会安装一次。

2. 依赖顺序正确

依赖解析使用后序 DFS,因此安装顺序总是“依赖在前,功能在后”。

3. 依赖环检测

如果 A 依赖 B,B 又依赖 A,会抛出 FeatureDependencyCycleError

4. 显式冲突检测

如果某个 Featureconflicts 里声明了另一个 Feature,而两者同时出现在最终组合中,会抛出 FeatureConflictError

5. 成员名冲突检测

如果两个没有依赖关系的 Feature 都声明了同名成员,会抛出 FeatureMemberConflictError

这个规则的目的,是避免两个互不相关的能力片段无意间抢同一个运行时名字。

但如果它们本来就在依赖链上,那么后者有意覆盖前者是允许的。

关于类型检查和 LSP

依赖型 Feature 很可能会写出下面这样的代码:

classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(message)

这在运行时是可行的,因为最终组合类会提供 log()。 但静态分析器并不知道 self 将来一定会被组合进什么样的最终类,所以 LSP 或类型检查工具可能会报警。

这是这个框架目前的已知特性,不是运行时 bug。

如果你希望既保留 requires 的运行时语义,又让 IDE 认识依赖成员,一个比较实用的办法是配合 TYPE_CHECKING 提供一个“只给类型系统看”的基类:

fromtypingimportTYPE_CHECKINGfromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
ifTYPE_CHECKING:
class_AuthBase(Logging):
passelse:
_AuthBase=FeatureclassAuth(_AuthBase):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
ifTYPE_CHECKING:
class_APIBase(Auth, Logging):
passelse:
_APIBase=FeatureclassAPI(_APIBase):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")

这个写法的关键点是:

  • TYPE_CHECKING 分支只参与静态分析,不参与运行时执行
  • 每个直接使用依赖成员的 Feature 都可以声明一个只给 IDE 看的辅助基类
  • IDE 会把 Auth 看成拥有 Logging 的成员,也会把 API 看成同时拥有 AuthLogging 提供的成员
  • 运行时里 API 仍然只是继承 Feature,真实依赖关系依然由 requires 决定

使用这个技巧时,建议注意三点:

  • else 分支最好写成 _AuthBase = Feature_APIBase = Feature 这样的别名,而不是再定义新的 Feature 子类。这样不会把这些辅助基类误带入运行时语义。
  • requires 继续表达“真实依赖”,也就是这个 Feature 自己直接使用到的能力。上面的 API 里既调用了 self.login(),也调用了 self.log(),所以应该显式声明 AuthLogging
  • 这个技巧通常需要逐层使用。API 的辅助基类不会自动让 Auth 内部的 self.log(...) 获得类型提示,因此 Auth 自己也需要对应的 _AuthBase

如果你不想引入这种双轨写法,也可以考虑:

  • 在项目内接受这类动态组合带来的局部告警
  • Protocolcast() 给依赖成员补充静态提示
  • 把依赖访问收敛到少量辅助方法里,减少裸 self.xxx 的分布范围

命名组合与匿名组合

use() 支持两种模式。

匿名组合

App=Feature.use(API, Logging)

这种方式适合临时组合。相同输入的匿名组合会被缓存复用。

命名组合

App=Feature.use(API, Logging, name="App")

这种方式适合你想要一个稳定、可复用、可导入的最终类。

什么时候该用继承,什么时候该用 requires

可以用下面这条经验法则。

  • 如果你想表达“这是另一个 Feature 的特化版本”,用继承
  • 如果你想表达“我不是它的子类,但我运行时需要它提供的方法/属性”,用 requires
  • 如果你想表达“这是最终可运行的组合结果”,用 use()

当前状态

AnyFrame 现在的公开 API 很小,重点在:

  • 保持组合模型足够直接
  • 把依赖解析和错误边界做清楚
  • 让运行时类组合比手写多重继承更可控

如果你在阅读源码,建议先从 FeatureFeature.use()resolve_features() 开始看。

About

用于开发类似FastApi语法框架的元框架

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

AnyFrame

AnyFrame 是一个面向 Python 的运行时特性组合框架。

它的核心思路不是“先造对象、再把对象注册到容器里”,而是:

  • Feature 子类声明能力
  • 用继承表达“这个能力是另一个能力的特化版本”
  • requires 表达“我不继承它,但我运行时依赖它的能力”
  • use() 把多个 Feature 组合成最终可实例化的类

安装

AnyFrame 目前要求 Python 3.12 及以上版本。

如果你在另一个 uv 管理的项目里通过 Git 引入它,推荐使用:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git"

如果你想固定到某个 tag 或 commit,也可以写成:

uv add "anyframe @ git+https://github.com/ncatbot/AnyFrame.git@79639e2c4bc812b9c4cf0fc87105f51e847f67c5"

如果你只是临时安装到当前虚拟环境,而不修改项目依赖声明,可以使用:

uv pip install "git+https://github.com/ncatbot/AnyFrame.git"

心智模型

在 AnyFrame 里,Feature 的类对象本身就是身份。

你可以把一个 Feature 理解成“能力片段”或“运行时 trait”:

  • 它可以自己提供方法和属性
  • 它可以依赖别的 Feature
  • 它可以被别的 Feature 继承
  • 它也可以在最终运行时被组合进一个新的类

框架目前支持三种最重要的 Feature 形态。

三类 Feature

1. 独立 Feature

独立 Feature 不依赖其他 Feature,自己就是一个完整能力单元。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")

这种类型通常适合作为最底层的基础能力。

2. 扩展型 Feature

如果一个 Feature 需要保留父类的大部分能力,并重写或补充一部分行为,那么直接继承父类即可。

这种场景下不需要重复写 requires,因为框架会把继承关系视为隐式依赖。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classJsonLogging(Logging):
deflog(self, message: str) ->None:
super().log(f'{{"message": "{message}"}}')

这里的语义是:

  • JsonLoggingLogging 的特化版本
  • 它沿用父类的大部分能力
  • 它可以通过 super() 复用父类实现

3. 依赖型 Feature

依赖型 Feature 不继承别的 Feature,但它运行时需要别的能力,因此通过 requires 声明依赖。

它的内部可以直接通过 self.xxx 使用依赖提供的成员。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(f"audit: {message}")

这个 Audit 在语义上不是一个推荐单独使用的最终类。 它更像一个“等待被组合”的能力片段:只有在最终类里确实混入了 Loggingself.log(...) 才有意义。

这也是 AnyFrame 的一个重要哲学:

  • 继承表达“我是某个 Feature 的特化版本”
  • requires 表达“我不是它的子类,但我运行时需要它的能力”

一个完整例子

下面这个例子把三种思路串起来。

fromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
classAuth(Feature):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
classAPI(Feature):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")
App=Feature.use(API, name="App")
app=App()
app.call("alice")

这里发生了几件事:

  • API 直接使用了 AuthLogging 的能力,所以显式依赖它们两者
  • Auth 依赖 Logging
  • Feature.use(API, name="App") 会解析整条依赖链
  • 框架生成一个新的最终类 App
  • App 的实例可以直接使用 APIAuthLogging 提供的能力

组合是怎么工作的

use() 是框架最核心的入口。

App=Feature.use(API, Logging)

或:

App=API.use(Logging)

它们都会返回一个新的类,而不是实例。

你可以把 use() 理解成“类组合器”:

  • 输入是若干个 Feature
  • 框架先解析依赖关系
  • 自动去重
  • 检查冲突
  • 最后构造一个新的合成类

匿名组合还会被缓存复用,因此:

Feature.use(A).use(B)

会和:

Feature.use(A, B)

表现一致。

requiresconflicts 的写法

requiresconflicts 现在都支持下面这些写法:

classA(Feature):
passclassB(Feature):
requires= (A)
classC(Feature):
requires= (A,)
classD(Feature):
conflicts= (A)

注意:

  • requires = (A) 在 Python 语法里其实等价于 requires = A
  • 框架会把它归一化成单元素依赖声明
  • 多个依赖时请写成真正的 tuple,例如 requires = (A, B)

公开 API

框架当前公开的 API 很小,主要就是下面这些。

Feature

所有能力单元的基类。

可用的类级接口:

  • requires 声明依赖的 Feature。可以是单个 Feature,也可以是多个 Feature 的 tuple。
  • conflicts 声明互斥的 Feature。当冲突双方同时出现在最终组合中时,会报错。
  • Feature.use(*features, name=None) 组合多个 Feature,返回一个新的类。
  • Feature.dependency_closure() 返回当前 Feature 的完整依赖闭包,包含它自己。
  • Feature.installed_features() 返回当前 Feature 实际安装的能力列表,不包含它自己,只包含依赖和被组合进去的其他 Feature

resolve_features(*features)

只做依赖解析,不创建新类。

它会返回去重后的安装顺序,顺序保证:

  • 依赖总是在依赖者之前
  • 每个 Feature 只出现一次

如果你只是想知道最终会装入哪些 Feature,或者想调试依赖图,resolve_features() 很有用。

异常类型

  • FeatureCompositionError 所有组合错误的基类。
  • FeatureConflictError 当最终组合包含显式互斥的 Feature 时抛出。
  • FeatureDependencyCycleError 当依赖图中存在环时抛出。
  • FeatureMemberConflictError 当两个互不相关的 Feature 声明了同名运行时成员时抛出。

框架保证了什么

AnyFrame 在组合前会做几类检查。

1. 依赖去重

如果多个 Feature 依赖同一个基础能力,它只会安装一次。

2. 依赖顺序正确

依赖解析使用后序 DFS,因此安装顺序总是“依赖在前,功能在后”。

3. 依赖环检测

如果 A 依赖 B,B 又依赖 A,会抛出 FeatureDependencyCycleError

4. 显式冲突检测

如果某个 Featureconflicts 里声明了另一个 Feature,而两者同时出现在最终组合中,会抛出 FeatureConflictError

5. 成员名冲突检测

如果两个没有依赖关系的 Feature 都声明了同名成员,会抛出 FeatureMemberConflictError

这个规则的目的,是避免两个互不相关的能力片段无意间抢同一个运行时名字。

但如果它们本来就在依赖链上,那么后者有意覆盖前者是允许的。

关于类型检查和 LSP

依赖型 Feature 很可能会写出下面这样的代码:

classAudit(Feature):
requires= (Logging)
defaudit(self, message: str) ->None:
self.log(message)

这在运行时是可行的,因为最终组合类会提供 log()。 但静态分析器并不知道 self 将来一定会被组合进什么样的最终类,所以 LSP 或类型检查工具可能会报警。

这是这个框架目前的已知特性,不是运行时 bug。

如果你希望既保留 requires 的运行时语义,又让 IDE 认识依赖成员,一个比较实用的办法是配合 TYPE_CHECKING 提供一个“只给类型系统看”的基类:

fromtypingimportTYPE_CHECKINGfromanyframeimportFeatureclassLogging(Feature):
deflog(self, message: str) ->None:
print(f"[log] {message}")
ifTYPE_CHECKING:
class_AuthBase(Logging):
passelse:
_AuthBase=FeatureclassAuth(_AuthBase):
requires= (Logging,)
deflogin(self, user: str) ->None:
self.log(f"user {user} logged in")
ifTYPE_CHECKING:
class_APIBase(Auth, Logging):
passelse:
_APIBase=FeatureclassAPI(_APIBase):
requires= (Auth, Logging)
defcall(self, user: str) ->None:
self.login(user)
self.log("api call completed")

这个写法的关键点是:

  • TYPE_CHECKING 分支只参与静态分析,不参与运行时执行
  • 每个直接使用依赖成员的 Feature 都可以声明一个只给 IDE 看的辅助基类
  • IDE 会把 Auth 看成拥有 Logging 的成员,也会把 API 看成同时拥有 AuthLogging 提供的成员
  • 运行时里 API 仍然只是继承 Feature,真实依赖关系依然由 requires 决定

使用这个技巧时,建议注意三点:

  • else 分支最好写成 _AuthBase = Feature_APIBase = Feature 这样的别名,而不是再定义新的 Feature 子类。这样不会把这些辅助基类误带入运行时语义。
  • requires 继续表达“真实依赖”,也就是这个 Feature 自己直接使用到的能力。上面的 API 里既调用了 self.login(),也调用了 self.log(),所以应该显式声明 AuthLogging
  • 这个技巧通常需要逐层使用。API 的辅助基类不会自动让 Auth 内部的 self.log(...) 获得类型提示,因此 Auth 自己也需要对应的 _AuthBase

如果你不想引入这种双轨写法,也可以考虑:

  • 在项目内接受这类动态组合带来的局部告警
  • Protocolcast() 给依赖成员补充静态提示
  • 把依赖访问收敛到少量辅助方法里,减少裸 self.xxx 的分布范围

命名组合与匿名组合

use() 支持两种模式。

匿名组合

App=Feature.use(API, Logging)

这种方式适合临时组合。相同输入的匿名组合会被缓存复用。

命名组合

App=Feature.use(API, Logging, name="App")

这种方式适合你想要一个稳定、可复用、可导入的最终类。

什么时候该用继承,什么时候该用 requires

可以用下面这条经验法则。

  • 如果你想表达“这是另一个 Feature 的特化版本”,用继承
  • 如果你想表达“我不是它的子类,但我运行时需要它提供的方法/属性”,用 requires
  • 如果你想表达“这是最终可运行的组合结果”,用 use()

当前状态

AnyFrame 现在的公开 API 很小,重点在:

  • 保持组合模型足够直接
  • 把依赖解析和错误边界做清楚
  • 让运行时类组合比手写多重继承更可控

如果你在阅读源码,建议先从 FeatureFeature.use()resolve_features() 开始看。

About

用于开发类似FastApi语法框架的元框架

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages