Skip to content

Commit aeaaf02

Browse files
committed
methods bound to class instance use weakrefs so __del__ works; see #146
1 parent f58bfd1 commit aeaaf02

File tree

2 files changed

+41
-5
lines changed

2 files changed

+41
-5
lines changed

custom_components/pyscript/eval.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import keyword
1111
import logging
1212
import sys
13+
import weakref
1314

1415
import yaml
1516

@@ -697,6 +698,7 @@ async def call(self, ast_ctx, *args, **kwargs):
697698
self.exception_long = None
698699
prev_func = ast_ctx.curr_func
699700
ast_ctx.curr_func = self
701+
del args, kwargs
700702
for arg1 in self.func_def.body:
701703
val = await self.try_aeval(ast_ctx, arg1)
702704
if isinstance(val, EvalReturn):
@@ -767,19 +769,19 @@ async def __call__(self, *args, **kwargs):
767769
class EvalFuncVarClassInst(EvalFuncVar):
768770
"""Class for a callable pyscript class instance function."""
769771

770-
def __init__(self, func, ast_ctx, class_inst):
772+
def __init__(self, func, ast_ctx, class_inst_weak):
771773
"""Initialize instance with given EvalFunc function."""
772774
super().__init__(func)
773775
self.ast_ctx = ast_ctx
774-
self.class_inst = class_inst
776+
self.class_inst_weak = class_inst_weak
775777

776778
async def call(self, ast_ctx, *args, **kwargs):
777779
"""Call the EvalFunc function."""
778-
return await self.func.call(ast_ctx, self.class_inst, *args, **kwargs)
780+
return await self.func.call(ast_ctx, self.class_inst_weak(), *args, **kwargs)
779781

780782
async def __call__(self, *args, **kwargs):
781783
"""Call the function using our saved ast ctx and class instance."""
782-
return await self.func.call(self.ast_ctx, self.class_inst, *args, **kwargs)
784+
return await self.func.call(self.ast_ctx, self.class_inst_weak(), *args, **kwargs)
783785

784786

785787
class AstEval:
@@ -1771,11 +1773,17 @@ async def call_func(self, func, func_name, *args, **kwargs):
17711773
return await func.call(self, *args, **kwargs)
17721774
if inspect.isclass(func) and hasattr(func, "__init__evalfunc_wrap__"):
17731775
inst = func()
1776+
#
1777+
# we use weak references when we bind the method calls to the instance inst;
1778+
# otherwise these self references cause the object to not be deleted until
1779+
# it is later garbage collected
1780+
#
1781+
inst_weak = weakref.ref(inst)
17741782
for name in inst.__dir__():
17751783
value = getattr(inst, name)
17761784
if type(value) is not EvalFuncVar:
17771785
continue
1778-
setattr(inst, name, EvalFuncVarClassInst(value.get_func(), value.get_ast_ctx(), inst))
1786+
setattr(inst, name, EvalFuncVarClassInst(value.get_func(), value.get_ast_ctx(), inst_weak))
17791787
if getattr(func, "__init__evalfunc_wrap__") is not None:
17801788
#
17811789
# since our __init__ function is async, call the renamed one

tests/test_unit_eval.py

+28
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,34 @@ async def f2(arg, mul=1):
10021002
],
10031003
[
10041004
"""
1005+
x = []
1006+
class TestClass:
1007+
def __init__(self):
1008+
x.append(1)
1009+
1010+
@pyscript_compile
1011+
def incr(self, a):
1012+
x.append(a)
1013+
return a + 1
1014+
1015+
def incr2(self, a):
1016+
x.append(a)
1017+
return a + 1
1018+
1019+
@pyscript_compile
1020+
def __del__(self):
1021+
x.append(-1)
1022+
1023+
c = TestClass()
1024+
c.incr(10)
1025+
c.incr2(20)
1026+
del c
1027+
x
1028+
""",
1029+
[1, 10, 20, -1],
1030+
],
1031+
[
1032+
"""
10051033
def func1(m):
10061034
def func2():
10071035
m[0] += 1

0 commit comments

Comments
 (0)