Repository: zoofIO/flexx (python)

Issue:
ui.Slider: intial value sometimes (randomly) not honored 
With the following MWE and flexx from git:

```
from flexx import app, ui
class Sliderbug(ui.Widget):
    def init(self):
        ui.Slider(min=3,max=10,value=6,step=1.)
if __name__=='__main__':
    app.launch(Sliderbug)
    app.run()

```

I sometimes get the initial value wrong, about 50%-50%, running exactly the same code. I can also verify that in the first (buggy) case the slider gives "1" as its value (rather than 3, which is the minimum):

![slider-bug](https://cloud.githubusercontent.com/assets/1029876/18514001/06a3771e-7a90-11e6-8531-67dd2413cd59.png)

![slider-ok](https://cloud.githubusercontent.com/assets/1029876/18514007/0e0e735a-7a90-11e6-8e58-eb080c3e3b7f.png)


Pull request description:
Fix recursion bug for named functions
* Fixes a nasty bug that broke things for closures that were called recursively inside a method, and `self` was used inside it.
* Methods remain anonymous (but we set `__name__` on them). Other functions (that are not lambdas) are named functions, but prefixed with "flx_"  to prevent calling the unbound method from a bound function.
* Fixes #212 consistent order of setting properties.

Patch:
diff --git a/flexx/event/_hasevents.py b/flexx/event/_hasevents.py
index fb7f47e..76c6ee0 100644
--- a/flexx/event/_hasevents.py
+++ b/flexx/event/_hasevents.py
@@ -152,8 +152,9 @@ class HasEvents(with_metaclass(HasEventsMeta, object)):
             dd = getattr(self.__class__, name)._defaults
             if dd:
                 self._set_prop(name, dd[0], True)
-        for name, value in property_values.items():
+        for name in sorted(property_values):  # sort for deterministic order
             if name in self.__properties__:
+                value = property_values[name]
                 setattr(self, name, value)  # should raise error whith readonly
             else:
                 cname = self.__class__.__name__
diff --git a/flexx/event/_js.py b/flexx/event/_js.py
index 9b19447..5bfa70b 100644
--- a/flexx/event/_js.py
+++ b/flexx/event/_js.py
@@ -95,8 +95,10 @@ class HasEventsJS:
             if not (isinstance(s, str) and len(s)):
                 raise ValueError('Connection string must be nonempty strings.')
         
-        name = func.name[6:] if func.name.startswith('bound ') else func.name
-        return self.__create_Handler(func, name or 'anonymous', connection_strings)
+        # Get function name (Flexx sets __name__ on methods)
+        name = func.__name__ or func.name or 'anonymous'
+        name = name.split(' ')[-1].split('flx_')[-1]
+        return self.__create_Handler(func, name, connection_strings)
     
     def __create_PyProperty(self, name):
         self.__create_Property(name)
diff --git a/flexx/pyscript/functions.py b/flexx/pyscript/functions.py
index f1b12cb..32b19b5 100644
--- a/flexx/pyscript/functions.py
+++ b/flexx/pyscript/functions.py
@@ -143,8 +143,8 @@ def js_rename(jscode, cur_name, new_name):
     Returns:
         jscode (str): the modified JavaScript source code
     """
-    jscode = jscode.replace('function %s' % cur_name,
-                            'function %s' % (new_name.split('.')[-1]), 1)
+    jscode = jscode.replace('function flx_%s' % cur_name,
+                            'function flx_%s' % (new_name.split('.')[-1]), 1)
     jscode = jscode.replace('%s = function' % cur_name, 
                             '%s = function' % (new_name), 1)
     jscode = jscode.replace('%s.prototype' % cur_name, 
diff --git a/flexx/pyscript/parser2.py b/flexx/pyscript/parser2.py
index 94b52df..cbc41c6 100644
--- a/flexx/pyscript/parser2.py
+++ b/flexx/pyscript/parser2.py
@@ -711,23 +711,29 @@ class Parser2(Parser1):
     def parse_FunctionDef(self, node, lambda_=False):
         # Common code for the FunctionDef and Lambda nodes.
         
+        has_self = node.arg_nodes and node.arg_nodes[0].name in ('self', 'this')
+        
         # Bind if this function is inside a function, and does not have self
         binder = ''  # code to add to the end
         if len(self._stack) >= 1 and self._stack[-1][0] == 'function':
-            if not (node.arg_nodes and node.arg_nodes[0].name in ('self', 'this')):
+            if not has_self:
                 binder = ').bind(this)'
         
         # Init function definition
+        # Non-anonymouse functions get a name so that they are debugged more
+        # easily and resolve to the correct event labels in flexx.event. However,
+        # we cannot use the exact name, since we don't want to actually *use* it.
+        # Classes give their methods a __name__, so no need to name these.
         code = []
         func_name = ''
         if not lambda_:
-            func_name = '' if node.name in RESERVED else node.name
+            if not has_self:
+                func_name = 'flx_' + node.name
             prefixed = self.with_prefix(node.name)
             if prefixed == node.name:  # normal function vs method
                 self.vars.add(node.name)
                 self._seen_func_names.add(node.name)
             code.append(self.lf('%s = ' % prefixed))
-            #code.append('function %s (' % node.name)
         code.append('%sfunction %s%s(' % ('(' if binder else '',
                                           func_name,
                                           ' ' if func_name else ''))
diff --git a/flexx/pyscript/stdlib.py b/flexx/pyscript/stdlib.py
index 87960cf..ccde1ae 100644
--- a/flexx/pyscript/stdlib.py
+++ b/flexx/pyscript/stdlib.py
@@ -133,6 +133,7 @@ FUNCTIONS['op_instantiate'] = """function (ob, args) { // nargs: 2
         if (Object[name] === undefined &&
             typeof ob[name] === 'function' && !ob[name].nobind) {
             ob[name] = ob[name].bind(ob);
+            ob[name].__name__ = name;
         }
     }
     if (ob.__init__) {


Test patch:
diff --git a/flexx/event/tests/test_both.py b/flexx/event/tests/test_both.py
index a281678..ceb991e 100644
--- a/flexx/event/tests/test_both.py
+++ b/flexx/event/tests/test_both.py
@@ -22,6 +22,7 @@ from flexx.event._js import create_js_hasevents_class, HasEventsJS, reprs
 from flexx.pyscript.functions import py2js, evaljs, evalpy, js_rename
 from flexx.pyscript.stdlib import get_std_info, get_partial_std_lib
 
+import os
 import sys
 from math import isnan as isNaN
 
@@ -971,6 +972,7 @@ def test_event_order1(Order1):
     
     return m.r
 
+
 @run_in_both(Order1, "['p', 'b', 'w', 'm']")
 def test_event_order2(Order1):
     
@@ -992,7 +994,8 @@ def test_event_order2(Order1):
     
     return m.r
 
-@run_in_both(Order1, "['p', 'b', '!', 'w', 'm']")
+
+@run_in_both(Order1, "['p', 'b', 'c', 'w', 'm']")
 def test_event_order3(Order1):
     
     # monkey patch JS event system
@@ -1005,7 +1008,7 @@ def test_event_order3(Order1):
     m = Order1()
     def myfunc(*events):
         for ev in events:
-            m.r.append('!')
+            m.r.append('c')
     m.connect(myfunc, 'foo:ccc')
     m.emit('foo', {})
     
@@ -1018,6 +1021,35 @@ def test_event_order3(Order1):
     return m.r
 
 
+if not os.getenv('TRAVIS'):
+    # Meeh, specific version of Node?
+    
+    @run_in_both(Order1, "['p', 'b', 'c', 'w', 'm']")
+    def test_event_order4(Order1):
+        
+        # monkey patch JS event system
+        if this_is_js():
+            pending_funcs = []
+            def setTimeout(func):
+                pending_funcs.append(func)
+            root.setTimeout = setTimeout
+        
+        m = Order1()
+        def ccc(*events):
+            for ev in events:
+                m.r.append('c')
+        m.connect(ccc, 'foo')  # use name of function
+        m.emit('foo', {})
+        
+        if this_is_js():
+            for func in pending_funcs:
+                func()
+        else:
+            event.loop.iter()
+        
+        return m.r
+
+
 class ConnectFail(event.HasEvents):
     
     @event.connect('foo.bar')
diff --git a/flexx/pyscript/tests/test_parser2.py b/flexx/pyscript/tests/test_parser2.py
index 28d7164..11c7a1d 100644
--- a/flexx/pyscript/tests/test_parser2.py
+++ b/flexx/pyscript/tests/test_parser2.py
@@ -297,7 +297,7 @@ class TestFunctions:
         lines = [line for line in code.split('\n') if line]
         
         assert len(lines) == 4  # only three lines + definition
-        assert lines[1] == 'func1 = function func1 () {'  # no args
+        assert lines[1] == 'func1 = function flx_func1 () {'  # no args
         assert lines[2].startswith('  ')  # indented
         assert lines[3] == '};'  # dedented
     
@@ -310,7 +310,7 @@ class TestFunctions:
         lines = [line for line in code.split('\n') if line]
         
         assert len(lines) == 4  # only three lines + definition
-        assert lines[1] == 'method1 = function method1 () {'  # no args, no self/this
+        assert lines[1] == 'method1 = function () {'  # no args, no self/this
         assert lines[2].startswith('  ')  # indented
         assert lines[3] == '};'  # dedented
     
@@ -322,7 +322,7 @@ class TestFunctions:
         code = py2js(func)
         lines = [line for line in code.split('\n') if line]
         
-        assert lines[1] == 'func = function func (foo, bar) {'
+        assert lines[1] == 'func = function (foo, bar) {'
         assert '2' in code
         
         assert evaljs(code + 'func(2)') == '0'
@@ -427,6 +427,18 @@ class TestFunctions:
         assert 'var x' in py2js(func2)
         assert 'var x' in py2js(func3)
     
+    def test_recursion(self=None):
+        
+        code = 'def f(i): i *= 2; return i if i > 10 else f(i)\n\n'
+        assert evalpy(code + 'f(1)') == '16'
+        
+        clscode = 'class G:\n  def __init__(self): self.i = 1\n\n'
+        code = clscode + '  def f(self): self.i *= 2; return self.i if self.i > 10 else self.f()\n\n'
+        assert evalpy(code + 'g = G(); g.f()') == '16'
+        
+        code = clscode + '  def f(self):\n    def h(): self.i *= 2; return self.i if self.i > 10 else h()\n\n'
+        assert evalpy(code + '    return h()\n\ng = G(); g.f()') == '16'
+    
     def test_global(self):
         assert py2js('global foo;foo = 3').strip() == 'foo = 3;'