模組:PythonScript/Librarys
外观
-- Module:PythonScript/Librarys
-- 中小型Built-in標準庫合集(二)
-- 1. itertools
-- 2. functools
-- 3. collections
local Librarys = {}
local PyClass = require("Module:PythonScript/Class")
local Runtime = require("Module:PythonScript/Runtime")
-- ==========================================
-- 內部輔助函數
-- ==========================================
local function wrap_func(func)
return function(py_args, py_kwargs)
if py_kwargs and next(py_kwargs) then
Runtime.raise("TypeError", "module functions do not support keyword arguments")
end
return func(unpack(py_args or {}))
end
end
local function make_namespace(name, tbl)
local dict = {}
for k, v in pairs(tbl) do
if type(v) == "function" then
dict[k] = wrap_func(v)
elseif type(v) == "table" then
dict[k] = make_namespace(name .. "_" .. k, v)
else
dict[k] = v
end
end
return PyClass.make_instance(PyClass.make_class(name, {}, dict))
end
local stdlibs = {}
-- ==========================================
-- 1. itertools
-- ==========================================
local function init_itertools_module()
local Evaluator = require("Module:PythonScript/Evaluator")
local B = Runtime.builtins
local SENTINEL = {}
local cache = {}
local iter_classes = {}
local function is_none(v)
return v == nil or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
end
local function to_int(v, err_msg)
if type(v) == "boolean" then return v and 1 or 0 end
if type(v) == "table" and v.__type == "int" then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
v = v.value or tonumber(MathCore.BigInt.to_string(v.sign, v.limbs))
end
if type(v) ~= "number" or math.floor(v) ~= v then
Runtime.raise("TypeError", err_msg or "expected integer")
end
return v
end
local function to_array(iterable)
if type(iterable) == "table" and (iterable.__type == "list" or iterable.__type == "tuple") then
local copy = {}
for i = 1, #iterable.elements do copy[i] = iterable.elements[i] end
return copy
end
local arr = {}
local it = B.iter({iterable})
while true do
local v = B.next({it, SENTINEL})
if v == SENTINEL then break end
table.insert(arr, v)
end
return arr
end
local function make_iter(name, step_fn)
local cls = iter_classes[name]
if not cls then
cls = PyClass.make_class(name, {}, {
__iter__ = { __type = "builtin_method", __call = function(a) return a[1] end },
__next__ = { __type = "builtin_method", __call = function(a) return a[1].__dict._step() end }
})
cls.__dict["__module__"] = "itertools"
iter_classes[name] = cls
end
local inst = PyClass.make_instance(cls)
inst.__dict._step = step_fn
return inst
end
local function make_chain_from_outer(outer_it)
local inner_it = nil
return make_iter("chain", function()
while true do
if not inner_it then
local next_iterable = B.next({outer_it, SENTINEL})
if next_iterable == SENTINEL then Runtime.raise("StopIteration") end
inner_it = B.iter({next_iterable})
end
local val = B.next({inner_it, SENTINEL})
if val ~= SENTINEL then return val end
inner_it = nil
end
end)
end
local function get_attr(attr)
if cache[attr] ~= nil then return cache[attr] end
-- [1] 無限迭代器家族: count, cycle, repeat
if attr == "count" then
cache.count = {
__type = "builtin_method", __name = "count",
__call = function(args, kwargs)
local start = (args and args[1] ~= nil) and args[1] or (kwargs and kwargs.start) or 0
local step = (args and args[2] ~= nil) and args[2] or (kwargs and kwargs.step) or 1
local curr = start
return make_iter("count", function()
local ret = curr
if type(curr) == "number" and type(step) == "number" then
local nxt = curr + step
if math.abs(nxt) > 9007199254740990 and math.floor(curr) == curr and math.floor(step) == step then
curr = Evaluator.bigint_op(curr, step, "+")
else
curr = nxt
end
elseif (type(curr) == "table" and curr.__type == "int") or (type(step) == "table" and step.__type == "int") then
curr = Evaluator.bigint_op(curr, step, "+")
elseif (type(curr) == "table" and curr.__type == "complex") or (type(step) == "table" and step.__type == "complex") then
curr = require("Module:PythonScript/Runtime/Cmath").Cmath.add(curr, step)
else
Runtime.raise("TypeError", "a number is required")
end
return ret
end)
end
}
return cache.count
elseif attr == "cycle" then
cache.cycle = {
__type = "builtin_method", __name = "cycle",
__call = function(args)
if not args or #args ~= 1 then Runtime.raise("TypeError", "cycle expected 1 argument") end
local it = B.iter({args[1]})
local saved = {}
local exhausted = false
local idx = 0
return make_iter("cycle", function()
if not exhausted then
local v = B.next({it, SENTINEL})
if v ~= SENTINEL then
table.insert(saved, v)
return v
end
exhausted = true
end
if #saved == 0 then Runtime.raise("StopIteration") end
idx = (idx % #saved) + 1
return saved[idx]
end)
end
}
return cache.cycle
elseif attr == "repeat" then
cache["repeat"] = {
__type = "builtin_method", __name = "repeat",
__call = function(args, kwargs)
if not args or #args < 1 then Runtime.raise("TypeError", "repeat() missing required argument 'object'") end
local obj = args[1]
local raw_times = (args[2] ~= nil) and args[2] or (kwargs and kwargs.times)
local has_times = not is_none(raw_times)
local remaining = has_times and math.max(0, to_int(raw_times)) or nil
return make_iter("repeat", function()
if has_times then
if remaining <= 0 then Runtime.raise("StopIteration") end
remaining = remaining - 1
end
return obj
end)
end
}
return cache["repeat"]
-- [2] 串接與切片: chain (含 from_iterable), islice
elseif attr == "chain" then
local chain_fn = {
__type = "builtin_method", __name = "chain",
__dict = {
from_iterable = {
__type = "builtin_method", __name = "from_iterable",
__call = function(args)
if not args or #args ~= 1 then Runtime.raise("TypeError", "from_iterable() takes exactly one argument") end
return make_chain_from_outer(B.iter({args[1]}))
end
}
},
__call = function(args)
local list_arg = { __type = "list", elements = args or {} }
return make_chain_from_outer(B.iter({list_arg}))
end
}
cache.chain = chain_fn
return cache.chain
elseif attr == "islice" then
cache.islice = {
__type = "builtin_method", __name = "islice",
__call = function(args)
if not args or #args < 2 or #args > 4 then
Runtime.raise("TypeError", "islice expected 2 to 4 arguments")
end
local it = B.iter({args[1]})
local start, stop, step = 0, nil, 1
if #args == 2 then
if not is_none(args[2]) then stop = to_int(args[2]) end
else
if not is_none(args[2]) then start = to_int(args[2]) end
if not is_none(args[3]) then stop = to_int(args[3]) end
if #args == 4 and not is_none(args[4]) then step = to_int(args[4]) end
end
if start < 0 or (stop ~= nil and stop < 0) or step < 1 then
Runtime.raise("ValueError", "Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize, and step >= 1.")
end
local curr_idx = 0
local next_idx = start
local done = false
return make_iter("islice", function()
if done or (stop ~= nil and next_idx >= stop) then
done = true
Runtime.raise("StopIteration")
end
while curr_idx < next_idx do
local skipped = B.next({it, SENTINEL})
if skipped == SENTINEL then
done = true
Runtime.raise("StopIteration")
end
curr_idx = curr_idx + 1
end
local val = B.next({it, SENTINEL})
if val == SENTINEL then
done = true
Runtime.raise("StopIteration")
end
curr_idx = curr_idx + 1
next_idx = next_idx + step
return val
end)
end
}
return cache.islice
-- [3] 資料轉換與配對: accumulate, compress, starmap, pairwise, batched, zip_longest, dropwhile, takewhile, filterfalse
elseif attr == "accumulate" then
cache.accumulate = {
__type = "builtin_method", __name = "accumulate",
__call = function(args, kwargs)
if not args or #args < 1 then Runtime.raise("TypeError", "accumulate() missing required argument 'iterable'") end
local it = B.iter({args[1]})
local func = (args[2] ~= nil) and args[2] or (kwargs and kwargs.func)
if is_none(func) then func = nil end
local initial = kwargs and kwargs.initial
local has_initial = not is_none(initial)
local started = false
local total = nil
local function default_add(a, b)
if type(a) == "number" and type(b) == "number" then
local s = a + b
if math.abs(s) > 9007199254740990 and math.floor(a) == a and math.floor(b) == b then
return Evaluator.bigint_op(a, b, "+")
end
return s
elseif (type(a) == "table" and a.__type == "int") or (type(b) == "table" and b.__type == "int") then
return Evaluator.bigint_op(a, b, "+")
elseif type(a) == "string" and type(b) == "string" then
return a .. b
elseif type(a) == "table" and type(b) == "table" and a.__type == b.__type and (a.__type == "list" or a.__type == "tuple") then
local res = {}
for _, v in ipairs(a.elements) do table.insert(res, v) end
for _, v in ipairs(b.elements) do table.insert(res, v) end
return { __type = a.__type, elements = res }
end
Runtime.raise("TypeError", "unsupported operand type(s) for + in accumulate()")
end
return make_iter("accumulate", function()
if not started then
started = true
if has_initial then
total = initial
return total
end
local first = B.next({it, SENTINEL})
if first == SENTINEL then Runtime.raise("StopIteration") end
total = first
return total
end
local val = B.next({it, SENTINEL})
if val == SENTINEL then Runtime.raise("StopIteration") end
if func then
total = Evaluator.invoke(func, {total, val}, {})
else
total = default_add(total, val)
end
return total
end)
end
}
return cache.accumulate
elseif attr == "compress" then
cache.compress = {
__type = "builtin_method", __name = "compress",
__call = function(args, kwargs)
local data = (args and args[1]) or (kwargs and kwargs.data)
local selectors = (args and args[2]) or (kwargs and kwargs.selectors)
if data == nil or selectors == nil then Runtime.raise("TypeError", "compress() requires data and selectors") end
local it_d = B.iter({data})
local it_s = B.iter({selectors})
return make_iter("compress", function()
while true do
local d = B.next({it_d, SENTINEL})
if d == SENTINEL then Runtime.raise("StopIteration") end
local s = B.next({it_s, SENTINEL})
if s == SENTINEL then Runtime.raise("StopIteration") end
if B.bool.__call({s}) then return d end
end
end)
end
}
return cache.compress
elseif attr == "starmap" then
cache.starmap = {
__type = "builtin_method", __name = "starmap",
__call = function(args)
if not args or #args ~= 2 then Runtime.raise("TypeError", "starmap expected 2 arguments") end
local func = args[1]
local it = B.iter({args[2]})
return make_iter("starmap", function()
local seq = B.next({it, SENTINEL})
if seq == SENTINEL then Runtime.raise("StopIteration") end
return Evaluator.invoke(func, to_array(seq), {})
end)
end
}
return cache.starmap
elseif attr == "pairwise" then
cache.pairwise = {
__type = "builtin_method", __name = "pairwise",
__call = function(args)
if not args or #args ~= 1 then Runtime.raise("TypeError", "pairwise expected 1 argument") end
local it = B.iter({args[1]})
local prev = nil
local initialized = false
local done = false
return make_iter("pairwise", function()
if done then Runtime.raise("StopIteration") end
if not initialized then
initialized = true
prev = B.next({it, SENTINEL})
if prev == SENTINEL then
done = true
Runtime.raise("StopIteration")
end
end
local curr = B.next({it, SENTINEL})
if curr == SENTINEL then
done = true
Runtime.raise("StopIteration")
end
local res = { __type = "tuple", elements = { prev, curr } }
prev = curr
return res
end)
end
}
return cache.pairwise
elseif attr == "batched" then
cache.batched = {
__type = "builtin_method", __name = "batched",
__call = function(args)
if not args or #args ~= 2 then Runtime.raise("TypeError", "batched expected 2 arguments") end
local it = B.iter({args[1]})
local n = to_int(args[2])
if n < 1 then Runtime.raise("ValueError", "n must be at least one") end
local done = false
return make_iter("batched", function()
if done then Runtime.raise("StopIteration") end
local batch = {}
for i = 1, n do
local v = B.next({it, SENTINEL})
if v == SENTINEL then
done = true
break
end
table.insert(batch, v)
end
if #batch == 0 then Runtime.raise("StopIteration") end
return { __type = "tuple", elements = batch }
end)
end
}
return cache.batched
elseif attr == "zip_longest" then
cache.zip_longest = {
__type = "builtin_method", __name = "zip_longest",
__call = function(args, kwargs)
local fillvalue = (kwargs and kwargs.fillvalue ~= nil) and kwargs.fillvalue or B.None
local iters = {}
for i, arg in ipairs(args or {}) do
iters[i] = B.iter({arg})
end
local active = #iters
return make_iter("zip_longest", function()
if active == 0 then Runtime.raise("StopIteration") end
local values = {}
for i = 1, #iters do
local it = iters[i]
if not it then
values[i] = fillvalue
else
local v = B.next({it, SENTINEL})
if v == SENTINEL then
active = active - 1
if active == 0 then Runtime.raise("StopIteration") end
iters[i] = false
values[i] = fillvalue
else
values[i] = v
end
end
end
return { __type = "tuple", elements = values }
end)
end
}
return cache.zip_longest
elseif attr == "dropwhile" then
cache.dropwhile = {
__type = "builtin_method", __name = "dropwhile",
__call = function(args)
if not args or #args ~= 2 then Runtime.raise("TypeError", "dropwhile expected 2 arguments") end
local pred, it = args[1], B.iter({args[2]})
local dropping = true
return make_iter("dropwhile", function()
while true do
local v = B.next({it, SENTINEL})
if v == SENTINEL then Runtime.raise("StopIteration") end
if dropping then
if not B.bool.__call({Evaluator.invoke(pred, {v}, {})}) then
dropping = false
return v
end
else
return v
end
end
end)
end
}
return cache.dropwhile
elseif attr == "takewhile" then
cache.takewhile = {
__type = "builtin_method", __name = "takewhile",
__call = function(args)
if not args or #args ~= 2 then Runtime.raise("TypeError", "takewhile expected 2 arguments") end
local pred, it = args[1], B.iter({args[2]})
local done = false
return make_iter("takewhile", function()
if done then Runtime.raise("StopIteration") end
local v = B.next({it, SENTINEL})
if v == SENTINEL then done = true; Runtime.raise("StopIteration") end
if B.bool.__call({Evaluator.invoke(pred, {v}, {})}) then
return v
end
done = true
Runtime.raise("StopIteration")
end)
end
}
return cache.takewhile
elseif attr == "filterfalse" then
cache.filterfalse = {
__type = "builtin_method", __name = "filterfalse",
__call = function(args)
if not args or #args ~= 2 then Runtime.raise("TypeError", "filterfalse expected 2 arguments") end
local pred, it = args[1], B.iter({args[2]})
local use_bool = is_none(pred)
return make_iter("filterfalse", function()
while true do
local v = B.next({it, SENTINEL})
if v == SENTINEL then Runtime.raise("StopIteration") end
local cond = use_bool and B.bool.__call({v}) or B.bool.__call({Evaluator.invoke(pred, {v}, {})})
if not cond then return v end
end
end)
end
}
return cache.filterfalse
-- [4] 排列組合與笛卡兒積家族: product, permutations, combinations, combinations_with_replacement
elseif attr == "product" then
cache.product = {
__type = "builtin_method", __name = "product",
__call = function(args, kwargs)
local rep = (kwargs and kwargs["repeat"] ~= nil) and to_int(kwargs["repeat"]) or 1
if rep < 0 then Runtime.raise("ValueError", "repeat argument cannot be negative") end
local base_pools = {}
for i, arg in ipairs(args or {}) do
base_pools[i] = to_array(arg)
end
local pools = {}
local done = false
for _ = 1, rep do
for _, p in ipairs(base_pools) do
if #p == 0 then done = true end
table.insert(pools, p)
end
end
local k = #pools
local indices = {}
for i = 1, k do indices[i] = 1 end
local first = true
return make_iter("product", function()
if done then Runtime.raise("StopIteration") end
if k == 0 then
done = true
return { __type = "tuple", elements = {} }
end
if first then
first = false
else
local pos = k
while pos >= 1 do
indices[pos] = indices[pos] + 1
if indices[pos] <= #pools[pos] then break end
indices[pos] = 1
pos = pos - 1
end
if pos == 0 then
done = true
Runtime.raise("StopIteration")
end
end
local res = {}
for i = 1, k do res[i] = pools[i][indices[i]] end
return { __type = "tuple", elements = res }
end)
end
}
return cache.product
elseif attr == "permutations" then
cache.permutations = {
__type = "builtin_method", __name = "permutations",
__call = function(args, kwargs)
if not args or #args < 1 then Runtime.raise("TypeError", "permutations() missing required argument 'iterable'") end
local pool = to_array(args[1])
local n = #pool
local raw_r = (args[2] ~= nil) and args[2] or (kwargs and kwargs.r)
local r = is_none(raw_r) and n or to_int(raw_r)
if r < 0 then Runtime.raise("ValueError", "r must be non-negative") end
local done = (r > n)
local first = true
local indices, cycles = {}, {}
for i = 1, n do indices[i] = i end
for i = 1, r do cycles[i] = n - i + 1 end
return make_iter("permutations", function()
if done then Runtime.raise("StopIteration") end
if r == 0 then
done = true
return { __type = "tuple", elements = {} }
end
if first then
first = false
local res = {}
for i = 1, r do res[i] = pool[indices[i]] end
return { __type = "tuple", elements = res }
end
local advanced = false
for i = r, 1, -1 do
cycles[i] = cycles[i] - 1
if cycles[i] == 0 then
local tmp = indices[i]
for k = i, n - 1 do indices[k] = indices[k + 1] end
indices[n] = tmp
cycles[i] = n - i + 1
else
local j = n - cycles[i] + 1
indices[i], indices[j] = indices[j], indices[i]
advanced = true
break
end
end
if not advanced then
done = true
Runtime.raise("StopIteration")
end
local res = {}
for i = 1, r do res[i] = pool[indices[i]] end
return { __type = "tuple", elements = res }
end)
end
}
return cache.permutations
elseif attr == "combinations" then
cache.combinations = {
__type = "builtin_method", __name = "combinations",
__call = function(args, kwargs)
if not args or #args < 1 then Runtime.raise("TypeError", "combinations() missing required argument") end
local pool = to_array(args[1])
local n = #pool
local raw_r = (args[2] ~= nil) and args[2] or (kwargs and kwargs.r)
if raw_r == nil then Runtime.raise("TypeError", "combinations() missing required argument 'r'") end
local r = to_int(raw_r)
if r < 0 then Runtime.raise("ValueError", "r must be non-negative") end
local done = (r > n)
local first = true
local indices = {}
for i = 1, r do indices[i] = i end
return make_iter("combinations", function()
if done then Runtime.raise("StopIteration") end
if r == 0 then
done = true
return { __type = "tuple", elements = {} }
end
if first then
first = false
else
local piv = 0
for i = r, 1, -1 do
if indices[i] ~= i + n - r then
piv = i
break
end
end
if piv == 0 then
done = true
Runtime.raise("StopIteration")
end
indices[piv] = indices[piv] + 1
for j = piv + 1, r do
indices[j] = indices[j - 1] + 1
end
end
local res = {}
for i = 1, r do res[i] = pool[indices[i]] end
return { __type = "tuple", elements = res }
end)
end
}
return cache.combinations
elseif attr == "combinations_with_replacement" then
cache.combinations_with_replacement = {
__type = "builtin_method", __name = "combinations_with_replacement",
__call = function(args, kwargs)
if not args or #args < 1 then Runtime.raise("TypeError", "combinations_with_replacement() missing required argument") end
local pool = to_array(args[1])
local n = #pool
local raw_r = (args[2] ~= nil) and args[2] or (kwargs and kwargs.r)
if raw_r == nil then Runtime.raise("TypeError", "combinations_with_replacement() missing required argument 'r'") end
local r = to_int(raw_r)
if r < 0 then Runtime.raise("ValueError", "r must be non-negative") end
local done = (n == 0 and r > 0)
local first = true
local indices = {}
for i = 1, r do indices[i] = 1 end
return make_iter("combinations_with_replacement", function()
if done then Runtime.raise("StopIteration") end
if r == 0 then
done = true
return { __type = "tuple", elements = {} }
end
if first then
first = false
else
local piv = 0
for i = r, 1, -1 do
if indices[i] ~= n then
piv = i
break
end
end
if piv == 0 then
done = true
Runtime.raise("StopIteration")
end
local next_v = indices[piv] + 1
for j = piv, r do
indices[j] = next_v
end
end
local res = {}
for i = 1, r do res[i] = pool[indices[i]] end
return { __type = "tuple", elements = res }
end)
end
}
return cache.combinations_with_replacement
end
return nil
end
local mod_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, a = args[1], args[2]
local val = get_attr(a)
if val ~= nil then
self.__dict[a] = val
return val
end
Runtime.raise("AttributeError", "module 'itertools' has no attribute '" .. tostring(a) .. "'")
end
}
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "itertools"
return mod
end
-- ==========================================
-- 2. functools
-- ==========================================
local function init_functools_module()
local Evaluator = require("Module:PythonScript/Evaluator")
local cache = {}
local WRAPPER_ASSIGNMENTS = {
__type = "tuple",
elements = { "__module__", "__name__", "__qualname__", "__doc__", "__annotations__" }
}
local WRAPPER_UPDATES = {
__type = "tuple",
elements = { "__dict__" }
}
-- 輔助:將任意可雜湊 Python 參數序列化為唯一快取鍵值 (同時檢查不可雜湊型別)
local function serialize_hashable(val, typed)
local t = type(val)
if t == "number" then
if val ~= val then return "NaN" end
if typed then
local is_int = (math.floor(val) == val and math.abs(val) <= 9007199254740990)
return (is_int and "I:" or "F:") .. tostring(val)
end
return "N:" .. tostring(val)
elseif t == "boolean" then
if typed then return val and "B:1" or "B:0" end
return val and "N:1" or "N:0"
elseif t == "string" then
return "S#" .. #val .. ":" .. val
elseif val == nil or (t == "table" and val.__class and val.__class.__name == "NoneType") then
return "None"
elseif t == "table" then
local pt = val.__type
if pt == "list" or pt == "dict" or pt == "set" or pt == "bytearray" then
Runtime.raise("TypeError", "unhashable type: '" .. pt .. "'")
elseif pt == "int" and val.is_big then
local MathCore = require("Module:PythonScript/Runtime/Cmath")
return (typed and "I:" or "N:") .. MathCore.BigInt.to_string(val.sign, val.limbs)
elseif pt == "tuple" then
local parts = {}
for i = 1, #val.elements do
parts[i] = serialize_hashable(val.elements[i], typed)
end
return "T#" .. #val.elements .. "(" .. table.concat(parts, ",") .. ")"
elseif pt == "frozenset" then
local parts = {}
for i = 1, #val.elements do
parts[i] = serialize_hashable(val.elements[i], typed)
end
table.sort(parts)
return "FS#" .. #val.elements .. "{" .. table.concat(parts, ",") .. "}"
elseif pt == "bytes" or pt == "memoryview" then
return "BY#" .. #val._data .. ":" .. val._data
elseif pt == "complex" then
if val.imag == 0 and not typed then return "N:" .. tostring(val.real) end
return "C:" .. tostring(val.real) .. ":" .. tostring(val.imag)
else
Runtime.builtins.hash({val})
return "O:" .. tostring(val)
end
end
return "U:" .. tostring(val)
end
local function make_cache_key(args, kwargs, typed)
local n_args = args and #args or 0
local has_kw = kwargs and next(kwargs) ~= nil
-- 極速通道:單一數值參數且無 kwargs 及 typed=False 時,直接使用 Lua number 作為索引
if not typed and not has_kw and n_args == 1 then
local a1 = args[1]
if type(a1) == "number" and a1 == a1 then
return a1
end
end
local parts = {}
for i = 1, n_args do
parts[i] = serialize_hashable(args[i], typed)
end
if has_kw then
local kw_keys = {}
for k, _ in pairs(kwargs) do table.insert(kw_keys, k) end
table.sort(kw_keys)
table.insert(parts, "|KW|")
for _, k in ipairs(kw_keys) do
table.insert(parts, "K#" .. #k .. ":" .. k .. "=" .. serialize_hashable(kwargs[k], typed))
end
end
return "K:" .. table.concat(parts, ";")
end
local CacheInfoClass = nil
local function get_cache_info_class()
if not CacheInfoClass then
local collections_mod = Librarys.get_module("collections")
local nt_fn = PyClass.get_attr(collections_mod, "namedtuple")
CacheInfoClass = nt_fn.__call({
"CacheInfo",
{ __type = "list", elements = { "hits", "misses", "maxsize", "currsize" } }
})
end
return CacheInfoClass
end
local function copy_wrapper_meta(wrapper, wrapped)
wrapper.__dict = wrapper.__dict or {}
for _, attr in ipairs({ "__module__", "__name__", "__qualname__", "__doc__" }) do
local ok, v = pcall(PyClass.get_attr, wrapped, attr)
if ok and v ~= nil then
PyClass.raw_set_attr(wrapper, attr, v)
end
end
PyClass.raw_set_attr(wrapper, "__wrapped__", wrapped)
return wrapper
end
local function create_lru_wrapper(user_func, maxsize, typed)
if not Runtime.builtins.callable({user_func}) then
Runtime.raise("TypeError", "the first argument must be callable")
end
local hits, misses, currsize = 0, 0, 0
local cache_map = {}
-- 雙向環狀鏈結串列 (僅在有限 maxsize > 0 時啟用)
local root = {}
root.prev = root
root.next = root
local wrapper = {
__type = "builtin_method",
__name = (type(user_func) == "table" and (user_func.__name or user_func.__qualname__)) or "<cached_func>",
__dict = {}
}
if maxsize == nil then
-- 無上限快取 (@cache 或 maxsize=None):免維護鏈結串列,極致高速
wrapper.__call = function(args, kwargs)
local key = make_cache_key(args, kwargs, typed)
local cached = cache_map[key]
if cached ~= nil then
hits = hits + 1
return cached
end
misses = misses + 1
local res = Evaluator.invoke(user_func, args, kwargs)
if res == nil then res = Runtime.builtins.None end
if cache_map[key] == nil then currsize = currsize + 1 end
cache_map[key] = res
return res
end
elseif maxsize == 0 then
-- maxsize == 0:停用快取,僅統計 misses
wrapper.__call = function(args, kwargs)
make_cache_key(args, kwargs, typed)
misses = misses + 1
return Evaluator.invoke(user_func, args, kwargs)
end
else
-- 具容量上限的 LRU 快取 (O(1) 雙向鏈結串列更新與淘汰)
wrapper.__call = function(args, kwargs)
local key = make_cache_key(args, kwargs, typed)
local node = cache_map[key]
if node ~= nil then
hits = hits + 1
node.prev.next = node.next
node.next.prev = node.prev
local last = root.prev
last.next = node
root.prev = node
node.prev = last
node.next = root
return node.val
end
misses = misses + 1
local res = Evaluator.invoke(user_func, args, kwargs)
if res == nil then res = Runtime.builtins.None end
node = cache_map[key]
if node ~= nil then
node.prev.next = node.next
node.next.prev = node.prev
local last = root.prev
last.next = node
root.prev = node
node.prev = last
node.next = root
node.val = res
elseif currsize >= maxsize then
local oldest = root.next
cache_map[oldest.key] = nil
oldest.key = key
oldest.val = res
root = oldest
cache_map[key] = oldest
else
local last = root.prev
local new_node = { prev = last, next = root, key = key, val = res }
last.next = new_node
root.prev = new_node
cache_map[key] = new_node
currsize = currsize + 1
end
return res
end
end
copy_wrapper_meta(wrapper, user_func)
local py_maxsize = (maxsize == nil) and Runtime.builtins.None or maxsize
wrapper.__dict.cache_info = {
__type = "builtin_method",
__name = "cache_info",
__call = function()
local cls = get_cache_info_class()
return Evaluator.invoke(cls, { hits, misses, py_maxsize, currsize }, {})
end
}
wrapper.__dict.cache_clear = {
__type = "builtin_method",
__name = "cache_clear",
__call = function()
cache_map = {}
hits, misses, currsize = 0, 0, 0
root.prev = root
root.next = root
return Runtime.builtins.None
end
}
wrapper.__dict.cache_parameters = {
__type = "builtin_method",
__name = "cache_parameters",
__call = function()
return {
__type = "dict",
pairs = {
{ key = "maxsize", value = py_maxsize },
{ key = "typed", value = typed }
}
}
end
}
return wrapper
end
local function get_attr(attr)
if cache[attr] ~= nil then return cache[attr] end
if attr == "WRAPPER_ASSIGNMENTS" then
cache[attr] = WRAPPER_ASSIGNMENTS
return WRAPPER_ASSIGNMENTS
elseif attr == "WRAPPER_UPDATES" then
cache[attr] = WRAPPER_UPDATES
return WRAPPER_UPDATES
elseif attr == "reduce" then
cache.reduce = {
__type = "builtin_method",
__name = "reduce",
__call = function(args, kwargs)
if kwargs and next(kwargs) then
Runtime.raise("TypeError", "reduce() takes no keyword arguments")
end
if not args or #args < 2 or #args > 3 then
Runtime.raise("TypeError", "reduce expected 2 or 3 arguments, got " .. (args and #args or 0))
end
local func, iterable = args[1], args[2]
local iter_obj = Runtime.builtins.iter({iterable})
local sentinel = {}
local acc
if #args == 3 then
acc = args[3]
else
acc = Runtime.builtins.next({iter_obj, sentinel})
if acc == sentinel then
Runtime.raise("TypeError", "reduce() of empty iterable with no initial value")
end
end
while true do
local v = Runtime.builtins.next({iter_obj, sentinel})
if v == sentinel then break end
acc = Evaluator.invoke(func, {acc, v}, {})
end
return acc
end
}
return cache.reduce
elseif attr == "lru_cache" then
cache.lru_cache = {
__type = "builtin_method",
__name = "lru_cache",
__call = function(args, kwargs)
-- 支援直接裝飾 @lru_cache (不帶括號)
if args and #args == 1 and (not kwargs or next(kwargs) == nil) and Runtime.builtins.callable({args[1]}) then
return create_lru_wrapper(args[1], 128, false)
end
local raw_max = 128
local has_max_arg = false
if args and #args >= 1 then
raw_max = args[1]; has_max_arg = true
elseif kwargs and kwargs.maxsize ~= nil then
raw_max = kwargs.maxsize; has_max_arg = true
end
local maxsize = 128
if has_max_arg then
if raw_max == nil or (type(raw_max) == "table" and raw_max.__class and raw_max.__class.__name == "NoneType") then
maxsize = nil
elseif type(raw_max) == "number" and math.floor(raw_max) == raw_max and type(raw_max) ~= "boolean" then
maxsize = math.max(0, raw_max)
elseif type(raw_max) == "table" and raw_max.__type == "int" then
maxsize = math.max(0, raw_max.value or 0)
else
Runtime.raise("TypeError", "Expected first argument to be an integer, a callable, or None")
end
end
local raw_typed = (args and args[2] ~= nil and args[2]) or (kwargs and kwargs.typed) or false
local typed = Runtime.builtins.bool.__call({raw_typed})
return {
__type = "builtin_method",
__name = "lru_cache_decorator",
__call = function(d_args)
if not d_args or #d_args < 1 then
Runtime.raise("TypeError", "lru_cache decorator requires a function argument")
end
return create_lru_wrapper(d_args[1], maxsize, typed)
end
}
end
}
return cache.lru_cache
elseif attr == "cache" then
cache.cache = {
__type = "builtin_method",
__name = "cache",
__call = function(args)
if not args or #args < 1 then
Runtime.raise("TypeError", "cache() missing required argument 'user_function'")
end
return create_lru_wrapper(args[1], nil, false)
end
}
return cache.cache
elseif attr == "update_wrapper" then
cache.update_wrapper = {
__type = "builtin_method",
__name = "update_wrapper",
__call = function(args, kwargs)
local wrapper, wrapped = args and args[1], args and args[2]
if not wrapper or not wrapped then
Runtime.raise("TypeError", "update_wrapper() missing required arguments")
end
local assigned = (args and args[3]) or (kwargs and kwargs.assigned) or WRAPPER_ASSIGNMENTS
for _, field in ipairs(assigned.elements or {}) do
local ok, val = pcall(PyClass.get_attr, wrapped, field)
if ok and val ~= nil then
pcall(PyClass.set_attr, wrapper, field, val)
end
end
if type(wrapped) == "table" and wrapped.__dict then
wrapper.__dict = wrapper.__dict or {}
for k, v in pairs(wrapped.__dict) do
wrapper.__dict[k] = v
end
end
pcall(PyClass.set_attr, wrapper, "__wrapped__", wrapped)
return wrapper
end
}
return cache.update_wrapper
elseif attr == "wraps" then
local uw = get_attr("update_wrapper")
cache.wraps = {
__type = "builtin_method",
__name = "wraps",
__call = function(args, kwargs)
local wrapped = args and args[1]
if not wrapped then
Runtime.raise("TypeError", "wraps() missing required argument 'wrapped'")
end
local assigned = (args and args[2]) or (kwargs and kwargs.assigned) or WRAPPER_ASSIGNMENTS
local updated = (args and args[3]) or (kwargs and kwargs.updated) or WRAPPER_UPDATES
return {
__type = "builtin_method",
__name = "wraps_decorator",
__call = function(d_args)
return uw.__call({ d_args[1], wrapped, assigned, updated }, {})
end
}
end
}
return cache.wraps
elseif attr == "partial" then
local partial_dict = {
__init__ = {
__type = "builtin_method",
__call = function(args, kwargs)
if not args or #args < 2 then
Runtime.raise("TypeError", "type 'partial' takes at least one argument")
end
local self, func = args[1], args[2]
if not Runtime.builtins.callable({func}) then
Runtime.raise("TypeError", "the first argument must be callable")
end
local p_args = {}
local p_kwargs = {}
-- 若傳入的 func 本身也是 partial 實例,自動展平合併
if type(func) == "table" and func.__type == "instance" and func.__class == self.__class then
for _, v in ipairs(func.__dict.args.elements) do table.insert(p_args, v) end
for _, p in ipairs(func.__dict.keywords.pairs) do p_kwargs[p.key] = p.value end
func = func.__dict.func
end
for i = 3, #args do table.insert(p_args, args[i]) end
if kwargs then
for k, v in pairs(kwargs) do p_kwargs[k] = v end
end
local kw_pairs = {}
for k, v in pairs(p_kwargs) do table.insert(kw_pairs, { key = k, value = v }) end
self.__dict.func = func
self.__dict.args = { __type = "tuple", elements = p_args }
self.__dict.keywords = { __type = "dict", pairs = kw_pairs }
return nil
end
},
__call__ = {
__type = "builtin_method",
__call = function(args, kwargs)
local self = args[1]
local call_args = {}
for _, v in ipairs(self.__dict.args.elements) do table.insert(call_args, v) end
for i = 2, #args do table.insert(call_args, args[i]) end
local call_kwargs = {}
for _, p in ipairs(self.__dict.keywords.pairs) do call_kwargs[p.key] = p.value end
if kwargs then
for k, v in pairs(kwargs) do call_kwargs[k] = v end
end
return Evaluator.invoke(self.__dict.func, call_args, call_kwargs)
end
},
__repr__ = {
__type = "builtin_method",
__call = function(args)
local self = args[1]
local parts = { Runtime.builtins.repr({self.__dict.func}) }
for _, v in ipairs(self.__dict.args.elements) do
table.insert(parts, Runtime.builtins.repr({v}))
end
for _, p in ipairs(self.__dict.keywords.pairs) do
table.insert(parts, tostring(p.key) .. "=" .. Runtime.builtins.repr({p.value}))
end
return "functools.partial(" .. table.concat(parts, ", ") .. ")"
end
}
}
partial_dict.__str__ = partial_dict.__repr__
cache.partial = PyClass.make_class("partial", {}, partial_dict)
return cache.partial
elseif attr == "cmp_to_key" then
cache.cmp_to_key = {
__type = "builtin_method",
__name = "cmp_to_key",
__call = function(args, kwargs)
local mycmp = (args and args[1]) or (kwargs and kwargs.mycmp)
if not mycmp then
Runtime.raise("TypeError", "cmp_to_key() missing required argument 'mycmp'")
end
local K_dict = {
__init__ = { __type = "builtin_method", __call = function(a) a[1].__dict.obj = a[2]; return nil end },
__lt__ = { __type = "builtin_method", __call = function(a) return Evaluator.invoke(mycmp, {a[1].__dict.obj, a[2].__dict.obj}, {}) < 0 end },
__gt__ = { __type = "builtin_method", __call = function(a) return Evaluator.invoke(mycmp, {a[1].__dict.obj, a[2].__dict.obj}, {}) > 0 end },
__eq__ = { __type = "builtin_method", __call = function(a) return Evaluator.invoke(mycmp, {a[1].__dict.obj, a[2].__dict.obj}, {}) == 0 end },
__le__ = { __type = "builtin_method", __call = function(a) return Evaluator.invoke(mycmp, {a[1].__dict.obj, a[2].__dict.obj}, {}) <= 0 end },
__ge__ = { __type = "builtin_method", __call = function(a) return Evaluator.invoke(mycmp, {a[1].__dict.obj, a[2].__dict.obj}, {}) >= 0 end },
__ne__ = { __type = "builtin_method", __call = function(a) return Evaluator.invoke(mycmp, {a[1].__dict.obj, a[2].__dict.obj}, {}) ~= 0 end }
}
return PyClass.make_class("KeyWrapper", {}, K_dict)
end
}
return cache.cmp_to_key
end
return nil
end
local mod_dict = {
__getattr__ = {
__type = "builtin_method",
__call = function(args)
local self, a = args[1], args[2]
local val = get_attr(a)
if val ~= nil then
self.__dict[a] = val
return val
end
Runtime.raise("AttributeError", "module 'functools' has no attribute '" .. tostring(a) .. "'")
end
}
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "functools"
return mod
end
-- ==========================================
-- 3. collections
-- ==========================================
function Librarys.init_collections_module(module_name,env_vars)
local Evaluator = require("Module:PythonScript/Evaluator")
local B = Runtime.builtins
-- ==========================================
-- 1. collections.abc 模組與結構化型別檢查補丁
-- ==========================================
local abc_dict = {}
local function make_abc(name, required_methods)
local cls = PyClass.make_class(name, { B.object }, {})
cls.__dict["__module__"] = "collections.abc"
cls._abc_name = name
cls._required_methods = required_methods
abc_dict[name] = cls
return cls
end
-- 註冊核心 ABC 介面與其要求的魔術方法
local Iterable = make_abc("Iterable", { "__iter__" })
local Iterator = make_abc("Iterator", { "__iter__", "__next__" })
local Generator = make_abc("Generator", { "send", "throw" })
local Sized = make_abc("Sized", { "__len__" })
local Container = make_abc("Container", { "__contains__" })
local Callable = make_abc("Callable", { "__call__" })
local Collection = make_abc("Collection", { "__iter__", "__len__", "__contains__" })
local Sequence = make_abc("Sequence", { "__getitem__", "__len__" })
local MutableSequence = make_abc("MutableSequence", { "__getitem__", "__setitem__", "__delitem__", "__len__", "insert" })
local Mapping = make_abc("Mapping", { "__getitem__", "keys", "items", "values", "__len__" })
local MutableMapping = make_abc("MutableMapping", { "__getitem__", "__setitem__", "__delitem__", "keys" })
local Set = make_abc("Set", { "__and__", "__or__", "__sub__", "__xor__" })
local MutableSet = make_abc("MutableSet", { "add", "discard" })
-- 【黑魔法補丁】:攔截 isinstance 與 issubclass,支援鴨子型別與原生型別速查
if not B._abc_patched then
B._abc_patched = true
local orig_isinstance = B.isinstance
local orig_issubclass = B.issubclass
local function check_structural(obj, cls, is_sub)
if type(cls) == "table" and cls._abc_name then
local t_name
if is_sub then
t_name = (type(obj) == "table" and obj.__type == "class" and obj.__name) or ""
else
if type(obj) == "table" then
t_name = obj.__type
if t_name == "instance" and obj.__class then t_name = obj.__class.__name end
elseif type(obj) == "string" then t_name = "str"
elseif type(obj) == "number" then t_name = (math.floor(obj) == obj) and "int" or "float"
elseif type(obj) == "boolean" then t_name = "bool"
end
end
-- 1. 原生高速比對路由 (完全吻合 CPython 對 builtins 的分類)
local n = cls._abc_name
if n == "Iterable" or n == "Container" or n == "Collection" then
if t_name == "list" or t_name == "tuple" or t_name == "dict" or t_name == "set" or t_name == "frozenset" or t_name == "str" or t_name == "bytes" or t_name == "bytearray" or t_name == "memoryview" or t_name == "range" then return true end
elseif n == "Sequence" then
if t_name == "list" or t_name == "tuple" or t_name == "str" or t_name == "bytes" or t_name == "bytearray" or t_name == "memoryview" or t_name == "range" then return true end
elseif n == "MutableSequence" then
if t_name == "list" or t_name == "bytearray" then return true end
elseif n == "Mapping" or n == "MutableMapping" then
if t_name == "dict" then return true end
elseif n == "Set" then
if t_name == "set" or t_name == "frozenset" then return true end
elseif n == "MutableSet" then
if t_name == "set" then return true end
elseif n == "Callable" then
if type(obj) == "function" then return true end
if type(obj) == "table" and (obj.__type == "function" or obj.__type == "bound_method" or obj.__type == "builtin_method" or obj.__type == "class") then return true end
if t_name == "instance" then
local ok, attr = pcall(PyClass.get_attr, obj, "__call__")
if ok and attr then return true end
end
end
-- 2. 使用者自定義類別的鴨子型別 (Duck Typing) 檢查
if cls._required_methods then
for _, method in ipairs(cls._required_methods) do
local ok, attr
if is_sub then
ok, attr = pcall(PyClass.lookup_class_attr, obj, method)
else
ok, attr = pcall(PyClass.get_attr, obj, method)
end
if not ok or not attr then return false end
end
return true
end
end
return nil
end
local function custom_isinstance(obj, cls)
if cls == nil then Runtime.raise("TypeError", "isinstance() arg 2 must be a type or tuple of types") end
if type(cls) == "table" and cls.__type == "tuple" then
for _, c in ipairs(cls.elements) do
if custom_isinstance(obj, c) then return true end
end
return false
end
local res = check_structural(obj, cls, false)
if res ~= nil then return res end
return orig_isinstance({ obj, cls })
end
local function custom_issubclass(child, parent)
if type(parent) == "table" and parent.__type == "tuple" then
for _, p in ipairs(parent.elements) do
if custom_issubclass(child, p) then return true end
end
return false
end
local res = check_structural(child, parent, true)
if res ~= nil then return res end
return orig_issubclass({ child, parent })
end
B.isinstance = function(args) return custom_isinstance(args[1], args[2]) end
B.issubclass = function(args) return custom_issubclass(args[1], args[2]) end
end
local abc_mod = PyClass.make_instance(PyClass.make_class("module", {}, {}))
abc_mod.__dict["__name__"] = "collections.abc"
for k, v in pairs(abc_dict) do abc_mod.__dict[k] = v end
local Sys = require("Module:PythonScript/Librarys/Argparse").Sys
Sys.modules_registry["collections.abc"] = abc_mod
local function _collections_eq(a, b)
if type(a) == "table" and type(b) == "table" then
if a.__type == "list" and b.__type == "list" then
if #a.elements ~= #b.elements then return false end
for i = 1, #a.elements do if not _collections_eq(a.elements[i], b.elements[i]) then return false end end
return true
end
end; return a == b
end
local cache = {}
-- 核心建構器:被存取時才動態消耗記憶體建立物件
local function get_attr(attr)
if cache[attr] then return cache[attr] end
if attr == "UserDict" then
local UserDict_dict = {
__init__ = { __type = "builtin_method", __call = function(args, kwargs)
local self = args[1]
self.__dict.data = Runtime.builtins.dict.__call({args[2]}, kwargs)
return nil
end},
__getitem__ = { __type = "builtin_method", __call = function(args)
local self, key = args[1], args[2]
for _, p in ipairs(self.__dict.data.pairs) do if _collections_eq(p.key, key) then return p.value end end
local success, missing = pcall(PyClass.get_attr, self, "__missing__")
if success and missing and missing.__type == "bound_method" then
return Evaluator.invoke(missing.__func, {key}, {}, missing.__self)
end
Runtime.raise("KeyError", tostring(key))
end},
__setitem__ = { __type = "builtin_method", __call = function(args)
local self, key, val = args[1], args[2], args[3]; local found = false
for _, p in ipairs(self.__dict.data.pairs) do if _collections_eq(p.key, key) then p.value = val; found = true; break end end
if not found then table.insert(self.__dict.data.pairs, {key=key, value=val}) end; return nil
end},
__delitem__ = { __type = "builtin_method", __call = function(args)
local self, key = args[1], args[2]
for i, p in ipairs(self.__dict.data.pairs) do if _collections_eq(p.key, key) then table.remove(self.__dict.data.pairs, i); return nil end end
Runtime.raise("KeyError", tostring(key))
end},
__iter__ = { __type = "builtin_method", __call = function(args) return Runtime.builtins.iter({args[1].__dict.data}) end},
__len__ = { __type = "builtin_method", __call = function(args) return #args[1].__dict.data.pairs end},
__contains__ = { __type = "builtin_method", __call = function(args)
for _, p in ipairs(args[1].__dict.data.pairs) do if _collections_eq(p.key, args[2]) then return true end end; return false
end},
__repr__ = { __type = "builtin_method", __call = function(args) return args[1].__class.__name .. "(" .. Runtime.builtins.repr({args[1].__dict.data}) .. ")" end},
keys = { __type = "builtin_method", __call = function(args) return PyClass.get_attr(args[1].__dict.data, "keys").__call({}) end},
values = { __type = "builtin_method", __call = function(args) return PyClass.get_attr(args[1].__dict.data, "values").__call({}) end},
items = { __type = "builtin_method", __call = function(args) return PyClass.get_attr(args[1].__dict.data, "items").__call({}) end},
get = { __type = "builtin_method", __call = function(args) return PyClass.get_attr(args[1].__dict.data, "get").__call({args[2], args[3]}) end},
clear = { __type = "builtin_method", __call = function(args) return PyClass.get_attr(args[1].__dict.data, "clear").__call({}) end},
update = { __type = "builtin_method", __call = function(args, kwargs) return PyClass.get_attr(args[1].__dict.data, "update").__call({args[2]}, kwargs) end},
pop = { __type = "builtin_method", __call = function(args) return PyClass.get_attr(args[1].__dict.data, "pop").__call({args[2], args[3]}) end},
copy = { __type = "builtin_method", __call = function(args)
local inst = PyClass.make_instance(args[1].__class)
inst.__dict.data = PyClass.get_attr(args[1].__dict.data, "copy").__call({}); return inst
end}
}
UserDict_dict.__str__ = UserDict_dict.__repr__
cache.UserDict = PyClass.make_class("UserDict", {}, UserDict_dict)
return cache.UserDict
end
if attr == "Counter" then
local UserDict = get_attr("UserDict")
local Counter_dict = {
__init__ = { __type = "builtin_method", __call = function(args, kwargs)
local self = args[1]
self.__dict.data = {__type="dict", pairs={}}
-- 使用 Evaluator.invoke 呼叫 update
if args[2] then Evaluator.invoke(PyClass.get_attr(self, "update"), {args[2]}, kwargs)
elseif kwargs and next(kwargs) then Evaluator.invoke(PyClass.get_attr(self, "update"), {}, kwargs) end
return nil
end},
__missing__ = { __type = "builtin_method", __call = function(args) return 0 end},
elements = { __type = "builtin_method", __call = function(args)
local elts = {}
for _, p in ipairs(args[1].__dict.data.pairs) do
if type(p.value) == "number" and p.value > 0 then
for i = 1, math.floor(p.value) do table.insert(elts, p.key) end
end
end; return Runtime.builtins.iter({{__type="list", elements=elts}})
end},
most_common = { __type = "builtin_method", __call = function(args)
local n = args[2]; local items = {}
for _, p in ipairs(args[1].__dict.data.pairs) do table.insert(items, {p.key, p.value}) end
table.sort(items, function(a, b) return a[2] > b[2] end)
local res = {}; local limit = n or #items
for i = 1, limit do if i > #items then break end table.insert(res, {__type="tuple", elements={items[i][1], items[i][2]}}) end
return {__type="list", elements=res}
end},
total = { __type = "builtin_method", __call = function(args)
local sum = 0; for _, p in ipairs(args[1].__dict.data.pairs) do sum = sum + p.value end return sum
end},
update = { __type = "builtin_method", __call = function(args, kwargs)
local self, iterable = args[1], args[2]
local function add_count(k, v)
local cur = 0
for _, p in ipairs(self.__dict.data.pairs) do if _collections_eq(p.key, k) then cur = p.value; break end end
-- 使用 Evaluator.invoke 呼叫 __setitem__
Evaluator.invoke(PyClass.get_attr(self, "__setitem__"), {k, cur + v}, {})
end
if iterable then
if type(iterable) == "table" and iterable.__type == "dict" then
for _, p in ipairs(iterable.pairs) do add_count(p.key, p.value) end
elseif type(iterable) == "table" and iterable.__type == "instance" and PyClass.is_instance(iterable, UserDict) then
for _, p in ipairs(iterable.__dict.data.pairs) do add_count(p.key, p.value) end
else
local iter_obj = Runtime.builtins.iter({iterable}); local next_fn = PyClass.get_attr(iter_obj, "__next__")
while true do
local success, v = pcall(Evaluator.invoke, next_fn.__func, {}, {}, next_fn.__self)
if not success then break end; add_count(v, 1)
end
end
end
if kwargs then for k, v in pairs(kwargs) do add_count(k, v) end end; return nil
end},
subtract = { __type = "builtin_method", __call = function(args, kwargs)
local self, iterable = args[1], args[2]
local function sub_count(k, v)
local cur = 0
for _, p in ipairs(self.__dict.data.pairs) do if _collections_eq(p.key, k) then cur = p.value; break end end
-- 使用 Evaluator.invoke 呼叫 __setitem__
Evaluator.invoke(PyClass.get_attr(self, "__setitem__"), {k, cur - v}, {})
end
if iterable then
if type(iterable) == "table" and iterable.__type == "dict" then
for _, p in ipairs(iterable.pairs) do sub_count(p.key, p.value) end
else
local iter_obj = Runtime.builtins.iter({iterable}); local next_fn = PyClass.get_attr(iter_obj, "__next__")
while true do
local success, v = pcall(Evaluator.invoke, next_fn.__func, {}, {}, next_fn.__self)
if not success then break end; sub_count(v, 1)
end
end
end
if kwargs then for k, v in pairs(kwargs) do sub_count(k, v) end end; return nil
end}
}
cache.Counter = PyClass.make_class("Counter", {UserDict}, Counter_dict)
return cache.Counter
end
if attr == "defaultdict" then
local UserDict = get_attr("UserDict")
local defaultdict_dict = {
__init__ = { __type = "builtin_method", __call = function(args, kwargs)
local self = args[1]; self.__dict.default_factory = args[2]
self.__dict.data = {__type="dict", pairs={}}
if args[3] then PyClass.get_attr(self.__dict.data, "update").__call({args[3]}, kwargs) end
return nil
end},
__missing__ = { __type = "builtin_method", __call = function(args)
local self, key = args[1], args[2]
if not self.__dict.default_factory or self.__dict.default_factory == Runtime.builtins.None then Runtime.raise("KeyError", tostring(key)) end
local val = Evaluator.invoke(self.__dict.default_factory, {}, {})
-- 使用 Evaluator.invoke 呼叫 __setitem__
Evaluator.invoke(PyClass.get_attr(self, "__setitem__"), {key, val}, {})
return val
end}
}
cache.defaultdict = PyClass.make_class("defaultdict", {UserDict}, defaultdict_dict)
return cache.defaultdict
end
if attr == "OrderedDict" then
local UserDict = get_attr("UserDict")
local OrderedDict_dict = {
move_to_end = { __type = "builtin_method", __call = function(args)
local self, key, last = args[1], args[2], args[3]
if last == nil then last = true else last = Runtime.builtins.bool.__call({last}) end
local pairs_list = self.__dict.data.pairs
for i, p in ipairs(pairs_list) do
if _collections_eq(p.key, key) then
local item = table.remove(pairs_list, i)
if last then table.insert(pairs_list, item) else table.insert(pairs_list, 1, item) end
return nil
end
end
Runtime.raise("KeyError", tostring(key))
end},
popitem = { __type = "builtin_method", __call = function(args)
local self, last = args[1], args[2]
if last == nil then last = true else last = Runtime.builtins.bool.__call({last}) end
local pairs_list = self.__dict.data.pairs
if #pairs_list == 0 then Runtime.raise("KeyError", "dictionary is empty") end
local idx = last and #pairs_list or 1
local item = table.remove(pairs_list, idx)
return {__type="tuple", elements={item.key, item.value}}
end}
}
cache.OrderedDict = PyClass.make_class("OrderedDict", {UserDict}, OrderedDict_dict)
return cache.OrderedDict
end
if attr == "deque" then
local deque_dict = {
__init__ = { __type = "builtin_method", __call = function(args, kwargs)
local self, iterable = args[1], args[2]
self.__dict.maxlen = (kwargs and kwargs.maxlen) or Runtime.builtins.None
self.__dict.data = {__type="list", elements={}}
-- 使用 Evaluator.invoke 呼叫 extend
if iterable then Evaluator.invoke(PyClass.get_attr(self, "extend"), {iterable}, {}) end
return nil
end},
append = { __type = "builtin_method", __call = function(args)
local self, x = args[1], args[2]; table.insert(self.__dict.data.elements, x)
if self.__dict.maxlen ~= Runtime.builtins.None and #self.__dict.data.elements > self.__dict.maxlen then table.remove(self.__dict.data.elements, 1) end
return nil
end},
appendleft = { __type = "builtin_method", __call = function(args)
local self, x = args[1], args[2]; table.insert(self.__dict.data.elements, 1, x)
if self.__dict.maxlen ~= Runtime.builtins.None and #self.__dict.data.elements > self.__dict.maxlen then table.remove(self.__dict.data.elements) end
return nil
end},
pop = { __type = "builtin_method", __call = function(args)
if #args[1].__dict.data.elements == 0 then Runtime.raise("IndexError", "pop from an empty deque") end
return table.remove(args[1].__dict.data.elements)
end},
popleft = { __type = "builtin_method", __call = function(args)
if #args[1].__dict.data.elements == 0 then Runtime.raise("IndexError", "pop from an empty deque") end
return table.remove(args[1].__dict.data.elements, 1)
end},
clear = { __type = "builtin_method", __call = function(args) args[1].__dict.data.elements = {}; return nil end},
extend = { __type = "builtin_method", __call = function(args)
local iter_obj = Runtime.builtins.iter({args[2]}); local next_fn = PyClass.get_attr(iter_obj, "__next__")
while true do
local success, v = pcall(Evaluator.invoke, next_fn.__func, {}, {}, next_fn.__self)
-- 使用 Evaluator.invoke 呼叫 append
if not success then break end; Evaluator.invoke(PyClass.get_attr(args[1], "append"), {v}, {})
end; return nil
end},
extendleft = { __type = "builtin_method", __call = function(args)
local iter_obj = Runtime.builtins.iter({args[2]}); local next_fn = PyClass.get_attr(iter_obj, "__next__")
while true do
local success, v = pcall(Evaluator.invoke, next_fn.__func, {}, {}, next_fn.__self)
-- 使用 Evaluator.invoke 呼叫 appendleft
if not success then break end; Evaluator.invoke(PyClass.get_attr(args[1], "appendleft"), {v}, {})
end; return nil
end},
__len__ = { __type = "builtin_method", __call = function(args) return #args[1].__dict.data.elements end},
__iter__ = { __type = "builtin_method", __call = function(args) return Runtime.builtins.iter({args[1].__dict.data}) end},
__repr__ = { __type = "builtin_method", __call = function(args)
local inner = Runtime.builtins.repr({args[1].__dict.data}); inner = string.sub(inner, 2, -2)
local ml = args[1].__dict.maxlen ~= Runtime.builtins.None and (", maxlen=" .. tostring(args[1].__dict.maxlen)) or ""
return "deque([" .. inner .. "]" .. ml .. ")"
end}
}
cache.deque = PyClass.make_class("deque", {}, deque_dict)
return cache.deque
end
if attr == "ChainMap" then
local UserDict = get_attr("UserDict")
local ChainMap_dict = {
__init__ = { __type = "builtin_method", __call = function(args)
local self = args[1]; self.__dict.maps = {__type="list", elements={}}
if #args > 1 then for i=2, #args do table.insert(self.__dict.maps.elements, args[i]) end
else table.insert(self.__dict.maps.elements, {__type="dict", pairs={}}) end
return nil
end},
__getitem__ = { __type = "builtin_method", __call = function(args)
local key = args[2]
for _, m in ipairs(args[1].__dict.maps.elements) do
local success, res = pcall(function()
if m.__type == "dict" then
for _, p in ipairs(m.pairs) do if _collections_eq(p.key, key) then return p.value end end; error()
else
return Evaluator.invoke(PyClass.get_attr(m, "__getitem__").__func, {key}, {}, m)
end
end)
if success then return res end
end
Runtime.raise("KeyError", tostring(key))
end},
__setitem__ = { __type = "builtin_method", __call = function(args)
local self, key, val = args[1], args[2], args[3]
local first_map = self.__dict.maps.elements[1]
if first_map.__type == "dict" then
local found = false
for _, p in ipairs(first_map.pairs) do if _collections_eq(p.key, key) then p.value = val; found = true; break end end
if not found then table.insert(first_map.pairs, {key=key, value=val}) end
else
local setitem = PyClass.get_attr(first_map, "__setitem__")
Evaluator.invoke(setitem.__func, {key, val}, {}, setitem.__self)
end; return nil
end}
}
cache.ChainMap = PyClass.make_class("ChainMap", {UserDict}, ChainMap_dict)
return cache.ChainMap
end
if attr == "UserList" then
local UserList_dict = {
__init__ = { __type = "builtin_method", __call = function(args)
local self = args[1]; self.__dict.data = Runtime.builtins.list.__call({args[2]}); return nil
end},
__len__ = { __type = "builtin_method", __call = function(args) return #args[1].__dict.data.elements end},
__iter__ = { __type = "builtin_method", __call = function(args) return Runtime.builtins.iter({args[1].__dict.data}) end},
__repr__ = { __type = "builtin_method", __call = function(args) return args[1].__class.__name .. "(" .. Runtime.builtins.repr({args[1].__dict.data}) .. ")" end}
}
cache.UserList = PyClass.make_class("UserList", {}, UserList_dict)
return cache.UserList
end
if attr == "UserString" then
local UserString_dict = {
__init__ = { __type = "builtin_method", __call = function(args)
local self = args[1]; self.__dict.data = Runtime.builtins.str.__call({args[2]}); return nil
end},
__len__ = { __type = "builtin_method", __call = function(args) return mw.ustring.len(args[1].__dict.data) end},
__iter__ = { __type = "builtin_method", __call = function(args) return Runtime.builtins.iter({args[1].__dict.data}) end},
__repr__ = { __type = "builtin_method", __call = function(args) return Runtime.builtins.repr({args[1].__dict.data}) end},
__str__ = { __type = "builtin_method", __call = function(args) return args[1].__dict.data end},
-- 【新增】__getattr__ 動態攔截並轉發字串原生方法
__getattr__ = { __type = "builtin_method", __call = function(args)
local self, attr_name = args[1], args[2]
-- 將屬性請求轉發給內部的真實字串物件 (self.data)
local success, method_or_val = pcall(PyClass.get_attr, self.__dict.data, attr_name)
if not success then
-- 找不到屬性時,拋出符合當前代理類別名稱的 AttributeError
require("Module:PythonScript/Runtime").raise("AttributeError", "'" .. self.__class.__name .. "' object has no attribute '" .. attr_name .. "'")
end
-- 若取得的是字串的原生內建方法 (如 upper, replace, split 等)
if type(method_or_val) == "table" and method_or_val.__type == "builtin_method" then
return {
__type = "builtin_method",
__call = function(m_args, m_kwargs)
-- 呼叫內建字串方法
local res = method_or_val.__call(m_args, m_kwargs)
-- 依照 CPython 規範:若方法回傳的結果是字串,必須將其重新包裝回 UserString (或繼承的子類別) 實例
if type(res) == "string" then
return Evaluator.invoke(self.__class, {res}, {})
end
-- 若回傳非字串 (例如 split 回傳 list,或是 count 回傳 int),則直接返回原始結果
return res
end
}
end
return method_or_val
end}
}
cache.UserString = PyClass.make_class("UserString", {}, UserString_dict)
return cache.UserString
end
if attr == "namedtuple" then
cache.namedtuple = { __type = "builtin_method", __call = function(args)
local typename, field_names = args[1], args[2]
local fields = {}
if type(field_names) == "string" then
local s = string.gsub(field_names, ",", " ")
for w in string.gmatch(s, "%S+") do table.insert(fields, w) end
elseif type(field_names) == "table" and (field_names.__type == "list" or field_names.__type == "tuple") then
for _, f in ipairs(field_names.elements) do table.insert(fields, f) end
end
local nt_dict = {
__match_args__ = { __type = "tuple", elements = fields },
__init__ = { __type = "builtin_method", __call = function(iargs, ikwargs)
local self = iargs[1]; local elements = {}
for i = 2, #iargs do table.insert(elements, iargs[i]) end
if #elements < #fields and ikwargs then
for i = #elements+1, #fields do table.insert(elements, ikwargs[fields[i]]) end
end
if #elements ~= #fields then Runtime.raise("TypeError", "namedtuple expected " .. #fields .. " arguments, got " .. #elements) end
self.__dict.data = {__type="tuple", elements=elements}; return nil
end},
__iter__ = { __type = "builtin_method", __call = function(iargs) return Runtime.builtins.iter({iargs[1].__dict.data}) end},
__len__ = { __type = "builtin_method", __call = function(iargs) return #fields end},
__repr__ = { __type = "builtin_method", __call = function(iargs)
local parts = {}
for i, f in ipairs(fields) do table.insert(parts, f .. "=" .. Runtime.builtins.repr({iargs[1].__dict.data.elements[i]})) end
return typename .. "(" .. table.concat(parts, ", ") .. ")"
end}
}
for i, f in ipairs(fields) do
nt_dict[f] = { __type = "property", fget = { __type = "builtin_method", __call = function(iargs) return iargs[1].__dict.data.elements[i] end } }
end
return PyClass.make_class(typename, {}, nt_dict)
end }
return cache.namedtuple
end
return nil
end
-- 建立帶有自訂 __getattr__ 的模組物件
local mod_dict = {
__getattr__ = { __type = "builtin_method", __call = function(args)
local self, a = args[1], args[2]
local val = get_attr(a)
if val then
self.__dict[a] = val
return val
end
Runtime.raise("AttributeError", "module 'collections' has no attribute '" .. tostring(a) .. "'")
end }
}
local mod = PyClass.make_instance(PyClass.make_class("module", {}, mod_dict))
mod.__dict["__name__"] = "collections"
mod.__dict.abc = abc_mod
if module_name == "collections.abc" then
if type(env_vars) == type({}) then
env_vars["collections"] = env_vars["collections"] or mod
end
return abc_mod
end
return mod
end
-- 對外接口
function Librarys.has_module(name)
local root = name:match("^([^%.]+)")
if name == "collections" or name == "functools" or name == "itertools" then return true end
return stdlibs[root] ~= nil
end
function Librarys.get_module(name)
local root, sub = name:match("^([^%.]+)%.?(.*)$")
if name == "collections" then return Librarys.init_collections_module() end
if name == "functools" then return init_functools_module() end
if name == "itertools" then return init_itertools_module() end
if not stdlibs[root] then Runtime.raise("ImportError", "No module named '" .. root .. "'") end
if sub and sub ~= "" then
if stdlibs[root][sub] then return make_namespace(name, stdlibs[root][sub]) end
Runtime.raise("ImportError", "No module named '" .. name .. "'")
end
return make_namespace(name, stdlibs[root])
end
return Librarys