Closed coldfix closed 6 years ago
Great work! This is a major rewrite, so it'll take some time to review.
Regarding the black_magic dependency: it does preserve function signatures, so this is probably important functionality beyond documentation, is it not? Otherwise, could you reasonably strip it out of this PR? I'm not saying that you should, just trying to assess our options here.
It is possible to exchange black_magic.decorator.wraps
for functools.wraps
. This makes the returned handlers less nice for help()
and drops support for default parameters. This should allow obsub to be used as a drop-in more easily. I have provided a commit that implements this. Coverage will probably go down a bit, since the ImportError control path isn't executed.
Btw., c5b00ff (_Use function.get instead of blackmagic.partial) should restore approximately the original speed but makes it much harder to decorate anything but basic functions. So, if we care about covering many use cases, we should probably revert this.
Finally, I managed to do some rudimentary speed tests.. Turns out, that black_magic.wraps
indeed has a pretty strong performance hit.
The code in the latest change may seem a little confusing, but it mostly fixes the performance issue by moving the only wraps
call to the contructor. Furthermore, the events are now practically indistinguishable from normal member functions. Also, it should be possible again to decorate some non-function objects.
If you are interested in the benchmark:
from obsub import event
import timeit
def create():
class X(object):
@event
def foo(self, bar=3):
return bar
return X
X = create()
x = X()
def invoke():
x.foo()
x.foo(2)
def access():
X.foo
def bench(func, number=10000):
return timeit.timeit("%s()" % func,
setup="from __main__ import %s" % func,
number=number)
if __name__ == '__main__':
print(bench('create'))
print(bench('invoke'))
print(bench('access'))
Result on CPython2.7
master | a7a7d24 | d2af23d | |
---|---|---|---|
create | 0.665 | 0.661 | 6.092 |
invoke | 0.253 | 9.753 | 0.105 |
access | 0.006 | 4.574 | 0.005 |
This looks similar onCPython2.6 and CPython3.3.
On pypy, however, the optimization doesn't make much difference:
master | a7a7d24 | d2af23d | |
---|---|---|---|
create | 0.264 | 0.266 | 0.3 |
invoke | 0.164 | 0.164 | 0.083 |
access | 0.063 | 0.063 | 0.072 |
There is a fatal flaw in this branch: Assuming a.foo
is an event
, a.foo == a.foo
will always evaluate to false. This will break code like x.connect(a.foo)
followed by x.disconnect(a.foo)
.
Reason: event.__get__
returns a new copy of a function each time and function comparison is done by identity. Furthermore, in python it is not possible to overload equality of functions (it is not possible to derive from FunctionType/MethodType).
The only possibility I can see to get the desired behaviour, is to revert back to a bound_event
class. This in turn, will make automatic help()
behave much less nicely, and therefore, the only reason left for the black_magic
dependency is the support for default arguments in python2.
I finally managed to create a reasonable version that uses
black_magic.decorator
to create nice function objects that preserve parameter names and default arguments even on python2.Possible issues to take into consideration: