@@ -18,18 +18,83 @@ class VarnameRetrievingWarning(Warning):
1818class VarnameRetrievingError (Exception ):
1919 """When failed to retrieve the varname"""
2020
21- def _get_executing (caller ):
22- """Try to get the executing object
21+ def _get_node (caller ):
22+ """Try to get node from the executing object.
23+
2324 This can fail when a frame is failed to retrieve.
2425 One case should be when python code is executed in
2526 R pacakge `reticulate`, where only first frame is kept.
27+
28+ When the node can not be retrieved, try to return the first statement.
2629 """
2730 try :
2831 frame = inspect .stack ()[caller + 2 ].frame
2932 except IndexError :
3033 return None
3134 else :
32- return executing .Source .executing (frame )
35+ exet = executing .Source .executing (frame )
36+
37+ if exet .node :
38+ return exet .node
39+
40+ return list (exet .statements )[0 ]
41+
42+ def _lookfor_parent_assign (node ):
43+ """Look for an ast.Assign node in the parents"""
44+ while hasattr (node , 'parent' ):
45+ node = node .parent
46+
47+ if isinstance (node , ast .Assign ):
48+ return node
49+ return None
50+
51+ def _lookfor_child_nameof (node ):
52+ """Look for ast.Call with func=Name(id='nameof',...)"""
53+ # pylint: disable=too-many-return-statements
54+ if isinstance (node , ast .Call ):
55+ # if node.func.id == 'nameof':
56+ # return node
57+
58+ # We want to support alias for nameof, i.e. nameof2
59+ # Or called like: varname.nameof(test)
60+ # If all args are ast.Name, then if must be alias of nameof
61+ # Since this is originated from it, and there is no other
62+ # ast.Call node in args
63+ if not any (isinstance (arg , ast .Call ) for arg in node .args ):
64+ return node
65+
66+ # print(nameof(test))
67+ for arg in node .args :
68+ found = _lookfor_child_nameof (arg )
69+ if found :
70+ return found
71+
72+ elif isinstance (node , ast .Compare ):
73+ # nameof(test) == 'test'
74+ found = _lookfor_child_nameof (node .left )
75+ if found :
76+ return found
77+ # 'test' == nameof(test)
78+ for comp in node .comparators :
79+ found = _lookfor_child_nameof (comp )
80+ if found :
81+ return found
82+
83+ elif isinstance (node , ast .Assert ):
84+ # assert nameof(test) == 'test'
85+ found = _lookfor_child_nameof (node .test )
86+ if found :
87+ return found
88+
89+ elif isinstance (node , ast .Expr ): # pragma: no cover
90+ # print(nameof(test)) in ipython's forloop
91+ # issue #5
92+ found = _lookfor_child_nameof (node .value )
93+ if found :
94+ return found
95+
96+ return None
97+
3398
3499def varname (caller = 1 , raise_exc = False ):
35100 """Get the variable name that assigned by function/class calls
@@ -53,42 +118,40 @@ def varname(caller=1, raise_exc=False):
53118 in the assign node. (e.g: `a = b = func()`, in such a case,
54119 `b == 'a'`, may not be the case you want)
55120 """
56- exec_obj = _get_executing (caller )
57- if not exec_obj :
121+ node = _get_node (caller )
122+ if not node :
58123 if raise_exc :
59- raise VarnameRetrievingError ("Unable to retrieve the frame ." )
124+ raise VarnameRetrievingError ("Unable to retrieve the ast node ." )
60125 VARNAME_INDEX [0 ] += 1
61126 warnings .warn (f"var_{ VARNAME_INDEX [0 ]} used." ,
62127 VarnameRetrievingWarning )
63128 return f"var_{ VARNAME_INDEX [0 ]} "
64129
65- node = exec_obj .node
66- while hasattr (node , 'parent' ):
67- node = node .parent
68-
69- if isinstance (node , ast .Assign ):
70- # Need to actually check that there's just one
71- if len (node .targets ) > 1 :
72- warnings .warn ("Multiple targets in assignment, variable name "
73- "on the very left will be used." ,
74- MultipleTargetAssignmentWarning )
75- target = node .targets [0 ]
76-
77- # Need to check that it's a variable
78- if isinstance (target , ast .Name ):
79- return target .id
80-
130+ node = _lookfor_parent_assign (node )
131+ if not node :
132+ if raise_exc :
81133 raise VarnameRetrievingError (
82- f"Invaid variable assigned: { ast . dump ( target )!r } "
134+ 'Failed to retrieve the variable name.'
83135 )
136+ VARNAME_INDEX [0 ] += 1
137+ warnings .warn (f"var_{ VARNAME_INDEX [0 ]} used." , VarnameRetrievingWarning )
138+ return f"var_{ VARNAME_INDEX [0 ]} "
139+
140+ # Need to actually check that there's just one
141+ # give warnings if: a = b = func()
142+ if len (node .targets ) > 1 :
143+ warnings .warn ("Multiple targets in assignment, variable name "
144+ "on the very left will be used." ,
145+ MultipleTargetAssignmentWarning )
146+ target = node .targets [0 ]
84147
85- if raise_exc :
86- raise VarnameRetrievingError ('Failed to retrieve the variable name.' )
148+ # must be a variable
149+ if isinstance (target , ast .Name ):
150+ return target .id
87151
88- VARNAME_INDEX [0 ] += 1
89- warnings .warn (f"var_{ VARNAME_INDEX [0 ]} used." ,
90- VarnameRetrievingWarning )
91- return f"var_{ VARNAME_INDEX [0 ]} "
152+ raise VarnameRetrievingError (
153+ f"Invaid variable assigned: { ast .dump (target )!r} "
154+ )
92155
93156def will (caller = 1 , raise_exc = False ):
94157 """Detect the attribute name right immediately after a function call.
@@ -118,15 +181,14 @@ def i_will():
118181 VarnameRetrievingError: When `raise_exc` is `True` and we failed to
119182 detect the attribute name (including not having one)
120183 """
121- exec_obj = _get_executing (caller )
122- if not exec_obj :
184+ node = _get_node (caller )
185+ if not node :
123186 if raise_exc :
124187 raise VarnameRetrievingError ("Unable to retrieve the frame." )
125188 return None
126189
127190 ret = None
128191 try :
129- node = exec_obj .node
130192 # have to be used in a call
131193 assert isinstance (node , (ast .Attribute , ast .Call )), (
132194 "Invalid use of function `will`"
@@ -174,24 +236,19 @@ def nameof(*args):
174236 *args: A couple of variables passed in
175237
176238 Returns:
177- tuple|str:
239+ tuple|str: The names of variables passed in
178240 """
179- exec_obj = _get_executing (0 )
180- if not exec_obj :
181- raise VarnameRetrievingError ("Unable to retrieve the frame." )
182-
183- if not exec_obj .node :
184- # we cannot do: assert nameof(a) == 'a' in pytest
185- raise VarnameRetrievingError ("Callee's node cannot be detected." )
186-
187- assert isinstance (exec_obj .node , ast .Call )
241+ node = _get_node (0 )
242+ node = _lookfor_child_nameof (node )
243+ if not node :
244+ raise VarnameRetrievingError ("Unable to retrieve callee's node." )
188245
189246 ret = []
190- for node in exec_obj . node .args :
191- if not isinstance (node , ast .Name ):
247+ for arg in node .args :
248+ if not isinstance (arg , ast .Name ):
192249 raise VarnameRetrievingError ("Only variables should "
193250 "be passed to nameof." )
194- ret .append (node .id )
251+ ret .append (arg .id )
195252
196253 if not ret :
197254 raise VarnameRetrievingError ("At least one variable should be "
0 commit comments