账本加载完是一条按日期排好序的指令列表(Directives = list[Directive],data.py:504)。很多操作要的不是逐条指令,而是从整条流里横切出来的汇总视图:这本账出现过哪些账户、每个账户第一次和最后一次被用到是哪天、哪些账户有
Open/Close 指令、有哪些标签链接收款人、声明过哪些
Commodity、账本跨了哪几年。getters.py
把这些一次性遍历写成十几个彼此独立的顶层函数:多数以
entries 为输入,另有
get_leveln_parent_accounts、get_dict_accounts
接收账户名集合,get_values_meta
接收名字到指令的映射(:228-263,356-382)。共同点是不改输入、不留状态;在标准类型契约内不返回
Beancount 错误列表,但不合契约的输入会直接抛出 Python
异常——get_active_years 对非连续重复的年份
assert,未知指令类型走到分发处触发
AttributeError(:52,70),get_min_max_dates
传入生成器而非序列时 reversed() 触发
TypeError(:289)。
模块 docstring 把自己定位成 "Getter functions that operate on lists
of entries to return various lists of things that they
reference"(getters.py:1-3)。
| 名称 | 位置 | 作用 |
|---|---|---|
class GetAccounts |
getters.py:37-116 |
账户提取器,按指令类名分发到同名方法 |
GetAccounts.get_accounts_use_map |
:40-57 |
遍历全流,产出首次/末次使用日期两张表 |
GetAccounts.get_entry_accounts |
:59-71 |
单条指令的账户集合 |
GetAccounts.Transaction / .Pad /
._one / ._zero |
:73-112 |
四个提取实现 |
分发表 Open = Close = ... = _one |
:114-116 |
十种类型经别名绑到 _one/_zero,加
Transaction、Pad 共覆盖 12 种 |
_GetAccounts = GetAccounts() |
:119-120 |
全局共享实例 |
get_accounts_use_map / get_accounts /
get_entry_accounts |
:123-158 |
三个顶层包装 |
get_account_components |
:161-174 |
所有账户名切分后的组件去重排序 |
get_all_tags / get_all_payees /
get_all_links |
:177-225 |
只扫 Transaction,返回排序后的 list |
get_leveln_parent_accounts |
:228-245 |
横切账户树的第 N 层 |
get_dict_accounts |
:248-263,标签 :266 |
把账户名列表折成嵌套 OrderedDict |
get_min_max_dates |
:269-295 |
取首尾指令的日期 |
get_active_years |
:298-314 |
生成器,产出有指令的年份 |
get_account_open_close |
:317-342 |
账户名到 [open, close] 的映射 |
get_commodity_directives |
:345-353 |
币种名到 Commodity 指令 |
get_values_meta |
:356-382 |
从一张"名字→指令"表里批量取元数据 |
getattr
分发for entry in entries:
method = getattr(self, entry.__class__.__name__)
for account_ in method(entry):
getters.py:51-53。分发不是 isinstance
链,也不是显式字典,而是用指令类的 __name__
去类实例上取同名属性。可取的属性由类体末尾两行赋值造出:
# Associate all the possible directives with their respective handlers.
Open = Close = Balance = Note = Document = _one
Commodity = Event = Query = Price = Custom = _zero
getters.py:114-116。加上单独定义的
Transaction(:73-82)和
Pad(:84-92),十种经别名绑到
_one/_zero,加 Transaction、Pad
共四个实现,恰好覆盖 data.ALL_DIRECTIVES 里的 12
个类型(data.py:452-465)。四个实现的语义是:Transaction
逐条 posting 产出账户;Pad 返回
(entry.account, entry.source_account)
两个账户;_one 返回单元素元组
(entry.account,);_zero
返回空元组。四者返回值都是可迭代对象,调用方一律
for ... in method(entry),不区分生成器与元组。
方法的第一个参数名写作 _ 而不是
self(:73,84,94,104),表示不依赖实例状态;实例
_GetAccounts
建成模块级单例(:119-120),三个顶层包装函数转发到它(:123-158)。
这套写法带一条作者注释:
Note: This should get replaced by a method on each directive eventually,
that would be the clean way to do this.
getters.py:62-63(get_entry_accounts 方法的
docstring),同一段话在顶层包装 get_entry_accounts 的
docstring
里重复了一遍(:150-151)。作者把"账户提取表"集中在一个外部类里标记为临时方案,认为归位的做法是让每个指令类自己提供方法。这条注释自
2014-06-21 commit ba76130a 引入
get_entry_accounts 时就写在那里,至今未变。
分发靠属性名字符串,代价是没有类型检查兜底:传入
ALL_DIRECTIVES 之外的对象(例如
data.TxnPosting)时 getattr 抛
AttributeError,而不是被忽略或报错误对象。getters_test.py:47-50 (test_methods_coverage)
用 hasattr(dispatcher, klass.__name__) 遍历
data.ALL_DIRECTIVES
锁定"每种指令都有对应方法",是新增指令类型时的唯一防线。
get_accounts_use_map 是基础实现,边遍历边填两张
dict:
for entry in entries:
method = getattr(self, entry.__class__.__name__)
for account_ in method(entry):
if account_ not in accounts_first:
accounts_first[account_] = entry.date
accounts_last[account_] = entry.date
getters.py:51-56。accounts_first
只在键不存在时写入,accounts_last
每次覆盖——"第一次"和"最后一次"都由遍历顺序决定,函数本身不比较日期。docstring(:41-48)没有声明输入需已排序;正确性完全依赖调用方传入的是加载器排好序的流。
get_accounts 从 accounts_last
取键成集合(:143-144),docstring
注明两张表的键相同(:129-130)。get_entry_accounts
走
set(method(entry))(:70-71),每次返回新集合,调用方可以直接改;interpolate.py:337-339
就是拿到后 update(additional_accounts)。
Open 和 Close 都走 _one,所以一个只被 open
声明、从未记过账的账户仍会出现在 get_accounts
的结果里,accounts_last 也会被 Close
指令的日期刷新。getters_test.py:52-74
锁定了这两点:五个账户的 accounts_first 全是 open 那天
2012-02-01,Assets:US:Cash 的 accounts_last 是
close 那天 2014-02-01,而没有 close 的 Expenses:Grocery
停在最后一次交易 2012-05-18。
get_account_open_close:重复指令只留最早的一条open_close_map = defaultdict(lambda: [None, None])
for entry in entries:
if not isinstance(entry, (Open, Close)):
continue
open_close = open_close_map[entry.account]
index = 0 if isinstance(entry, Open) else 1
previous_entry = open_close[index]
if previous_entry is not None:
if previous_entry.date <= entry.date:
entry = previous_entry
open_close[index] = entry
return dict(open_close_map)
getters.py:330-342。docstring 明说规则:"If an open or
close entry happens to be duplicated, accept the earliest entry
(chronologically)"(:320-321)。写法是保留槽位里已有的那条,仅当新条目更早时才替换;日期相等时
<= 让先出现的那条胜出。重复的 Open/Close 本身由
ops/validation.py
报成错误,这里只保证下游拿到的映射是确定的一条。这段防御来自 2014-06-21
commit ba76130a,与之前的写法(直接
open_closes_map[entry.account][index] = entry,后者覆盖前者)相反。
值是长度 2 的可变 list 而非元组,构造期需要按下标改写。返回时套一层
dict()(:342):2017-05-08 commit
4489fcb2 做的改动,CHANGES 记为 "Made the open/close map
return by the getters not a defaultdict by default, so that accidentally
looking up elements won't mutate
it."(CHANGES:1360-1362)。同一个 commit 把
plugins/leafonly.py 改成
try/except KeyError(现为
leafonly.py:48-51),因为查一个不存在的账户不再静默插入
[None, None]。
未出现在任何 Open/Close 指令里的账户根本不进这张表,所以
account not in open_close_map
表示"从未声明过",scripts/doctor.py:530-531
用这个条件生成缺失的 Open 指令。
get_min_max_dates 不做
min/max,直接取首尾:
for entry in entries:
if types and not isinstance(entry, types):
continue
date_first = entry.date
break
for entry in reversed(entries):
...
getters.py:283-293。两个循环各自
break,成本是 O(1)(带 types 过滤时是
O(k)),前提是 entries
已按日期升序。reversed() 还要求入参是序列,传生成器抛
TypeError。空列表或 types
过滤后无匹配时两个变量保持初值 None,返回
(None, None)(:281,295)。
get_active_years 把同样的假设写成断言:
seen = set()
prev_year = None
for entry in entries:
year = entry.date.year
if year != prev_year:
prev_year = year
assert year not in seen
seen.add(year)
yield year
getters.py:306-314。只在年份切换时产出,seen
集合的作用是让同一年份在被其他年份隔开后再次出现时立即
AssertionError——它不能验证输入整体是否按日期或年份升序:降序输入(如
2020、2019)不触发断言,正常产出 [2020, 2019];只有
2020、2019、2020 这种非连续重复的年份才会被拦下。这三行是 2013-07-05
commit 3c568161 加进去的,比 get_min_max_dates
的首尾取法更早地把"已排序"这个前提显式化。函数是生成器,断言在消费时才触发;python -O
下断言被剥离,非连续重复的年份会被重复产出而不报错。
两处对排序的依赖能够成立,靠的是加载器的保障而非 getter
自身校验:标准加载路径在 booking
前对全部指令排序,并在每个插件执行完后重新排序,防止插件打乱顺序(loader.py:596-602,738-740)。
| 函数 | 位置 | 输入 | 输出与要点 |
|---|---|---|---|
get_all_tags |
:177-191 |
entries | 只看 Transaction,if entry.tags: 后
update,返回 sorted(set) |
get_all_links |
:211-225 |
entries | 同上,字段换成 links |
get_all_payees |
:194-208 |
entries | 无条件 add(entry.payee),末尾
all_payees.discard(None) 再排序 |
get_commodity_directives |
:345-353 |
entries | 字典推导,只收 Commodity
指令;同名重复时后者覆盖前者 |
get_values_meta |
:356-382 |
名字→指令的 dict | 按 meta_keys
取元数据;单键返回值本身,多键返回元组;指令为 None
或键缺失时用 default |
get_account_components |
:161-174 |
entries | 先 get_accounts 再
account.split,组件去重排序 |
get_leveln_parent_accounts |
:228-245 |
账户名 list | 统计第 level 层组件出现次数,只留
count > nrepeats 的 |
get_dict_accounts |
:248-263 |
账户名可迭代 | 嵌套 OrderedDict |
三个 get_all_* 都跳过非 Transaction
指令(:187-188,204-205,221-222),所以只挂在
Note/Document 上的
tags/links(data.py:309-310,423-424)不会被收进来。get_all_payees
与另外两个不同,先把 None 一起塞进集合再
discard,省掉循环里的判空;返回值带
# type: ignore[arg-type]
注释(:208),因为静态类型看不出 discard
之后集合里不再有 None。三者返回排序好的 list(docstring
仍写 set,见第 6
节):get_all_tags、get_all_payees 从返回 set
改为排序 list 来自 2015-01-04 commit
b2e4e387;get_all_links 由 2018-03-27 commit
4e5b638f 新增,实现之初就是
sorted(all_links),不存在"从 set 改造"的历史。
get_dict_accounts:既是叶子又是前缀的账户leveldict = OrderedDict()
for account_name in account_names:
nested_dict = leveldict
for component in account.split(account_name):
nested_dict = nested_dict.setdefault(component, OrderedDict())
nested_dict[get_dict_accounts.ACCOUNT_LABEL] = True
return leveldict
get_dict_accounts.ACCOUNT_LABEL = "__root__"
getters.py:257-266。每个账户名逐层
setdefault 下钻,走到底后在最内层字典里插一个
"__root__": True
标记。这个标记回答的问题是:"这个节点本身是一个账户名,还是仅仅是别的账户名的中间层?"Expenses:Grocery
与 Expenses:Grocery:Bean 同时存在时,Grocery
节点的字典里既有 "__root__": True 又有子键
"Bean"——它既是叶子又是前缀。标记名不会与真实组件名冲突:账户组件的正则要求首字符是大写字母或数字(account.py:37-38),下划线开头的名字进不了合法账户名。
标记挂在函数对象上而不是模块级常量(:266),带
# type: ignore[attr-defined];调用方通过
getters.get_dict_accounts.ACCOUNT_LABEL
取用,getters_test.py:142 就是这么写的。
用 OrderedDict 而不是普通
dict,意味着键的顺序参与相等比较,而顺序由输入的账户名顺序决定:__root__
与子键谁先出现,取决于 Expenses:Grocery 和
Expenses:Grocery:Bean
哪个先被处理。getters_test.py:170 那行
("Bean", root), # Wrong order here
标记的正是这一点(详见第 7 节)。
| 决策 | 理由 | 证据 |
|---|---|---|
账户提取用类名 getattr 分发,而非
isinstance 链 |
分发表就是类体末尾两行赋值,新增指令类型时只需补一行;提取逻辑集中在一处 | getters.py:52,70,114-116 |
| 分发器实例化成模块单例 | 方法不依赖实例状态(首参写作
_),无需每次调用新建对象 |
getters.py:73,119-120 |
| 作者标注分发表是临时方案 | 归位的做法是每个指令类自带方法;集中表与 data.py
的类定义分离,易漏 |
getters.py:62-63,150-151 |
use_map 两张表靠遍历顺序而非比较日期 |
输入已由加载器排序,一次遍历即可,无需
min/max |
getters.py:54-56 |
| 重复 Open/Close 保留最早一条 | 结果确定、可复现;重复本身的报错由 validation 负责 | getters.py:320-321,336-339 |
| open/close 映射返回普通 dict | defaultdict 被误查会插入新键,污染映射 |
getters.py:342;CHANGES:1360-1362 |
get_min_max_dates 取首尾 |
已排序前提下 O(1);带 types
过滤时跳过不匹配项继续找 |
getters.py:283-293 |
get_active_years 用 seen
集合表达"每年至多一个连续区段" |
非连续重复年份立即失败;不能验证输入整体是否升序 | getters.py:312 |
get_all_* 返回排序 list |
输出可复现,可直接用于渲染 | getters.py:191,208,225;tags/payees 见
b2e4e387,links 见 4e5b638f |
get_dict_accounts 用 "__root__"
标记账户节点 |
中间节点可能同时是账户名;标记名与合法组件名不可能冲突 | getters.py:262,266;account.py:37-38 |
get_commodity_directives 只收 Commodity
指令 |
调用方只关心显式声明挂的元数据,不需要从 posting 里推断币种 | commit bd6cad6f 的提交信息 |
| 现象 | 后果 | 证据 |
|---|---|---|
分发靠 getattr(self, 类名) |
传入 ALL_DIRECTIVES 之外的对象抛
AttributeError;新增指令类型忘记登记时同样如此 |
getters.py:52,70;getters_test.py:47-50 |
类体内 Open = Close = ... = _one
遮蔽了模块导入的同名类 |
仅在 GetAccounts 的类命名空间内;模块级
Open/Close/Transaction/Commodity
不受影响,get_account_open_close 与
get_all_tags 用的仍是真实类 |
getters.py:15-18,114-116,332,187 |
Custom 走 _zero |
Custom 指令的 values
里可以出现账户名(account.py:44-46 的 TYPE
哑对象就是为在 custom 值里区分字符串与账户名而设),这些账户不会被
get_accounts 收集;但 realization.py 的
postings_by_account 会识别
dtype == account.TYPE 的 Custom
值并把该指令挂到对应账户下,两套账户发现规则并不一致 |
getters.py:116;data.py:445-448;account.py:44-46;realization.py:309-313 |
_zero 的 docstring 与 get_all_* 的
Returns 描述与实际返回类型不符 |
【文档漂移】_zero docstring 写 "An empty list",实际
return ()
是空元组;get_all_tags/get_all_payees/get_all_links
的 Returns 写 "A set of ... strings",实际返回排序后的
list |
getters.py:104-112;:182-183,199-200,216-217 |
| Open/Close 计入账户的"使用日期" | 只声明未记账的账户仍出现在 get_accounts
里;accounts_last 会被 close 日期刷新 |
getters.py:115;getters_test.py:65-73 |
get_accounts_use_map 的正确性依赖输入已排序 |
docstring 未声明该前提;乱序输入不报错,直接得到错的首末日期 | getters.py:41-48,54-56 |
get_min_max_dates 对生成器不可用 |
reversed(entries) 要求序列,生成器抛
TypeError |
getters.py:289 |
get_min_max_dates 空输入或过滤后无匹配 |
返回
(None, None),与"账本只有一条指令"时返回同一日期两次的形状一致 |
getters.py:281,295 |
get_active_years 的 assert 在
python -O 下失效 |
非连续重复的年份会被重复产出,而不是提前失败 | getters.py:312 |
get_active_years 是生成器 |
断言在消费时才触发,不是调用时 | getters.py:298,314 |
get_account_open_close 的值是可变 list |
拿到的 [open, close]
可被调用方改写,且会反映到映射里;测试、balance.py、summarize.py
解包为二元组;leafonly.py、check_average_cost.py
取 [0];doctor.py 只判断键存在 |
getters.py:330,340;getters_test.py:207;ops/balance.py:110;ops/summarize.py:721-722;plugins/leafonly.py:47-51;plugins/check_average_cost.py:62-69;scripts/doctor.py:527-531 |
同一币种多条 Commodity 指令 |
字典推导里后者覆盖前者,与 open/close 保留最早的规则相反;但重复
Commodity 本身会被 validate_duplicate_commodities
判为错误,"后者覆盖前者"只是错误输入仍被继续处理时的局部结果,不是受支持的语义 |
getters.py:353;ops/validation.py:160-191;CHANGES:4096-4099 |
get_values_meta 的 default
同时兼两种缺失 |
指令为 None
与元数据键不存在,返回同一个默认值,调用方无法区分;不传
meta_keys 时也不报错,每个名字返回空元组
() |
getters.py:374-381 |
get_leveln_parent_accounts 先
set(account_names) 去重 |
nrepeats
计的是"有多少个不同账户在该层用了这个组件",不是出现总次数;level
不校验非负,负数按 Python 负索引从末级取组件,越过负边界抛
IndexError |
getters.py:240-244 |
get_min_max_dates 的 types=() |
空元组是假值,if types and ...
判断为假,等同于不启用过滤,不会得到"无类型匹配"的
(None, None) |
getters.py:283-292 |
get_dict_accounts 的键顺序取决于输入顺序 |
OrderedDict
相等比较看顺序,同一批账户名换个顺序喂进去得到不相等的结果 |
getters.py:257-263;getters_test.py:170,182-188 |
| 9 个函数在当前仓库内无非测试调用点 | get_account_components、get_all_tags、get_all_payees、get_all_links、get_leveln_parent_accounts、get_dict_accounts、get_min_max_dates、get_active_years、get_values_meta
只被 getters_test.py 调用;v2 的 web 界面与 reports 模块在
v3 中已移除 |
全仓库 grep;commit 5f8aadde、bd6cad6f 的
stat |
getters_test.py 一个类
TestGetters(:46),17 个 test_
方法,大部分共用同一段 TEST_INPUT(:12-43):5
条 open、2 条 commodity、3 笔交易、2 条 close。
| 测试 | 行号 | 锁定的行为 |
|---|---|---|
test_methods_coverage |
47-50 | data.ALL_DIRECTIVES 里每个类名都能在
GetAccounts 上取到属性 |
test_get_accounts_use_map |
52-74 | 两张表的完整内容:首次日期全是 open 日;末次日期区分有无 close |
test_get_accounts |
76-88 | 返回 5 个账户的 set |
test_get_entry_accounts |
90-97 | 三腿交易返回 3 个账户(含自动补全的那腿) |
test_get_all_tags |
99-102 | ["books", "dinner"],已排序去重 |
test_get_all_payees |
104-107 | 只有两个 payee;无 payee 的交易不产出 None |
test_get_all_links |
109-112 | 两笔交易共用的 link 只出现一次 |
test_get_leveln_parent_accounts |
114-130 | level 0/1/2 三层横切的结果 |
test_get_dict_accounts |
132-188 | 见下 |
test_get_min_max_dates |
190-194 | 2012-02-01 与 2014-02-01 |
test_get_active_years |
196-199 | [2012, 2013, 2014] |
test_get_account_open_close |
201-214 | 表长 5;逐个账户断言 open/close 是否为 None |
test_get_account_open_close__duplicates |
216-229 | 两条 open 两条 close 时,取 01-01 的 open 与 01-28 的 close;用
expect_errors=True 接受 validation 报的重复错误 |
test_get_account_components |
231-244 | 8 个组件,已排序 |
test_get_commodity_directives |
246-252 | 键为 {"HOOL", "PIPA"},值都是
Commodity |
test_get_values_meta__single |
254-258 | 单键返回值本身,不套元组 |
test_get_values_meta__multi |
260-266 | 多键返回元组,缺失的 ticker 为默认值
None |
test_get_dict_accounts(:132-188)的形态特殊。输入里
Expenses:Grocery 排在 Expenses:Grocery:Bean
前面,所以实际结果中 Grocery 节点是
{"__root__": True, "Bean": {...}};测试把期望值写成相反的顺序并标注
# Wrong order here(:170),断言用的是
assertNotEqual(:182),演示的是"OrderedDict
的相等比较看顺序"。随后 :183-188 把
account_dict["Expenses"]["Grocery"]
改成正确顺序,但函数到此结束,没有再做
assertEqual——修正后的期望值没有被任何断言使用。这个形态从
2018-03-11 commit 6e97fe97 引入起就是如此:该 commit
把原先针对普通 dict 的 assertEqual 改成了
OrderedDict 版本的 assertNotEqual。
getters_test.py
未直接覆盖的行为:GetAccounts 对 Pad
指令的提取(TEST_INPUT 里没有
Pad);get_min_max_dates 的 types
参数与空输入;get_active_years 的 assert
触发路径;get_leveln_parent_accounts 的
nrepeats > 0 与负数
level;get_values_meta 传入值为
None 的表以及不传
meta_keys;分发到不存在方法时的
AttributeError。test_methods_coverage(:47-50)只验证
GetAccounts
上存在同名属性,不验证指令是否分配到正确处理器。
全仓库层面:TestValidateActiveAccounts(validation_test.py:135-201)经
get_entry_accounts 断言含 Note、Pad、关闭后 Document
的错误;TestValidateDuplicateCommodities(:117-132)断言重复
Commodity 错误;但均未锁定 getters.py
内部语义——Pad
双账户返回、各指令到处理器映射、get_commodity_directives
的"后者覆盖前者",全仓库无测试直接断言。
主线 2017-04-30 移除 src/python/
前缀(CHANGES:1364-1369),但历史分支提交仍可能显示旧路径:晚于该日期的
4e5b638f(2018-03-27)diff 仍是
src/python/beancount/core/getters.py,不可仅按日期推断路径。
| 日期 | 提交 / 记录 | 变化 |
|---|---|---|
| 2013-07-05 | 3c568161 |
get_active_years 加入 seen 集合与
assert year not in seen |
| 2014-06-21 | ba76130a |
GetAccounts.__call__ 改名
get_accounts;新增 get_entry_accounts 与那条
"should get replaced by a method on each directive"
注释;引入全局单例;get_account_open_close
加入"重复取最早"规则 |
| 2014-08-31 | ade7e62a |
get_accounts 扩展出
use_map(首次/末次使用日期),为 auto_accounts
插件的自动补 Open 服务 |
| 2015-01-04 | b2e4e387 |
get_all_tags、get_all_payees 从返回 set
改为返回排序后的 list |
| 2015-01-17 | 96e6bb13 |
新增 get_commodity_map 与
get_values_meta;同时加入
test_methods_coverage |
| 2015-01-18 | CHANGES:4086-4094 |
Commodity 指令正式加入,配套函数为
get_commodity_map |
| 2015-11-28 | a3c06172 |
删除 get_commodity_map 的一个未使用参数 |
| 2016-02-28 | 376a3ccc |
新增 Custom 指令类型,分发表加上
Custom = ... = _zero |
| 2017-05-08 | 4489fcb2 |
get_account_open_close 返回 dict(...) 而非
defaultdict;连带修改 leafonly.py |
| 2018-03-10 | b66f8bee |
新增 get_dict_accounts(外部贡献) |
| 2018-03-11 | 6e97fe97 |
该函数的测试改用 OrderedDict 并加入 "wrong order"
演示 |
| 2018-03-27 | 4e5b638f |
新增 get_all_links(仿照
get_all_tags),实现之初即返回
sorted(all_links) |
| 2020-07-02 | bd6cad6f |
get_commodity_map 简化并改名
get_commodity_directives:删掉从 posting/Balance/Price
收集币种以及自动补建 Commodity
指令的逻辑,提交信息称改名是为避免外部代码"possible subtle
breakage" |
| 2020-10-29 | 22ab3984 → 25f43f2b |
为 issue #570 加回 get_commodity_map 兼容
stub,同日撤销;撤销信息称改名是有意为之,因为行为不同 |
| 2024-11-09 | a9fd82def |
加 from __future__ import annotations 与
TYPE_CHECKING
块;顶层函数补类型注解;get_all_payees 返回处加
# type: ignore[arg-type] |
| 2024-12-16 | 955876a9 |
扩充 TYPE_CHECKING 块(补
Account/Directive/Pad
等类型)并新增 AccountsUseMap
别名;GetAccounts
各方法与部分顶层函数补齐注解,get_dict_accounts、get_account_open_close、get_values_meta
仍无类型注解;ACCOUNT_LABEL 赋值处加
# type: ignore[attr-defined] |
| 2024-12-23 | 0f879811 |
为启用 mypy,AccountsUseMap 暂改用
typing.Dict/typing.Tuple 而非内置泛型 |
| 2024-12-23 | 8108c924 |
Python 基线提到 3.9,AccountsUseMap 改回 PEP 585
内置泛型;get_accounts 返回类型改为 set[str]
并在实现里包一层 set(...) |
data.py 的四个指令类和
account.split。Balance、Note、Document、Pad
等名字只在类型注解里用到,放在 TYPE_CHECKING
块(getters.py:20-32);类型别名
AccountsUseMap 也定义在那里(:34)。ops/balance.py:83 用
get_accounts 预建 realization
节点;ops/documents.py:55 与
scripts/directories.py:65
用它对照文件系统目录;scripts/example.py:1665
生成示例账本时用它。ops/validation.py:226
检查指令引用的账户是否处于开启状态;core/interpolate.py:337
计算某条指令的余额上下文;parser/context.py:105
打印上下文;plugins/nounused.py:45
统计被引用过的账户。plugins/auto_accounts.py:32-36
按首次使用日期补建 Open 指令;scripts/doctor.py:526-531
做同样的事并输出到文件。ops/balance.py:90、ops/summarize.py:721-722、plugins/leafonly.py:47、plugins/check_average_cost.py:62、projects/export.py:85、scripts/doctor.py:527。projects/export.py:76 取
get_commodity_directives 的结果后,用该文件本地的
get_metamap_table(:60-71)与一个内联
lambda(:78)读取每个币种的元数据;get_values_meta
当前在仓库内没有非测试调用点,只被 getters_test.py
调用。get_dict_accounts 把账户名折成仅含名称与
__root__ 标记的轻量嵌套
OrderedDict(:248-263,标签
:266);core/realization.py 独立建
RealAccount 树——realize() 调
postings_by_account()(:278-315)分组,再用
get_or_create() 建节点并挂 TxnPosting、非
Transaction 指令与 Inventory
余额(:45-66,211-267)——不依赖
get_dict_accounts(无非测试调用点,见第 6 节),二者对
Custom 账户值的识别规则也不一致。beancount/core/getters.py:1-3 模块 docstring;10-18
导入;20-34 TYPE_CHECKING 与
AccountsUseMap;37-38 类定义;40-57
get_accounts_use_map 方法;51-56 遍历主体;59-71
get_entry_accounts 方法;62-63 作者注释;73-82
Transaction;84-92 Pad;94-102
_one;104-112 _zero;114-116 分发表;119-120
单例;123-132 顶层 get_accounts_use_map;135-144
get_accounts;147-158 顶层
get_entry_accounts;161-174
get_account_components;177-191
get_all_tags;194-208 get_all_payees;211-225
get_all_links;228-245
get_leveln_parent_accounts;248-263
get_dict_accounts;266 ACCOUNT_LABEL;269-295
get_min_max_dates;298-314
get_active_years;317-342
get_account_open_close;345-353
get_commodity_directives;356-382
get_values_meta。
beancount/core/getters_test.py:12-43 TEST_INPUT;46
测试类;47-50、52-74、76-88、90-97、99-102、104-107、109-112、114-130、132-188(170
"Wrong order here"、182
assertNotEqual)、190-194、196-199、201-214、216-229、231-244、246-252、254-258、260-266。
其它:beancount/core/data.py:452-465
ALL_DIRECTIVES、:504
Directives、:309-310,423-424 Note/Document 的
tags/links、:445-448
Custom.values;beancount/core/account.py:29
sep、:37-38 组件正则、:44-46 TYPE
哑对象、:98-106
split;beancount/api.py:60-61,148,153;beancount/core/interpolate.py:337-339;beancount/loader.py:596-602,738-740
排序保障;beancount/ops/validation.py:160-191
validate_duplicate_commodities、:226;beancount/ops/balance.py:83,90;beancount/ops/summarize.py:721-722;beancount/ops/documents.py:55;beancount/core/realization.py:45-66
RealAccount、:211-267
realize/树构建、:278-315,309-313
postings_by_account 对 Custom
的处理;beancount/plugins/auto_accounts.py:32-36;beancount/plugins/leafonly.py:47-51;beancount/plugins/nounused.py:45;beancount/plugins/check_average_cost.py:62;beancount/projects/export.py:60-71,74-80,85;beancount/parser/context.py:105;beancount/scripts/doctor.py:526-531;beancount/scripts/directories.py:65;beancount/scripts/example.py:1665;CHANGES:1360-1362,4086-4094,4096-4099。
commit:3c568161(2013-07-05)、ba76130a(2014-06-21)、ade7e62a(2014-08-31)、b2e4e387(2015-01-04)、96e6bb13(2015-01-17)、a3c06172(2015-11-28)、376a3ccc(2016-02-28)、4489fcb2(2017-05-08)、b66f8bee(2018-03-10)、6e97fe97(2018-03-11)、4e5b638f(2018-03-27)、bd6cad6f(2020-07-02)、22ab3984
与
25f43f2b(2020-10-29)、a9fd82def(2024-11-09)、955876a9(2024-12-16)、0f879811(2024-12-23,先)、8108c924(2024-12-23,后,0f879811
的子孙提交)。