mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-24 05:20:17 +00:00
add @dataclass
This commit is contained in:
parent
d1080aab1f
commit
475bce9999
14
docs/modules/dataclasses.md
Normal file
14
docs/modules/dataclasses.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
icon: package
|
||||
label: dataclasses
|
||||
---
|
||||
|
||||
### `dataclasses.dataclass`
|
||||
|
||||
A decorator that is used to add generated special method to classes, including `__init__`, `__repr__` and `__eq__`.
|
||||
|
||||
### `dataclasses.asdict(obj) -> dict`
|
||||
|
||||
Convert a dataclass instance to a dictionary.
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ namespace pkpy {
|
||||
throw std::runtime_error(msg.str()); \
|
||||
} \
|
||||
PyObject* type = vm->new_type_object(mod, #name, base); \
|
||||
mod->attr().set(#name, type); \
|
||||
T::_register(vm, mod, type); \
|
||||
return type; \
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
#include <bitset>
|
||||
#include <deque>
|
||||
|
||||
#define PK_VERSION "1.3.3"
|
||||
#define PK_VERSION "1.3.5"
|
||||
|
||||
#include "config.h"
|
||||
#include "export.h"
|
||||
@ -166,7 +166,7 @@ static_assert(sizeof(Number::int_t) == sizeof(void*));
|
||||
static_assert(sizeof(BitsCvt) == sizeof(void*));
|
||||
static_assert(std::numeric_limits<f64>::is_iec559);
|
||||
|
||||
struct Dummy { };
|
||||
struct Dummy { }; // for special objects: True, False, None, Ellipsis, etc.
|
||||
struct DummyInstance { };
|
||||
struct DummyModule { };
|
||||
struct NoReturn { };
|
||||
|
||||
@ -118,7 +118,8 @@ class Compiler {
|
||||
bool try_compile_assignment();
|
||||
void compile_stmt();
|
||||
void consume_type_hints();
|
||||
void compile_class();
|
||||
void _add_decorators(const std::vector<Expr_>& decorators);
|
||||
void compile_class(const std::vector<Expr_>& decorators={});
|
||||
void _compile_f_args(FuncDecl_ decl, bool enable_type_hints);
|
||||
void compile_function(const std::vector<Expr_>& decorators={});
|
||||
|
||||
|
||||
@ -118,6 +118,9 @@ OPCODE(UNPACK_EX)
|
||||
OPCODE(BEGIN_CLASS)
|
||||
OPCODE(END_CLASS)
|
||||
OPCODE(STORE_CLASS_ATTR)
|
||||
OPCODE(BEGIN_CLASS_DECORATION)
|
||||
OPCODE(END_CLASS_DECORATION)
|
||||
OPCODE(ADD_CLASS_ANNOTATION)
|
||||
/**************************/
|
||||
OPCODE(WITH_ENTER)
|
||||
OPCODE(WITH_EXIT)
|
||||
|
||||
@ -55,6 +55,8 @@ struct PyTypeInfo{
|
||||
Str name;
|
||||
bool subclass_enabled;
|
||||
|
||||
std::vector<StrName> annotated_fields;
|
||||
|
||||
// cached special methods
|
||||
// unary operators
|
||||
PyObject* (*m__repr__)(VM* vm, PyObject*) = nullptr;
|
||||
|
||||
@ -5,6 +5,17 @@ def print(*args, sep=' ', end='\n'):
|
||||
s = sep.join([str(i) for i in args])
|
||||
_sys.stdout.write(s + end)
|
||||
|
||||
def issubclass(cls, base):
|
||||
if type(cls) is not type:
|
||||
raise TypeError('issubclass() arg 1 must be a class')
|
||||
if type(base) is not type:
|
||||
raise TypeError('issubclass() arg 2 must be a class')
|
||||
while cls is not None:
|
||||
if cls is base:
|
||||
return True
|
||||
cls = cls.__base__
|
||||
return False
|
||||
|
||||
def _minmax_reduce(op, args, key):
|
||||
if key is None:
|
||||
if len(args) == 2:
|
||||
|
||||
59
python/dataclasses.py
Normal file
59
python/dataclasses.py
Normal file
@ -0,0 +1,59 @@
|
||||
def _wrapped__init__(self, *args, **kwargs):
|
||||
cls = type(self)
|
||||
cls_d = cls.__dict__
|
||||
fields: tuple[str] = cls.__annotations__
|
||||
i = 0 # index into args
|
||||
for field in fields:
|
||||
if field in kwargs:
|
||||
setattr(self, field, kwargs.pop(field))
|
||||
else:
|
||||
if i < len(args):
|
||||
setattr(self, field, args[i])
|
||||
++i
|
||||
elif field in cls_d: # has default value
|
||||
setattr(self, field, cls_d[field])
|
||||
else:
|
||||
raise TypeError(f"{cls.__name__} missing required argument {field!r}")
|
||||
if len(args) > i:
|
||||
raise TypeError(f"{cls.__name__} takes {len(field)} positional arguments but {len(args)} were given")
|
||||
if len(kwargs) > 0:
|
||||
raise TypeError(f"{cls.__name__} got an unexpected keyword argument {next(iter(kwargs))!r}")
|
||||
|
||||
def _wrapped__repr__(self):
|
||||
fields: tuple[str] = type(self).__annotations__
|
||||
obj_d = self.__dict__
|
||||
args: list = [f"{field}={obj_d[field]!r}" for field in fields]
|
||||
return f"{type(self).__name__}({', '.join(args)})"
|
||||
|
||||
def _wrapped__eq__(self, other):
|
||||
if type(self) is not type(other):
|
||||
return False
|
||||
fields: tuple[str] = type(self).__annotations__
|
||||
for field in fields:
|
||||
if getattr(self, field) != getattr(other, field):
|
||||
return False
|
||||
return True
|
||||
|
||||
def dataclass(cls: type):
|
||||
assert type(cls) is type
|
||||
cls_d = cls.__dict__
|
||||
if '__init__' not in cls_d:
|
||||
cls.__init__ = _wrapped__init__
|
||||
if '__repr__' not in cls_d:
|
||||
cls.__repr__ = _wrapped__repr__
|
||||
if '__eq__' not in cls_d:
|
||||
cls.__eq__ = _wrapped__eq__
|
||||
fields: tuple[str] = cls.__annotations__
|
||||
has_default = False
|
||||
for field in fields:
|
||||
if field in cls_d:
|
||||
has_default = True
|
||||
else:
|
||||
if has_default:
|
||||
raise TypeError(f"non-default argument {field!r} follows default argument")
|
||||
return cls
|
||||
|
||||
def asdict(obj) -> dict:
|
||||
fields: tuple[str] = type(obj).__annotations__
|
||||
obj_d = obj.__dict__
|
||||
return {field: obj_d[field] for field in fields}
|
||||
@ -749,6 +749,8 @@ __NEXT_STEP:;
|
||||
} DISPATCH();
|
||||
TARGET(END_CLASS) {
|
||||
PK_ASSERT(_curr_class != nullptr);
|
||||
StrName _name(byte.arg);
|
||||
frame->_module->attr().set(_name, _curr_class);
|
||||
_curr_class = nullptr;
|
||||
} DISPATCH();
|
||||
TARGET(STORE_CLASS_ATTR){
|
||||
@ -760,6 +762,18 @@ __NEXT_STEP:;
|
||||
}
|
||||
_curr_class->attr().set(_name, _0);
|
||||
} DISPATCH();
|
||||
TARGET(BEGIN_CLASS_DECORATION){
|
||||
PUSH(_curr_class);
|
||||
} DISPATCH();
|
||||
TARGET(END_CLASS_DECORATION){
|
||||
_curr_class = POPX();
|
||||
} DISPATCH();
|
||||
TARGET(ADD_CLASS_ANNOTATION) {
|
||||
PK_ASSERT(_curr_class != nullptr);
|
||||
StrName _name(byte.arg);
|
||||
Type type = PK_OBJ_GET(Type, _curr_class);
|
||||
_type_info(type)->annotated_fields.push_back(_name);
|
||||
} DISPATCH();
|
||||
/*****************************************/
|
||||
TARGET(WITH_ENTER)
|
||||
call_method(POPX(), __enter__);
|
||||
@ -818,8 +832,8 @@ __NEXT_STEP:;
|
||||
} DISPATCH();
|
||||
|
||||
#if !PK_ENABLE_COMPUTED_GOTO
|
||||
static_assert(OP_DEC_GLOBAL == 107);
|
||||
case 108: case 109: case 110: case 111: case 112: case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: case 123: case 124: case 125: case 126: case 127: case 128: case 129: case 130: case 131: case 132: case 133: case 134: case 135: case 136: case 137: case 138: case 139: case 140: case 141: case 142: case 143: case 144: case 145: case 146: case 147: case 148: case 149: case 150: case 151: case 152: case 153: case 154: case 155: case 156: case 157: case 158: case 159: case 160: case 161: case 162: case 163: case 164: case 165: case 166: case 167: case 168: case 169: case 170: case 171: case 172: case 173: case 174: case 175: case 176: case 177: case 178: case 179: case 180: case 181: case 182: case 183: case 184: case 185: case 186: case 187: case 188: case 189: case 190: case 191: case 192: case 193: case 194: case 195: case 196: case 197: case 198: case 199: case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 207: case 208: case 209: case 210: case 211: case 212: case 213: case 214: case 215: case 216: case 217: case 218: case 219: case 220: case 221: case 222: case 223: case 224: case 225: case 226: case 227: case 228: case 229: case 230: case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239: case 240: case 241: case 242: case 243: case 244: case 245: case 246: case 247: case 248: case 249: case 250: case 251: case 252: case 253: case 254: case 255: FATAL_ERROR(); break;
|
||||
static_assert(OP_DEC_GLOBAL == 110);
|
||||
case 111: case 112: case 113: case 114: case 115: case 116: case 117: case 118: case 119: case 120: case 121: case 122: case 123: case 124: case 125: case 126: case 127: case 128: case 129: case 130: case 131: case 132: case 133: case 134: case 135: case 136: case 137: case 138: case 139: case 140: case 141: case 142: case 143: case 144: case 145: case 146: case 147: case 148: case 149: case 150: case 151: case 152: case 153: case 154: case 155: case 156: case 157: case 158: case 159: case 160: case 161: case 162: case 163: case 164: case 165: case 166: case 167: case 168: case 169: case 170: case 171: case 172: case 173: case 174: case 175: case 176: case 177: case 178: case 179: case 180: case 181: case 182: case 183: case 184: case 185: case 186: case 187: case 188: case 189: case 190: case 191: case 192: case 193: case 194: case 195: case 196: case 197: case 198: case 199: case 200: case 201: case 202: case 203: case 204: case 205: case 206: case 207: case 208: case 209: case 210: case 211: case 212: case 213: case 214: case 215: case 216: case 217: case 218: case 219: case 220: case 221: case 222: case 223: case 224: case 225: case 226: case 227: case 228: case 229: case 230: case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238: case 239: case 240: case 241: case 242: case 243: case 244: case 245: case 246: case 247: case 248: case 249: case 250: case 251: case 252: case 253: case 254: case 255: FATAL_ERROR(); break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -164,11 +164,12 @@ void add_module_c(VM* vm){
|
||||
Type type_t;
|
||||
|
||||
#define BIND_PRIMITIVE(T, CNAME) \
|
||||
vm->bind_func<1>(mod, CNAME "_", [](VM* vm, ArgsView args){ \
|
||||
vm->bind_func<1>(mod, CNAME "_", [](VM* vm, ArgsView args){ \
|
||||
T val = CAST(T, args[0]); \
|
||||
return VAR_T(C99Struct, &val, sizeof(T)); \
|
||||
}); \
|
||||
type = vm->new_type_object(mod, CNAME "_p", VoidP::_type(vm)); \
|
||||
type = vm->new_type_object(mod, CNAME "_p", VoidP::_type(vm)); \
|
||||
mod->attr().set(CNAME "_p", type); \
|
||||
type_t = PK_OBJ_GET(Type, type); \
|
||||
vm->bind_method<0>(type, "read", [](VM* vm, ArgsView args){ \
|
||||
VoidP& voidp = PK_OBJ_GET(VoidP, args[0]); \
|
||||
|
||||
@ -712,8 +712,13 @@ __EAT_DOTS_END:
|
||||
decorators.push_back(ctx()->s_expr.popx());
|
||||
if(!match_newlines_repl()) SyntaxError();
|
||||
}while(match(TK("@")));
|
||||
consume(TK("def"));
|
||||
compile_function(decorators);
|
||||
|
||||
if(match(TK("class"))){
|
||||
compile_class(decorators);
|
||||
}else{
|
||||
consume(TK("def"));
|
||||
compile_function(decorators);
|
||||
}
|
||||
}
|
||||
|
||||
bool Compiler::try_compile_assignment(){
|
||||
@ -927,6 +932,12 @@ __EAT_DOTS_END:
|
||||
if(match(TK(":"))){
|
||||
consume_type_hints();
|
||||
is_typed_name = true;
|
||||
|
||||
if(ctx()->is_compiling_class){
|
||||
// add to __annotations__
|
||||
NameExpr* ne = static_cast<NameExpr*>(ctx()->s_expr.top().get());
|
||||
ctx()->emit_(OP_ADD_CLASS_ANNOTATION, ne->name.index, BC_KEEPLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!try_compile_assignment()){
|
||||
@ -955,7 +966,18 @@ __EAT_DOTS_END:
|
||||
ctx()->s_expr.pop();
|
||||
}
|
||||
|
||||
void Compiler::compile_class(){
|
||||
void Compiler::_add_decorators(const std::vector<Expr_>& decorators){
|
||||
// [obj]
|
||||
for(auto it=decorators.rbegin(); it!=decorators.rend(); ++it){
|
||||
(*it)->emit_(ctx()); // [obj, f]
|
||||
ctx()->emit_(OP_ROT_TWO, BC_NOARG, (*it)->line); // [f, obj]
|
||||
ctx()->emit_(OP_LOAD_NULL, BC_NOARG, BC_KEEPLINE); // [f, obj, NULL]
|
||||
ctx()->emit_(OP_ROT_TWO, BC_NOARG, BC_KEEPLINE); // [obj, NULL, f]
|
||||
ctx()->emit_(OP_CALL, 1, (*it)->line); // [obj]
|
||||
}
|
||||
}
|
||||
|
||||
void Compiler::compile_class(const std::vector<Expr_>& decorators){
|
||||
consume(TK("@id"));
|
||||
int namei = StrName(prev().sv()).index;
|
||||
Expr_ base = nullptr;
|
||||
@ -981,7 +1003,14 @@ __EAT_DOTS_END:
|
||||
ctx()->is_compiling_class = true;
|
||||
compile_block_body();
|
||||
ctx()->is_compiling_class = false;
|
||||
ctx()->emit_(OP_END_CLASS, BC_NOARG, BC_KEEPLINE);
|
||||
|
||||
if(!decorators.empty()){
|
||||
ctx()->emit_(OP_BEGIN_CLASS_DECORATION, BC_NOARG, BC_KEEPLINE);
|
||||
_add_decorators(decorators);
|
||||
ctx()->emit_(OP_END_CLASS_DECORATION, BC_NOARG, BC_KEEPLINE);
|
||||
}
|
||||
|
||||
ctx()->emit_(OP_END_CLASS, namei, BC_KEEPLINE);
|
||||
}
|
||||
|
||||
void Compiler::_compile_f_args(FuncDecl_ decl, bool enable_type_hints){
|
||||
@ -1077,14 +1106,8 @@ __EAT_DOTS_END:
|
||||
}
|
||||
ctx()->emit_(OP_LOAD_FUNCTION, ctx()->add_func_decl(decl), prev().line);
|
||||
|
||||
// add decorators
|
||||
for(auto it=decorators.rbegin(); it!=decorators.rend(); ++it){
|
||||
(*it)->emit_(ctx());
|
||||
ctx()->emit_(OP_ROT_TWO, BC_NOARG, (*it)->line);
|
||||
ctx()->emit_(OP_LOAD_NULL, BC_NOARG, BC_KEEPLINE);
|
||||
ctx()->emit_(OP_ROT_TWO, BC_NOARG, BC_KEEPLINE);
|
||||
ctx()->emit_(OP_CALL, 1, (*it)->line);
|
||||
}
|
||||
_add_decorators(decorators);
|
||||
|
||||
if(!ctx()->is_compiling_class){
|
||||
auto e = make_expr<NameExpr>(decl_name, name_scope());
|
||||
e->emit_store(ctx());
|
||||
|
||||
@ -1146,29 +1146,6 @@ void init_builtins(VM* _vm) {
|
||||
return value;
|
||||
});
|
||||
|
||||
// _vm->bind_method<0>("dict", "_data", [](VM* vm, ArgsView args) {
|
||||
// Dict& self = _CAST(Dict&, args[0]);
|
||||
// SStream ss;
|
||||
// ss << "[\n";
|
||||
// for(int i=0; i<self._capacity; i++){
|
||||
// auto item = self._items[i];
|
||||
// Str key("None");
|
||||
// Str value("None");
|
||||
// if(item.first != nullptr){
|
||||
// key = CAST(Str&, vm->py_repr(item.first));
|
||||
// }
|
||||
// if(item.second != nullptr){
|
||||
// value = CAST(Str&, vm->py_repr(item.second));
|
||||
// }
|
||||
// int prev = self._nodes[i].prev;
|
||||
// int next = self._nodes[i].next;
|
||||
// ss << " [" << key << ", " << value << ", " << prev << ", " << next << "],\n";
|
||||
// }
|
||||
// ss << "]\n";
|
||||
// vm->stdout_write(ss.str());
|
||||
// return vm->None;
|
||||
// });
|
||||
|
||||
_vm->bind__contains__(_vm->tp_dict, [](VM* vm, PyObject* obj, PyObject* key) {
|
||||
Dict& self = _CAST(Dict&, obj);
|
||||
return VAR(self.contains(key));
|
||||
@ -1615,6 +1592,15 @@ void VM::post_init(){
|
||||
return self; // for generics
|
||||
});
|
||||
|
||||
bind_property(_t(tp_type), "__annotations__", [](VM* vm, ArgsView args){
|
||||
PyTypeInfo* ti = vm->_type_info(PK_OBJ_GET(Type, args[0]));
|
||||
Tuple t(ti->annotated_fields.size());
|
||||
for(int i=0; i<ti->annotated_fields.size(); i++){
|
||||
t[i] = VAR(ti->annotated_fields[i].sv());
|
||||
}
|
||||
return VAR(std::move(t));
|
||||
});
|
||||
|
||||
bind__repr__(tp_type, [](VM* vm, PyObject* self){
|
||||
SStream ss;
|
||||
const PyTypeInfo& info = vm->_all_types[PK_OBJ_GET(Type, self)];
|
||||
@ -1679,7 +1665,7 @@ void VM::post_init(){
|
||||
add_module_operator(this);
|
||||
add_module_csv(this);
|
||||
|
||||
for(const char* name: {"this", "functools", "heapq", "bisect", "pickle", "_long", "colorsys", "typing", "datetime"}){
|
||||
for(const char* name: {"this", "functools", "heapq", "bisect", "pickle", "_long", "colorsys", "typing", "datetime", "dataclasses"}){
|
||||
_lazy_modules[name] = kPythonLibs[name];
|
||||
}
|
||||
|
||||
|
||||
@ -204,7 +204,6 @@ namespace pkpy{
|
||||
name.sv(),
|
||||
subclass_enabled,
|
||||
};
|
||||
if(mod != nullptr) mod->attr().set(name, obj);
|
||||
_all_types.push_back(info);
|
||||
return obj;
|
||||
}
|
||||
|
||||
29
tests/82_dataclasses.py
Normal file
29
tests/82_dataclasses.py
Normal file
@ -0,0 +1,29 @@
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
@dataclass
|
||||
class A:
|
||||
x: int
|
||||
y: str = '123'
|
||||
|
||||
assert repr(A(1)) == "A(x=1, y='123')"
|
||||
assert repr(A(x=3)) == "A(x=3, y='123')"
|
||||
assert repr(A(1, '555')) == "A(x=1, y='555')"
|
||||
assert repr(A(x=7, y='555')) == "A(x=7, y='555')"
|
||||
|
||||
assert asdict(A(1, '555')) == {'x': 1, 'y': '555'}
|
||||
|
||||
assert A(1, 'N') == A(1, 'N')
|
||||
assert A(1, 'N') != A(1, 'M')
|
||||
|
||||
def wrapped(cls):
|
||||
return int
|
||||
|
||||
@wrapped
|
||||
@wrapped
|
||||
@wrapped
|
||||
@wrapped
|
||||
class A:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
assert A('123') == 123
|
||||
@ -978,4 +978,13 @@ assert callable(isinstance) is True # builtin function
|
||||
|
||||
|
||||
assert id(0) is None
|
||||
assert id(2**62) is not None
|
||||
assert id(2**62) is not None
|
||||
|
||||
# test issubclass
|
||||
assert issubclass(int, int) is True
|
||||
assert issubclass(int, object) is True
|
||||
assert issubclass(object, int) is False
|
||||
assert issubclass(object, object) is True
|
||||
assert issubclass(int, type) is False
|
||||
assert issubclass(type, type) is True
|
||||
assert issubclass(float, int) is False
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user