Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
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 2 Lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def __instancecheck__(cls, inst):
except AttributeError:
return False
else:
return type(value) in _const_types[cls]
return isinstance(value, _const_types[cls])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like making this isinstance will allow type sub-classes for all the different constant node types. I guess this only affects 'ast' module users that are using the old class names? Just wondering if it is safe to allow any subtype in there. The constants need to get marshaled don't they?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that serialization has to be considered in isinstance().

It looks like making this isinstance will allow type sub-classes for all the different constant node types.

I don't understand. The second argument is _const_types[cls], not _const_types. ast.Str only check for str and str subclasses.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the change affects all the constant node types (Str, Num, Bytes, etc). I was wondering aloud if that was okay. I looked at the new ast.py code again today and tested older versions of Python. I think this change is fine. It only affects code that uses the backwards compatibility node types. The _ABC.__instancecheck__ is not enough to make perfect backwards compatibility. Old versions of Python didn't do any type checking for ast node values so you could do something like:

    x = ast.Str({})
    assert isinstance(x, ast.Str)

That will fail with the new ast.py module, even after this PR. Maybe the following would be better. Make Constant "remember" what kind of constant node it is when created. Then have __instancecheck__ use that as the first test.

--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -338,6 +338,9 @@ Constant.s = property(_getter, _setter)
 class _ABC(type):
 
     def __instancecheck__(cls, inst):
+        const_type = getattr(inst, '_const_type', None)
+        if const_type and const_type is cls:
+            return True
         if not isinstance(inst, Constant):
             return False
         if cls in _const_types:
@@ -351,7 +354,9 @@ class _ABC(type):
 
 def _new(cls, *args, **kwargs):
     if cls in _const_types:
-        return Constant(*args, **kwargs)
+        n = Constant(*args, **kwargs)
+        n._const_type = cls
+        return n
     return Constant.__new__(cls, *args, **kwargs)
 
 class Num(Constant, metaclass=_ABC):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done simpler if Str.__new__ return Str instead of Constant. But I think that since old classes will be removed eventually, it is better to return the pure Constant.

I don't think we should support ast.Str({}).

return type.__instancecheck__(cls, inst)

def _new(cls, *args, **kwargs):
Expand Down
4 changes: 4 additions & 0 deletions 4 Lib/test/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,10 @@ def test_isinstance(self):
self.assertFalse(isinstance(ast.Constant(), ast.NameConstant))
self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis))

class S(str): pass
self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add this test?
self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(maybe instanciate the ast object only once)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it looks clearer if the ast object is instantiated at the same line. In most tests different ast objects are tested in sequential lines.

self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num))

def test_subclasses(self):
class N(ast.Num):
def __init__(self, *args, **kwargs):
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.