模組:PythonScript/Class
外观
-- Module:PythonScript/Class
-- 物件導向核心模型 (C3 MRO、類別/實例生命週期、屬性查找與型別檢查)
local PyClassModel = {}
local Utils = require("Module:PythonScript/Class/Utils")
Utils.bind_class_model(PyClassModel)
local safe_pcall = Utils.safe_pcall
local did_you_mean = Utils.did_you_mean
local IMPLICIT_DUNDERS = Utils.IMPLICIT_DUNDERS
-- 儲存根類別 object 的參照 (於 Runtime 初始化 make_class("object") 時自動捕獲)
PyClassModel._object_class = nil
-- ==========================================
-- 1. C3 超類別線性化演算法 (C3 Linearization MRO)
-- ==========================================
local function compute_mro(cls)
local obj_cls = PyClassModel._object_class
local bases = {}
for _, b in ipairs(cls.__bases or {}) do
if type(b) == "table" and b.__type == "class" then
table.insert(bases, b)
else
require("Module:PythonScript/Runtime").raise("TypeError", "base class must be a class type")
end
end
if #bases == 0 and obj_cls and cls ~= obj_cls and cls.__name ~= "object" then
table.insert(bases, obj_cls)
end
local to_merge = {}
for _, base in ipairs(bases) do
local bmro = base.__mro or compute_mro(base)
local copy = {}
for _, c in ipairs(bmro) do table.insert(copy, c) end
table.insert(to_merge, copy)
end
if #bases > 0 then
local base_list = {}
for _, b in ipairs(bases) do table.insert(base_list, b) end
table.insert(to_merge, base_list)
end
local mro = { cls }
while true do
local non_empty = {}
for _, seq in ipairs(to_merge) do
if #seq > 0 then table.insert(non_empty, seq) end
end
if #non_empty == 0 then break end
local candidate = nil
for _, seq in ipairs(non_empty) do
local head = seq[1]
local in_tail = false
for _, other_seq in ipairs(non_empty) do
for i = 2, #other_seq do
if other_seq[i] == head then in_tail = true; break end
end
if in_tail then break end
end
if not in_tail then candidate = head; break end
end
if not candidate then
require("Module:PythonScript/Runtime").raise("TypeError", "Cannot create a consistent method resolution order (MRO)")
end
table.insert(mro, candidate)
for _, seq in ipairs(non_empty) do
if seq[1] == candidate then table.remove(seq, 1) end
end
end
if obj_cls and cls ~= obj_cls and cls.__name ~= "object" then
local has_obj = false
for _, c in ipairs(mro) do if c == obj_cls then has_obj = true; break end end
if not has_obj then table.insert(mro, obj_cls) end
end
return mro
end
function PyClassModel.make_class(name, bases, dict)
if dict then
if type(dict["__new__"]) == "table" and (dict["__new__"].__type == "function" or dict["__new__"].__type == "builtin_method") then
dict["__new__"] = { __type = "staticmethod", __func = dict["__new__"] }
end
if type(dict["__init_subclass__"]) == "table" and (dict["__init_subclass__"].__type == "function" or dict["__init_subclass__"].__type == "builtin_method") then
dict["__init_subclass__"] = { __type = "classmethod", __func = dict["__init_subclass__"] }
end
if type(dict["__class_getitem__"]) == "table" and (dict["__class_getitem__"].__type == "function" or dict["__class_getitem__"].__type == "builtin_method") then
dict["__class_getitem__"] = { __type = "classmethod", __func = dict["__class_getitem__"] }
end
end
local cls = { __type = "class", __name = name, __bases = bases or {}, __dict = dict or {} }
if name == "object" and (not bases or #bases == 0) and not PyClassModel._object_class then
PyClassModel._object_class = cls
end
cls.__mro = compute_mro(cls)
for _, v in pairs(cls.__dict) do
if type(v) == "table" then
if v.__type == "function" then v.__defining_class = cls
elseif (v.__type == "classmethod" or v.__type == "staticmethod") and type(v.__func) == "table" then v.__func.__defining_class = cls
elseif v.__type == "property" then
if type(v.fget) == "table" then v.fget.__defining_class = cls end
if type(v.fset) == "table" then v.fset.__defining_class = cls end
if type(v.fdel) == "table" then v.fdel.__defining_class = cls end
end
end
end
return cls
end
-- 計算衍生元類別並檢查元類別衝突 (Metaclass Conflict Resolution)
function PyClassModel.resolve_metaclass(explicit_meta, bases)
local Runtime = require("Module:PythonScript/Runtime")
local B = Runtime.builtins
local meta = explicit_meta or B.type
if (not explicit_meta) and bases and #bases > 0 then
local b1 = bases[1]
if type(b1) == "table" and b1.__type == "class" and b1.__class then
meta = b1.__class
end
end
if type(meta) == "table" and meta.__type ~= "class" then
return meta
end
for _, base in ipairs(bases or {}) do
if type(base) == "table" and base.__type == "class" then
local base_meta = base.__class or B.type
if PyClassModel.is_subclass(meta, base_meta) then
-- meta 已是 base_meta 的子類別
elseif PyClassModel.is_subclass(base_meta, meta) then
meta = base_meta
else
Runtime.raise("TypeError", "metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases")
end
end
end
return meta
end
-- 呼叫元類別的 __prepare__(metacls, name, bases, **kwds)
function PyClassModel.prepare_namespace(meta, name, bases, kwargs)
local Runtime = require("Module:PythonScript/Runtime")
local B = Runtime.builtins
local bases_tup = { __type = "tuple", elements = bases or {} }
if type(meta) == "table" and meta.__type == "class" then
for _, m_base in ipairs(meta.__mro or {meta}) do
if m_base == B.type or m_base == B.object then break end
local prep = m_base.__dict and m_base.__dict["__prepare__"]
if prep ~= nil then
local Evaluator = require("Module:PythonScript/Evaluator")
local ns
if prep.__type == "classmethod" or prep.__type == "builtin_method" then
local fn_p = prep.__func or prep
ns = Evaluator.invoke(fn_p, { meta, name, bases_tup }, kwargs or {})
elseif prep.__type == "staticmethod" then
ns = Evaluator.invoke(prep.__func, { name, bases_tup }, kwargs or {})
elseif prep.__type == "function" then
ns = Evaluator.invoke(prep, { meta, name, bases_tup }, kwargs or {})
else
ns = Evaluator.invoke(prep, { name, bases_tup }, kwargs or {})
end
if type(ns) ~= "table" or (ns.__type ~= "dict" and ns.__type ~= "instance") then
Runtime.raise("TypeError", tostring(meta.__name) .. ".__prepare__() must return a mapping, not " .. tostring(type(ns) == "table" and (ns.__class and ns.__class.__name or ns.__type) or type(ns)))
end
return ns
end
end
end
return { __type = "dict", pairs = {} }
end
-- 觸發描述器 __set_name__ 與父類別 __init_subclass__ 鉤子
function PyClassModel.init_class_hooks(cls, metaclass, kwargs)
local Runtime = require("Module:PythonScript/Runtime")
local B = Runtime.builtins
local Evaluator = require("Module:PythonScript/Evaluator")
cls.__class = metaclass or cls.__class or B.type
-- 1. 觸發描述器的 __set_name__(self, owner, name)
if not cls._is_enum_class then
local keys = {}
for k, v in pairs(cls.__dict or {}) do
if type(k) == "string" and type(v) == "table" and v.__type == "instance" and not v._is_enum_member then
table.insert(keys, k)
end
end
table.sort(keys)
for _, k in ipairs(keys) do
local v = cls.__dict[k]
local set_name_fn = PyClassModel.lookup_class_attr(v, "__set_name__")
if set_name_fn then
if set_name_fn.__type == "builtin_method" then
set_name_fn.__call({ v, cls, k }, {})
else
Evaluator.invoke(set_name_fn, { v, cls, k }, {})
end
end
end
end
-- 2. 觸發父類別的 __init_subclass__(cls, **kwargs)
local mro = cls.__mro or {}
for i = 2, #mro do
local base_cls = mro[i]
local init_sub = base_cls.__dict and base_cls.__dict["__init_subclass__"]
if init_sub ~= nil then
local fn_sub = (type(init_sub) == "table" and (init_sub.__type == "classmethod" or init_sub.__type == "staticmethod")) and init_sub.__func or init_sub
if fn_sub.__type == "builtin_method" then
fn_sub.__call({ cls }, kwargs or {})
else
Evaluator.invoke(fn_sub, { cls }, kwargs or {})
end
break
end
end
return cls
end
function PyClassModel.make_instance(cls)
return { __type = "instance", __class = cls, __dict = {} }
end
local function make_bound_method(instance, func)
return {
__type = "bound_method",
__self = instance,
__func = func,
-- 💡 【向下相容黑魔法】:讓那 70 個舊的 .__call 寫法繼續存活,並自動補回 self 參數!
__call = function(args, kwargs)
local call_args = { instance }
for _, v in ipairs(args or {}) do table.insert(call_args, v) end
if func.__type == "builtin_method" then
return func.__call(call_args, kwargs)
else
local Evaluator = require("Module:PythonScript/Evaluator")
return Evaluator.invoke(func, call_args, kwargs)
end
end
}
end
-- ==========================================
-- 2. 核心屬性查找引擎 (get_attr)
-- ==========================================
local INTERCEPT_BYPASS = {
__getattribute__ = true, __getattr__ = true, __setattr__ = true,
__delattr__ = true, __new__ = true, __init__ = true
}
local function raise_attribute_error(obj, attr_name)
local keys = {}
if type(obj) == "table" then
if obj.__type == "instance" then
for k, _ in pairs(obj.__dict or {}) do table.insert(keys, k) end
for _, base in ipairs(obj.__class.__mro or {}) do for k, _ in pairs(base.__dict or {}) do table.insert(keys, k) end end
elseif obj.__type == "class" then
for _, base in ipairs(obj.__mro or {}) do for k, _ in pairs(base.__dict or {}) do table.insert(keys, k) end end
end
end
local suggestion = did_you_mean(attr_name, keys)
local suggest_str = suggestion and (". Did you mean: '" .. suggestion .. "'?") or ""
local obj_name
if type(obj) == "table" then
obj_name = obj.__name or (obj.__class and obj.__class.__name) or obj.__type or "object"
elseif type(obj) == "number" then
obj_name = (math.floor(obj) == obj) and "int" or "float"
elseif type(obj) == "boolean" then obj_name = "bool"
elseif type(obj) == "string" then obj_name = "str"
else obj_name = type(obj) end
require("Module:PythonScript/Runtime").raise("AttributeError", "'" .. obj_name .. "' object has no attribute '" .. tostring(attr_name) .. "'" .. suggest_str)
end
-- 標準 object.__getattribute__ 與 type.__getattribute__ 查找邏輯
function PyClassModel.raw_get_attr(obj, attr_name)
if type(obj) == "table" and obj.__type == "instance" then
if attr_name == "__class__" then return obj.__class end
if attr_name == "__dict__" then
local kv_pairs = {}
for k, v in pairs(obj.__dict or {}) do table.insert(kv_pairs, {key = k, value = v}) end
return { __type = "dict", pairs = kv_pairs, __scope_ref = obj.__dict }
end
-- 1. 資料描述器 (Data Descriptor: property 或同時具備 __get__ 與 __set__/__delete__ 的實例)
for _, base_cls in ipairs(obj.__class.__mro or {}) do
local cval = base_cls.__dict and base_cls.__dict[attr_name]
if type(cval) == "table" then
if cval.__type == "property" then
if not cval.fget then
require("Module:PythonScript/Runtime").raise("AttributeError", "unreadable attribute '" .. tostring(attr_name) .. "'")
end
return require("Module:PythonScript/Evaluator").invoke(cval.fget, {obj}, {})
elseif cval.__type == "instance" then
local get_fn = PyClassModel.lookup_class_attr(cval, "__get__")
local is_data_desc = (PyClassModel.lookup_class_attr(cval, "__set__") ~= nil)
or (PyClassModel.lookup_class_attr(cval, "__delete__") ~= nil)
if is_data_desc and get_fn then
return require("Module:PythonScript/Evaluator").invoke(get_fn, {cval, obj, obj.__class}, {})
end
end
end
end
-- 2. 實例自身屬性字典
if obj.__dict and obj.__dict[attr_name] ~= nil then return obj.__dict[attr_name] end
-- 3. 類別階層方法與非資料描述器 (Non-Data Descriptor)
for _, base_cls in ipairs(obj.__class.__mro or {}) do
local val = base_cls.__dict and base_cls.__dict[attr_name]
if val ~= nil then
if type(val) == "table" then
if val.__type == "function" or val.__type == "builtin_method" then return make_bound_method(obj, val)
elseif val.__type == "classmethod" then return make_bound_method(obj.__class, val.__func)
elseif val.__type == "staticmethod" then return val.__func
elseif val.__type == "instance" then
local get_fn = PyClassModel.lookup_class_attr(val, "__get__")
if get_fn then
return require("Module:PythonScript/Evaluator").invoke(get_fn, {val, obj, obj.__class}, {})
end
end
end
return val
end
end
if obj.__builtin_value ~= nil then
local bv = obj.__builtin_value
local res = Utils.get_str_or_bytes_attr(bv, attr_name)
or Utils.get_number_attr(bv, attr_name)
or Utils.get_container_attr(bv, attr_name)
if res ~= nil then return res end
end
elseif type(obj) == "table" and obj.__type == "class" then
local Runtime = require("Module:PythonScript/Runtime")
local B = Runtime.builtins
if attr_name == "__name__" or attr_name == "__qualname__" then return obj.__name end
if attr_name == "__mro__" then return { __type = "tuple", elements = obj.__mro } end
if attr_name == "__bases__" then return { __type = "tuple", elements = obj.__bases } end
if attr_name == "__class__" then return obj.__class or (B and B.type) end
if attr_name == "__dict__" then
local kv_pairs = {}
for k, v in pairs(obj.__dict or {}) do table.insert(kv_pairs, {key = k, value = v}) end
return { __type = "dict", pairs = kv_pairs, __scope_ref = obj.__dict }
end
-- 1. 檢查元類別 (Metaclass) 上的資料描述器 (如定義在 Meta 上的 @property)
local meta = obj.__class
if type(meta) == "table" and meta.__type == "class" and B and meta ~= B.type then
for _, m_base in ipairs(meta.__mro or {}) do
if m_base == B.type or m_base == B.object then break end
local mval = m_base.__dict and m_base.__dict[attr_name]
if type(mval) == "table" then
if mval.__type == "property" then
if not mval.fget then Runtime.raise("AttributeError", "unreadable attribute '" .. tostring(attr_name) .. "'") end
return require("Module:PythonScript/Evaluator").invoke(mval.fget, {obj}, {})
elseif mval.__type == "instance" then
local get_fn = PyClassModel.lookup_class_attr(mval, "__get__")
local is_data = (PyClassModel.lookup_class_attr(mval, "__set__") ~= nil) or (PyClassModel.lookup_class_attr(mval, "__delete__") ~= nil)
if is_data and get_fn then
return require("Module:PythonScript/Evaluator").invoke(get_fn, {mval, obj, meta}, {})
end
end
end
end
end
-- 2. 查找類別自身的 MRO (obj.__mro)
for _, base_cls in ipairs(obj.__mro or {}) do
local val = base_cls.__dict and base_cls.__dict[attr_name]
if val ~= nil then
if type(val) == "table" then
if val.__type == "classmethod" then return make_bound_method(obj, val.__func)
elseif val.__type == "staticmethod" then return val.__func
elseif val.__type == "instance" and not val._is_enum_member then
local get_fn = PyClassModel.lookup_class_attr(val, "__get__")
if get_fn then
return require("Module:PythonScript/Evaluator").invoke(get_fn, {val, B.None, obj}, {})
end
end
end
return val
end
end
-- 3. 查找元類別 (Metaclass) 上的方法與一般屬性 (綁定至類別 obj)
if type(meta) == "table" and meta.__type == "class" and B and meta ~= B.type then
for _, m_base in ipairs(meta.__mro or {}) do
if m_base == B.type or m_base == B.object then break end
local mval = m_base.__dict and m_base.__dict[attr_name]
if mval ~= nil then
if type(mval) == "table" then
if mval.__type == "function" or mval.__type == "builtin_method" then return make_bound_method(obj, mval)
elseif mval.__type == "classmethod" then return make_bound_method(meta, mval.__func)
elseif mval.__type == "staticmethod" then return mval.__func
elseif mval.__type == "instance" then
local get_fn = PyClassModel.lookup_class_attr(mval, "__get__")
if get_fn then
return require("Module:PythonScript/Evaluator").invoke(get_fn, {mval, obj, meta}, {})
end
end
end
return mval
end
end
end
-- 4. 若在類別上請求 __getitem__ (如 Cls[int]) 且未在元類別定義,自動轉發至 __class_getitem__
if attr_name == "__getitem__" then
for _, base_cls in ipairs(obj.__mro or {}) do
local cg = base_cls.__dict and base_cls.__dict["__class_getitem__"]
if cg ~= nil then
if type(cg) == "table" and cg.__type == "classmethod" then
return make_bound_method(obj, cg.__func)
elseif type(cg) == "table" and (cg.__type == "function" or cg.__type == "builtin_method") then
return make_bound_method(obj, cg)
end
return cg
end
end
end
-- 5. 支援內建類別直接存取未綁定方法 (如 str.upper, dict.fromkeys, int.from_bytes, list.append)
if B then
local proto = nil
if obj == B.str then proto = ""
elseif obj == B.bytes then proto = { __type = "bytes", _data = "" }
elseif obj == B.bytearray then proto = { __type = "bytearray", _data = "" }
elseif obj == B.list then proto = { __type = "list", elements = {} }
elseif obj == B.tuple then proto = { __type = "tuple", elements = {} }
elseif obj == B.dict then proto = { __type = "dict", pairs = {} }
elseif obj == B.set then proto = { __type = "set", elements = {} }
elseif obj == B.frozenset then proto = { __type = "frozenset", elements = {} }
elseif obj == B.int then proto = 0
elseif obj == B.float then proto = 0.0
elseif obj == B.complex then proto = { __type = "complex", real = 0, imag = 0 } end
if proto ~= nil then
local ok_p, proto_m = safe_pcall(PyClassModel.get_attr, proto, attr_name)
if ok_p and type(proto_m) == "table" and (proto_m.__type == "builtin_method" or proto_m.__type == "bound_method") then
if attr_name == "fromkeys" or attr_name == "from_bytes" or attr_name == "fromhex" or attr_name == "maketrans" then
return proto_m.__func or proto_m
end
return {
__type = "builtin_method",
__name = attr_name,
__call = function(args, kwargs)
if not args or #args == 0 then
Runtime.raise("TypeError", "descriptor '" .. attr_name .. "' of '" .. obj.__name .. "' object needs an argument")
end
local self_inst = args[1]
local rest_args = {}
for i = 2, #args do table.insert(rest_args, args[i]) end
local bound_m = PyClassModel.get_attr(self_inst, attr_name)
if bound_m.__type == "bound_method" then
return bound_m.__func.__call({self_inst, unpack(rest_args)}, kwargs)
end
return bound_m.__call(rest_args, kwargs)
end
}
end
end
end
end
raise_attribute_error(obj, attr_name)
end
function PyClassModel.get_attr(obj, attr_name)
-- [A] 內建字串與二進位型別
if type(obj) == "string" or (type(obj) == "table" and (obj.__type == "bytes" or obj.__type == "bytearray" or obj.__type == "memoryview")) then
-- 💡 [終極攔截點]:劫持 encode 與 decode,強力覆蓋隱藏在 Class/Utils 裡的舊方法!
if type(obj) == "string" and attr_name == "encode" then
return {
__type = "builtin_method", __name = "encode",
__call = function(args, kwargs)
local enc = (args and args[1]) or (kwargs and kwargs.encoding) or "utf-8"
local err = (args and args[2]) or (kwargs and kwargs.errors) or "strict"
local RUtils = require("Module:PythonScript/Runtime/Utils")
return require("Module:PythonScript/Librarys/Codecs").Codecs.lua_encode(obj, RUtils.to_str(enc, false), RUtils.to_str(err, false))
end
}
end
if type(obj) == "table" and (obj.__type == "bytes" or obj.__type == "bytearray") and attr_name == "decode" then
return {
__type = "builtin_method", __name = "decode",
__call = function(args, kwargs)
local enc = (args and args[1]) or (kwargs and kwargs.encoding) or "utf-8"
local err = (args and args[2]) or (kwargs and kwargs.errors) or "strict"
local RUtils = require("Module:PythonScript/Runtime/Utils")
return require("Module:PythonScript/Librarys/Codecs").Codecs.lua_decode(obj, RUtils.to_str(enc, false), RUtils.to_str(err, false))
end
}
end
local res = Utils.get_str_or_bytes_attr(obj, attr_name)
if res ~= nil then return res end
end
-- [B] 內建數值型別 (int, float, bool, complex)
if type(obj) == "number" or type(obj) == "boolean" or (type(obj) == "table" and (obj.__type == "int" or obj.__type == "complex")) then
local res = Utils.get_number_attr(obj, attr_name)
if res ~= nil then return res end
end
-- [C] 內建容器型別 (dict, set, frozenset, list, tuple)
if type(obj) == "table" and (obj.__type == "dict" or obj.__type == "set" or obj.__type == "frozenset" or obj.__type == "list" or obj.__type == "tuple") then
local res = Utils.get_container_attr(obj, attr_name)
if res ~= nil then return res end
end
-- [D] 實例物件 (含 super_proxy、__getattribute__ 優先攔截與 __getattr__ Fallback)
if type(obj) == "table" and obj.__type == "instance" then
if obj.__class and obj.__class.__name == "super_proxy" then
if attr_name == "__class__" then return obj.__class end
local ga = obj.__class.__dict and obj.__class.__dict["__getattr__"]
if ga then return ga.__call({obj, attr_name}) end
end
local bypass = IMPLICIT_DUNDERS[attr_name] or INTERCEPT_BYPASS[attr_name]
local custom_ga = nil
if not bypass then
for _, base_cls in ipairs(obj.__class.__mro or {}) do
local ga = base_cls.__dict and base_cls.__dict["__getattribute__"]
if ga ~= nil then
if base_cls ~= PyClassModel._object_class and base_cls.__name ~= "object" then
custom_ga = ga
end
break
end
end
end
local ok, res
if custom_ga then
ok, res = safe_pcall(function()
if custom_ga.__type == "builtin_method" then
return custom_ga.__call({obj, attr_name}, {})
elseif custom_ga.__type == "function" then
return require("Module:PythonScript/Evaluator").invoke(custom_ga, {obj, attr_name}, {})
end
end)
else
ok, res = safe_pcall(PyClassModel.raw_get_attr, obj, attr_name)
end
if ok then return res end
-- 若非 AttributeError 則直接向外拋出
local Runtime = require("Module:PythonScript/Runtime")
if type(res) ~= "table" or not res.__class or not PyClassModel.is_subclass(res.__class, Runtime.builtins.AttributeError) then
error(res, 0)
end
-- [E] AttributeError 時嘗試觸發 __getattr__ Fallback
if not bypass then
for _, base_cls in ipairs(obj.__class.__mro or {}) do
local getattr_func = base_cls.__dict and base_cls.__dict["__getattr__"]
if getattr_func then
if getattr_func.__type == "builtin_method" then
return getattr_func.__call({obj, attr_name})
elseif getattr_func.__type == "function" then
return require("Module:PythonScript/Evaluator").invoke(getattr_func, {obj, attr_name}, {})
end
end
end
end
error(res, 0)
elseif type(obj) == "table" and obj.__type == "class" then
local Runtime = require("Module:PythonScript/Runtime")
local B = Runtime.builtins
local meta = obj.__class
local bypass = IMPLICIT_DUNDERS[attr_name] or INTERCEPT_BYPASS[attr_name]
local custom_ga = nil
if not bypass and type(meta) == "table" and meta.__type == "class" and B and meta ~= B.type then
for _, m_base in ipairs(meta.__mro or {}) do
local ga = m_base.__dict and m_base.__dict["__getattribute__"]
if ga ~= nil then
if m_base ~= B.type and m_base ~= PyClassModel._object_class and m_base.__name ~= "object" then
custom_ga = ga
end
break
end
end
end
local ok, res
if custom_ga then
ok, res = safe_pcall(function()
if custom_ga.__type == "builtin_method" then
return custom_ga.__call({obj, attr_name}, {})
else
return require("Module:PythonScript/Evaluator").invoke(custom_ga, {obj, attr_name}, {})
end
end)
else
-- 呼叫上方我們剛擴充好的 raw_get_attr(內含 __name__, __mro__, MRO 與 str.upper 未綁定方法)
ok, res = safe_pcall(PyClassModel.raw_get_attr, obj, attr_name)
end
if ok then return res end
if type(res) ~= "table" or not res.__class or not PyClassModel.is_subclass(res.__class, B.AttributeError) then
error(res, 0)
end
if not bypass and type(meta) == "table" and meta.__type == "class" and B and meta ~= B.type then
for _, m_base in ipairs(meta.__mro or {}) do
if m_base == B.type or m_base == B.object then break end
local getattr_func = m_base.__dict and m_base.__dict["__getattr__"]
if getattr_func then
if getattr_func.__type == "builtin_method" then
return getattr_func.__call({obj, attr_name}, {})
else
return require("Module:PythonScript/Evaluator").invoke(getattr_func, {obj, attr_name}, {})
end
end
end
end
error(res, 0)
elseif type(obj) == "table" and (obj.__type == "function" or obj.__type == "builtin_method") then
if obj.__dict and obj.__dict[attr_name] ~= nil then return obj.__dict[attr_name] end
if attr_name == "__name__" then return obj.__name or obj.__qualname__ or obj.__name__ or "<unknown>" end
if attr_name == "__qualname__" then return obj.__qualname__ or obj.__name or obj.__name__ or "<unknown>" end
if attr_name == "__module__" then return obj.__module__ or "__main__" end
if attr_name == "__doc__" then return obj.__doc__ or require("Module:PythonScript/Runtime").builtins.None end
if attr_name == "__dict__" then
obj.__dict = obj.__dict or {}
local kv_pairs = {}
for k, v in pairs(obj.__dict) do table.insert(kv_pairs, {key = k, value = v}) end
return { __type = "dict", pairs = kv_pairs, __scope_ref = obj.__dict }
end
elseif type(obj) == "table" and obj.__type == "bound_method" then
if attr_name == "__name__" then return obj.__func and (obj.__func.__name or obj.__func.__qualname__ or obj.__func.__name__) or "<unknown>" end
if attr_name == "__self__" then return obj.__self end
if attr_name == "__func__" then return obj.__func end
if obj.__func and obj.__func.__dict and obj.__func.__dict[attr_name] ~= nil then return obj.__func.__dict[attr_name] end
end
raise_attribute_error(obj, attr_name)
end
-- ==========================================
-- 3. 屬性寫入/刪除與型別階層檢查
-- ==========================================
function PyClassModel.lookup_class_attr(obj, attr_name)
if type(obj) ~= "table" or obj.__type ~= "instance" then return nil end
for _, base_cls in ipairs(obj.__class.__mro or {}) do
if base_cls.__dict and base_cls.__dict[attr_name] ~= nil then return base_cls.__dict[attr_name] end
end
return nil
end
function PyClassModel.raw_set_attr(obj, attr_name, value)
local is_func = type(obj) == "table" and (obj.__type == "function" or obj.__type == "builtin_method")
if type(obj) ~= "table" or (obj.__type ~= "instance" and obj.__type ~= "class" and not is_func) then
local tname = type(obj) == "table" and (obj.__type or "object") or type(obj)
require("Module:PythonScript/Runtime").raise("TypeError", "cannot set attribute on '" .. tostring(tname) .. "'")
end
if is_func then
obj.__dict = obj.__dict or {}
if attr_name == "__name__" then obj.__name = value; obj.__name__ = value
elseif attr_name == "__qualname__" then obj.__qualname__ = value
elseif attr_name == "__module__" then obj.__module__ = value
elseif attr_name == "__doc__" then obj.__doc__ = value end
obj.__dict[attr_name] = value
return
end
local owner_cls = (obj.__type == "instance" or obj.__type == "class") and obj.__class or nil
local c_attr = nil
if type(owner_cls) == "table" and owner_cls.__mro then
for _, base_cls in ipairs(owner_cls.__mro) do
if base_cls.__dict and base_cls.__dict[attr_name] ~= nil then
c_attr = base_cls.__dict[attr_name]
break
end
end
end
if value == nil then
if type(c_attr) == "table" then
if c_attr.__type == "property" then
if not c_attr.fdel then require("Module:PythonScript/Runtime").raise("AttributeError", "can't delete attribute") end
require("Module:PythonScript/Evaluator").invoke(c_attr.fdel, {obj}, {})
return
elseif c_attr.__type == "instance" then
local del_fn = PyClassModel.lookup_class_attr(c_attr, "__delete__")
if del_fn then
require("Module:PythonScript/Evaluator").invoke(del_fn, {c_attr, obj}, {})
return
end
end
end
if obj.__dict[attr_name] == nil then
local cname = obj.__name or (obj.__class and obj.__class.__name) or "object"
require("Module:PythonScript/Runtime").raise("AttributeError", "'" .. cname .. "' object has no attribute '" .. tostring(attr_name) .. "'")
end
obj.__dict[attr_name] = nil
return
end
if type(c_attr) == "table" then
if c_attr.__type == "property" then
if not c_attr.fset then require("Module:PythonScript/Runtime").raise("AttributeError", "can't set attribute") end
require("Module:PythonScript/Evaluator").invoke(c_attr.fset, {obj, value}, {})
return
elseif c_attr.__type == "instance" then
local set_fn = PyClassModel.lookup_class_attr(c_attr, "__set__")
if set_fn then
require("Module:PythonScript/Evaluator").invoke(set_fn, {c_attr, obj, value}, {})
return
end
end
end
obj.__dict[attr_name] = value
end
function PyClassModel.set_attr(obj, attr_name, value)
if type(obj) == "table" and (obj.__type == "instance" or obj.__type == "class") and obj.__class then
local B = require("Module:PythonScript/Runtime").builtins
local dunder = (value == nil) and "__delattr__" or "__setattr__"
local custom_fn = nil
for _, base_cls in ipairs(obj.__class.__mro or {}) do
local fn = base_cls.__dict and base_cls.__dict[dunder]
if fn ~= nil then
if base_cls ~= B.type and base_cls ~= PyClassModel._object_class and base_cls.__name ~= "object" then
custom_fn = fn
end
break
end
end
if custom_fn then
local call_args = (value == nil) and {obj, attr_name} or {obj, attr_name, value}
if custom_fn.__type == "builtin_method" then
custom_fn.__call(call_args, {})
else
require("Module:PythonScript/Evaluator").invoke(custom_fn, call_args, {})
end
return
end
end
PyClassModel.raw_set_attr(obj, attr_name, value)
end
function PyClassModel.is_instance(obj, cls)
if type(cls) == "table" and cls.__type == "tuple" then
for _, c in ipairs(cls.elements) do
if PyClassModel.is_instance(obj, c) then return true end
end
return false
end
if type(cls) == "table" and cls.__type == "instance" and cls.__dict then
if cls.__dict._is_any then return true end
if cls.__dict._is_union and cls.__dict.__args__ then
return PyClassModel.is_instance(obj, cls.__dict.__args__)
end
if cls.__dict._origin then
return PyClassModel.is_instance(obj, cls.__dict._origin)
end
end
local B = require("Module:PythonScript/Runtime").builtins
if cls == B.object then return true end
if type(obj) == "boolean" and cls == B.int then return true end
local obj_class = B.type.__call({obj})
if obj_class == cls then return true end
if type(obj_class) == "table" and obj_class.__mro then
for _, base in ipairs(obj_class.__mro) do if base == cls then return true end end
end
if type(obj) == "table" and obj.__type == "instance" and obj.__class and obj.__class.__mro then
for _, base in ipairs(obj.__class.__mro) do if base == cls then return true end end
end
return false
end
function PyClassModel.is_subclass(cls, parent_cls)
if type(parent_cls) == "table" and parent_cls.__type == "tuple" then
for _, p in ipairs(parent_cls.elements) do
if PyClassModel.is_subclass(cls, p) then return true end
end
return false
end
if type(parent_cls) == "table" and parent_cls.__type == "instance" and parent_cls.__dict then
if parent_cls.__dict._is_any then return true end
if parent_cls.__dict._is_union and parent_cls.__dict.__args__ then
return PyClassModel.is_subclass(cls, parent_cls.__dict.__args__)
end
if parent_cls.__dict._origin then
return PyClassModel.is_subclass(cls, parent_cls.__dict._origin)
end
end
if type(cls) ~= "table" or cls.__type ~= "class" then return false end
local B = require("Module:PythonScript/Runtime").builtins
if parent_cls == B.object then return true end
if cls == B.bool and parent_cls == B.int then return true end
if cls == parent_cls then return true end
for _, base in ipairs(cls.__mro or {}) do if base == parent_cls then return true end end
return false
end
return PyClassModel