Skip to content

Commit 5efddb3

Browse files
author
hartsantler
committed
fixed class level attributes, fixed __getattr__, added test__getattr__.html
1 parent 54f3256 commit 5efddb3

2 files changed

Lines changed: 85 additions & 18 deletions

File tree

pythonscript/python_to_pythonjs.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ class PythonToPythonJS(NodeVisitor):
112112
def __init__(self, module=None, module_path=None):
113113
super(PythonToPythonJS, self).__init__()
114114
self._classes = dict() ## class name : [method names]
115-
self._inline_classes = dict() ## class name : [attribute names]
115+
self._instance_attributes = dict() ## class name : [attribute names]
116+
self._class_attributes = dict()
116117
self._catch_attributes = None
117118
self._names = set() ## not used?
118119
self._instances = dict() ## instance name : class name
@@ -134,14 +135,17 @@ def get_typedef(self, instance=None, class_name=None):
134135
name = class_name,
135136
methods = self._classes[ class_name ],
136137
properties = self._decorator_class_props[ class_name ],
138+
attributes = self._instance_attributes[ class_name ],
139+
class_attributes = self._class_attributes[ class_name ],
137140
)
138141
return typedef
139142

140143
def save_module(self):
141144
if self._module and self._module_path:
142145
a = dict(
143146
classes = self._classes,
144-
inline_classes = self._inline_classes,
147+
instance_attributes = self._instance_attributes,
148+
class_attributes = self._class_attributes,
145149
decorator_class_props = self._decorator_class_props,
146150
function_return_types = self._function_return_types,
147151
)
@@ -157,13 +161,14 @@ def visit_ImportFrom(self, node):
157161
f = open( os.path.join(self._module_path, node.module+'.module'), 'rb' )
158162
a = pickle.load( f ); f.close()
159163
self._classes.update( a['classes'] )
160-
self._inline_classes.update( a['inline_classes'] )
164+
self._class_attributes.update( a['class_attributes'] )
165+
self._instance_attributes.update( a['instance_attributes'] )
161166
self._decorator_class_props.update( a['decorator_class_props'] )
162167
self._function_return_types.update( a['function_return_types'] )
163168

164169
def visit_Assert(self, node):
165170
## hijacking "assert isinstance(a,A)" as a type system ##
166-
if isinstance( node.test, Call ) and node.test.func.id == 'isinstance':
171+
if isinstance( node.test, Call ) and isinstance(node.test.func, Name) and node.test.func.id == 'isinstance':
167172
a,b = node.test.args
168173
if b.id in self._classes:
169174
self._instances[ a.id ] = b.id
@@ -217,14 +222,19 @@ def _gen_getattr_helper(self, class_name, func_name):
217222
def visit_ClassDef(self, node):
218223
name = node.name
219224
self._classes[ name ] = list() ## method names
225+
self._class_attributes[ name ] = set()
220226
self._catch_attributes = None
221227
self._decorator_properties = dict() ## property names : {'get':func, 'set':func}
222228
self._decorator_class_props[ name ] = self._decorator_properties
223229
self._instances[ 'self' ] = name
224230

225-
for dec in node.decorator_list:
226-
if isinstance(dec, Name) and dec.id == 'inline':
227-
self._catch_attributes = set()
231+
#for dec in node.decorator_list:
232+
# if isinstance(dec, Name) and dec.id == 'inline':
233+
# self._catch_attributes = set()
234+
## always catch attributes ##
235+
self._catch_attributes = set()
236+
self._instance_attributes[ name ] = self._catch_attributes
237+
228238

229239
writer.write('var(%s, __%s_attrs, __%s_parents)' % (name, name, name))
230240
writer.write('__%s_attrs = JSObject()' % name)
@@ -246,17 +256,20 @@ def visit_ClassDef(self, node):
246256
else:
247257
writer.write('__%s_attrs["%s"] = %s' % (name, item_name, item.name))
248258

249-
if item_name == '__getattr__':
250-
writer.write( self._gen_getattr_helper(name, item.name) )
259+
#if item_name == '__getattr__':
260+
# writer.write( self._gen_getattr_helper(name, item.name) )
251261

252-
elif isinstance(item, Assign):
262+
elif isinstance(item, Assign) and isinstance(item.targets[0], Name):
253263
item_name = item.targets[0].id
254-
item.targets[0].id = '__%s_%s' % (name.id, item_name)
264+
item.targets[0].id = '__%s_%s' % (name, item_name)
255265
self.visit(item) # this will output the code for the assign
256-
writer.write('%s_attrs["%s"] = %s' % (name, item_name, item.targets[0].id))
266+
writer.write('__%s_attrs["%s"] = %s' % (name, item_name, item.targets[0].id))
267+
self._class_attributes[ name ].add( item_name ) ## should this come before self.visit(item) ??
268+
else:
269+
raise NotImplementedError
257270

258-
if self._catch_attributes:
259-
self._inline_classes[ name ] = self._catch_attributes
271+
#if self._catch_attributes:
272+
# self._instance_attributes[ name ] = self._catch_attributes
260273

261274
self._catch_attributes = None
262275
self._decorator_properties = None
@@ -407,6 +420,13 @@ def visit_Attribute(self, node):
407420
if getter in self._function_return_types:
408421
node.returns_type = self._function_return_types[getter]
409422
return '%s( [%s] )' %(getter, node_value)
423+
elif node.attr in typedef.class_attributes:
424+
return "%s['__class__']['__dict__']['%s']" %(node_value, node.attr)
425+
elif node.attr in typedef.attributes:
426+
return "%s['__dict__']['%s']" %(node_value, node.attr)
427+
elif '__getattr__' in typedef.methods:
428+
func = typedef.get_pythonjs_function_name( '__getattr__' )
429+
return '%s([%s, "%s"])' %(func, node_value, node.attr)
410430
else:
411431
return 'get_attribute(%s, "%s")' % (node_value, node.attr)
412432
else:
@@ -418,8 +438,8 @@ def visit_Attribute_OLD(self, node):
418438
if name in self._instances: ## support '.' operator overloading
419439
klass = self._instances[ name ]
420440
if '__getattr__' in self._classes[ klass ]:
421-
if klass in self._inline_classes: ## static attribute
422-
if node.attr in self._inline_classes[klass]:
441+
if klass in self._instance_attributes: ## static attribute
442+
if node.attr in self._instance_attributes[klass]:
423443
#return '''JS('%s.__dict__["%s"]')''' %(name, node.attr) ## this is not ClosureCompiler compatible
424444
return '''JS('%s["__dict__"]["%s"]')''' %(name, node.attr)
425445
elif node.attr in self._classes[ klass ]: ## method
@@ -433,8 +453,8 @@ def visit_Attribute_OLD(self, node):
433453
else: ## dynamic python style
434454
return '__%s____getattr_helper( [%s, "%s"] )' % (klass, name, node.attr)
435455
else:
436-
if klass in self._inline_classes: ## static attribute
437-
if node.attr in self._inline_classes[klass]:
456+
if klass in self._instance_attributes: ## static attribute
457+
if node.attr in self._instance_attributes[klass]:
438458
return '''JS('%s["__dict__"]["%s"]')''' %(name, node.attr)
439459
elif node.attr in self._classes[ klass ]: ## method
440460
return '''JS('__%s_attrs["%s"]')''' %(klass, node.attr)

tests/test__getattr__.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<html>
2+
<head>
3+
<script src="pythonscript.js"></script>
4+
5+
<script type="text/python">
6+
7+
class A:
8+
W = 'my class attribute'
9+
10+
def __init__(self, x,y,z):
11+
self.x = x
12+
self.y = y
13+
self._z = z
14+
15+
@property
16+
def z(self):
17+
return self._z
18+
19+
def __getattr__(self, name):
20+
if name == 'hello':
21+
return 100
22+
elif name == 'world':
23+
return 200
24+
else:
25+
return 300
26+
27+
28+
def test():
29+
a = A(1,2,3)
30+
print('--testing class level attribute')
31+
print( a.W )
32+
print('--testing normal attributes and @property')
33+
print( a.x )
34+
print( a.y )
35+
print( a.z )
36+
print('--testing __getattr__')
37+
print( a.hello )
38+
print( a.world )
39+
print( a.XXX )
40+
41+
</script>
42+
</head>
43+
44+
<body>
45+
<button onclick="test()">click me</button>
46+
</body>
47+
</html>

0 commit comments

Comments
 (0)