Implementing __getattr__() in Pyjs

43 views
Skip to first unread message

Clavier

unread,
Feb 5, 2014, 4:50:07 PM2/5/14
to pyjs-...@googlegroups.com
Hi.

I am trying to see whether I can implement __getattr__() which is called when a method does not exist. I have to use a very long unreadable call in the current version of Pyjs:

       server=WebRpc('http://server.com/')
       server.request(arg1, arg2)                                     # Python call
       server.__getattr__('request').__call__(arg1, arg2)     # Pyjs call because __getattr__() is not automatically called

I made a class (WebRpc) that a client can access server-side objects, and this makes client-server programming very simple. In this case, supporting __getattr__() is important because it makes a server-client call exactly like a local call. The current Pyjs doesn't support it, so I have to call it explicitly making the code unreadable.

I found a previous discussion about it (https://groups.google.com/forum/#!topic/pyjamas-dev/_aJ2nmFjjas), but I don't understand why it has a performance issue. I think that a programmer can avoid it by directly calling the method (i.e. "obj.__getattr__('request').call()" instead of "obj.request()") if performance is an issue.

Please give me your advice about it (how to implement, caveats, or performance issues, etc).

C Anthony Risinger

unread,
Feb 6, 2014, 6:25:10 PM2/6/14
to pyjs-...@googlegroups.com
are you building with --enable-accessor-proto? that discussion is from 2009.

this code:

=========================
class Hi(object):

    def __getattr__(self, key):
        print(key)

hi = Hi()
print(hi.there)
=========================

...will print 'there' and 'null' to the console.

--

C Anthony

Clavier

unread,
Feb 7, 2014, 2:39:46 PM2/7/14
to pyjs-...@googlegroups.com
Your example(member referencing: hi.there) worked with and without  --enable-accessor-proto. But my application including method calls (i.e. hi.there()) building with --enable-accessor-proto still didn't work. I think that the current Pyjs throws an error on a method call(hi.there()), but not on member reference (hi.there) regardless of the option. 

Following is the simplified code I tested:

#######################
# test __getattr__()
class Hi2():
    def __init__(self, name):
        self.name=name
    def __call__(self):
        print 'Hi2.%s() was called'% (self.name)
class Hi(object):
    def __getattr__(self, key):
        print 'in Hi.__getattr__(%s)' % key
        return Hi2(key)
hi = Hi()
hi.there                 #member reference
hi.there()               #method call

Python result :
in Hi.__getattr__(there)
in Hi.__getattr__(there)
Hi2.there() was called
 
Pyjs result (IE and  FireFox):
 in Hi.__getattr__(there)
 
Unknown exception: [object Error]
Traceback:
test_getattr.py, line 17:
    hi.there()

As you can see, member reference(hi.there) worked, but method call (hi.there()) threw an exception. 

C Anthony Risinger

unread,
Feb 8, 2014, 12:22:01 AM2/8/14
to pyjs-...@googlegroups.com

This is expected behavior though?

As your example demonstrates, pyjs does not currently support __call__. This doesn't mean it isn't handling the __getattr__ call; ultimately it's failing because you're trying to call an instance (tho I can't comment offhand why you only see one print) . Try putting a print in the __init__ def of the proxied class, and I believe you will see it created twice. Also, replace __call__() with call(), then invoke with hi.there.call()... I'm pretty sure that will work as well.

If you take a look at the generated JavaScript you will see most attribute access is transformed into getattr() calls; also take a look at translater_proto.py if you want to explore further, because that is where getattr (and most other AST magic) is actually handled.

would be nice to have __call__ though...

--

C Anthony [mobile]

Clavier

unread,
Feb 8, 2014, 9:06:41 PM2/8/14
to pyjs-...@googlegroups.com
I modified the test code as you suggested, but Hi2() was not created twice.

#######################
# test __getattr__() (modified as C. Anthony sugguested)
class Hi2():
    def __init__(self, name):
        print 'in Hi2.__init__(%s)' % name
        self.name=name
    def call(self):
        print 'Hi2.%s() was called'% (self.name)
class Hi(object):
    def __getattr__(self, key):
        print 'in Hi.__getattr__(%s)' % key
        return Hi2(key)
hi = Hi()
hi.there
hi.there.call()

result of Python:
in Hi.__getattr__(there)
in Hi2.__init__(there)
in Hi.__getattr__(there)
in Hi2.__init__(there)
Hi2.there() was called
 
result of Pyjs: 
 in Hi.__getattr__(there)
 in Hi2.__init__(there)
 
 Unknown exception: [object Error]
 Traceback:
 test_getattr.py, line 18:
    hi.there.call()
 
It is a little odd because the two sentences (hi.there and hi.there.call()) share the same part (hi.there) but the latter one was not called.  It looks like it is not just a problem of __call__() not implemented. As you can see I used call(), but it didn't work.

Anyway, what I want is not to use call(). I want to make my program run both in desktop and Pyjs without modifying my source code. I think that the expected behavior is how Python behaves.  The current Pyjs's behavior is not same to Python in the above examples, so I want to fix it.

Thanks again.

Clavier

unread,
Feb 8, 2014, 11:31:52 PM2/8/14
to pyjs-...@googlegroups.com
I took a look at the translated code. This is the source:
hi = Hi()
hi.there
hi.there.call()
hi.there()

and this is the corresponding code translated (generated by pyjs/translator.py):
$m['hi'] = $m['Hi']();
$p['getattr']($m['hi'], 'there');
$m['hi']['there']['$$call']();
$m['hi']['there']();

hi.there.call() and hi.there() were not translated correctly (calling getattr is missing). I am guessing that the correct translation is:
$m['hi'] = $m['Hi']();
$p['getattr']($m['hi'], 'there');
$p['getattr']($m['hi'], 'there')['$$call']();
$p['getattr']($m['hi'], 'there')();

Am I right? If so, we have two the problems. The one is in translation, and the other is __call__() not implemented.
Reply all
Reply to author
Forward
0 new messages