Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion astroid/brain/brain_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def _functools_partial_inference(node, context=None):
doc=inferred_wrapped_function.doc,
lineno=inferred_wrapped_function.lineno,
col_offset=inferred_wrapped_function.col_offset,
parent=inferred_wrapped_function.parent,
parent=node.parent,
)
partial_function.postinit(
args=inferred_wrapped_function.args,
Expand Down
12 changes: 2 additions & 10 deletions astroid/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1209,16 +1209,8 @@ def _filter_stmts(self, stmts, frame, offset):
# want to clear previous assignments if any (hence the test on
# optional_assign)
if not (optional_assign or are_exclusive(_stmts[pindex], node)):
if (
# In case of partial function node, if the statement is different
# from the origin function then it can be deleted otherwise it should
# remain to be able to correctly infer the call to origin function.
not node.is_function
or node.qname() != "PartialFunction"
or node.name != _stmts[pindex].name
):
del _stmt_parents[pindex]
del _stmts[pindex]
del _stmt_parents[pindex]
del _stmts[pindex]
if isinstance(node, AssignName):
if not optional_assign and stmt.parent is mystmt.parent:
_stmts = []
Expand Down
5 changes: 4 additions & 1 deletion astroid/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,10 @@ class PartialFunction(scoped_nodes.FunctionDef):
def __init__(
self, call, name=None, doc=None, lineno=None, col_offset=None, parent=None
):
super().__init__(name, doc, lineno, col_offset, parent)
super().__init__(name, doc, lineno, col_offset, parent=None)
# A typical FunctionDef automatically adds its name to the parent scope,
# but a partial should not, so defer setting parent until after init
self.parent = parent
self.filled_positionals = len(call.positional_arguments[1:])
self.filled_args = call.positional_arguments[1:]
self.filled_keywords = call.keyword_arguments
Expand Down
40 changes: 40 additions & 0 deletions tests/unittest_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2720,6 +2720,46 @@ def other_test(a, b, *, c=1):
assert isinstance(inferred, astroid.Const)
assert inferred.value == expected_value

def test_partial_assignment(self):
"""Make sure partials are not assigned to original scope."""
ast_nodes = astroid.extract_node(
"""
from functools import partial
def test(a, b): #@
return a + b
test2 = partial(test, 1)
test2 #@
def test3_scope(a):
test3 = partial(test, a)
test3 #@
"""
)
func1, func2, func3 = ast_nodes
assert func1.parent.scope() == func2.parent.scope()
assert func1.parent.scope() != func3.parent.scope()
partial_func3 = next(func3.infer())
# use scope of parent, so that it doesn't just refer to self
scope = partial_func3.parent.scope()
assert scope.name == "test3_scope", "parented by closure"

def test_partial_does_not_affect_scope(self):
"""Make sure partials are not automatically assigned."""
ast_nodes = astroid.extract_node(
"""
from functools import partial
def test(a, b):
return a + b
def scope():
test2 = partial(test, 1)
test2 #@
"""
)
test2 = next(ast_nodes.infer())
mod_scope = test2.root()
scope = test2.parent.scope()
assert set(mod_scope) == {"test", "scope", "partial"}
assert set(scope) == {"test2"}


def test_http_client_brain():
node = astroid.extract_node(
Expand Down