Skip to content

Commit 98647e8

Browse files
committed
py/modbuiltins: Simplify and generalise dir() by probing qstrs.
This patch improves the builtin dir() function by probing the target object with all possible qstrs via mp_load_method_maybe. This is very simple (in terms of implementation), doesn't require recursion, and allows to list all methods of user-defined classes (without duplicates) even if they have multiple inheritance with a common parent. The downside is that it can be slow because it has to iterate through all the qstrs in the system, but the "dir()" function is anyway mostly used for testing frameworks and user introspection of types, so speed is not considered a priority. In addition to providing a more complete implementation of dir(), this patch is simpler than the previous implementation and saves some code space: bare-arm: -80 minimal x86: -80 unix x64: -56 unix nanbox: -48 stm32: -80 cc3200: -80 esp8266: -104 esp32: -64
1 parent a8775aa commit 98647e8

2 files changed

Lines changed: 31 additions & 34 deletions

File tree

py/modbuiltins.c

Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -173,46 +173,24 @@ STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) {
173173
MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_chr_obj, mp_builtin_chr);
174174

175175
STATIC mp_obj_t mp_builtin_dir(size_t n_args, const mp_obj_t *args) {
176-
// TODO make this function more general and less of a hack
177-
178-
mp_obj_dict_t *dict = NULL;
179-
mp_map_t *members = NULL;
180-
if (n_args == 0) {
181-
// make a list of names in the local name space
182-
dict = mp_locals_get();
183-
} else { // n_args == 1
184-
// make a list of names in the given object
185-
if (MP_OBJ_IS_TYPE(args[0], &mp_type_module)) {
186-
dict = mp_obj_module_get_globals(args[0]);
187-
} else {
188-
mp_obj_type_t *type;
189-
if (MP_OBJ_IS_TYPE(args[0], &mp_type_type)) {
190-
type = MP_OBJ_TO_PTR(args[0]);
191-
} else {
192-
type = mp_obj_get_type(args[0]);
193-
}
194-
if (type->locals_dict != NULL && type->locals_dict->base.type == &mp_type_dict) {
195-
dict = type->locals_dict;
196-
}
197-
}
198-
if (mp_obj_is_instance_type(mp_obj_get_type(args[0]))) {
199-
mp_obj_instance_t *inst = MP_OBJ_TO_PTR(args[0]);
200-
members = &inst->members;
201-
}
202-
}
203-
204176
mp_obj_t dir = mp_obj_new_list(0, NULL);
205-
if (dict != NULL) {
177+
if (n_args == 0) {
178+
// Make a list of names in the local namespace
179+
mp_obj_dict_t *dict = mp_locals_get();
206180
for (size_t i = 0; i < dict->map.alloc; i++) {
207181
if (MP_MAP_SLOT_IS_FILLED(&dict->map, i)) {
208182
mp_obj_list_append(dir, dict->map.table[i].key);
209183
}
210184
}
211-
}
212-
if (members != NULL) {
213-
for (size_t i = 0; i < members->alloc; i++) {
214-
if (MP_MAP_SLOT_IS_FILLED(members, i)) {
215-
mp_obj_list_append(dir, members->table[i].key);
185+
} else { // n_args == 1
186+
// Make a list of names in the given object
187+
// Implemented by probing all possible qstrs with mp_load_method_maybe
188+
size_t nqstr = QSTR_TOTAL();
189+
for (size_t i = 1; i < nqstr; ++i) {
190+
mp_obj_t dest[2];
191+
mp_load_method_maybe(args[0], i, dest);
192+
if (dest[0] != MP_OBJ_NULL) {
193+
mp_obj_list_append(dir, MP_OBJ_NEW_QSTR(i));
216194
}
217195
}
218196
}

tests/basics/builtin_dir.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,22 @@ def __init__(self):
1717
print('__init__' in dir(foo))
1818
print('x' in dir(foo))
1919

20+
# dir of subclass
21+
class A:
22+
def a():
23+
pass
24+
class B(A):
25+
def b():
26+
pass
27+
d = dir(B())
28+
print(d.count('a'), d.count('b'))
29+
30+
# dir of class with multiple bases and a common parent
31+
class C(A):
32+
def c():
33+
pass
34+
class D(B, C):
35+
def d():
36+
pass
37+
d = dir(D())
38+
print(d.count('a'), d.count('b'), d.count('c'), d.count('d'))

0 commit comments

Comments
 (0)