GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — bpo-41905: added abc.update_abstractmethods by bentheiii · Pull Request #22485 · python/cpython

Conversation

@bentheiii

@bentheiii bentheiii commented Oct 1, 2020 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

relevant python-ideas thread: https://mail.python.org/archives/list/[email protected]/thread/6BNJ3YSEBPHEPGXSAZGBW3TJ64ZGZIHE/

https://bugs.python.org/issue41905

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
ben avrahami
added update_abstractmethods
f65b0f8
@bentheiii
bentheiii requested review from ericvsmith and rhettinger as code owners October 1, 2020 20:57
@the-knights-who-say-ni the-knights-who-say-ni added the CLA not signed label Oct 1, 2020
@the-knights-who-say-ni

the-knights-who-say-ni commented Oct 1, 2020

Copy link
Copy Markdown

Hello, and thanks for your contribution!

I'm a bot set up to make sure that the project can legally accept this contribution by verifying everyone involved has signed the PSF contributor agreement (CLA).

Recognized GitHub username

We couldn't find a bugs.python.org (b.p.o) account corresponding to the following GitHub usernames:

@bentheiii

This might be simply due to a missing "GitHub Name" entry in one's b.p.o account settings. This is necessary for legal reasons before we can look at this contribution. Please follow the steps outlined in the CPython devguide to rectify this issue.

You can check yourself to see if the CLA has been received.

Thanks again for the contribution, we look forward to reviewing it!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot added the awaiting review label Oct 1, 2020
@the-knights-who-say-ni the-knights-who-say-ni added CLA signed and removed CLA not signed labels Oct 1, 2020
@blurb-it
📜🤖 Added by blurb_it.
c04ba74
ericvsmith
ericvsmith previously requested changes Oct 1, 2020
View reviewed changes

@ericvsmith ericvsmith left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Although I'm no expert on ABC's, this looks good to me, with a few noted tiny changes. I'll do some more research before signing off on this, though.

My only real concern is that dataclasses now imports abc. We've jumped through hoops to make dataclasses not import typing, because it's slow to import. I don't know if abc brings the same issues, but I'll try to do some investigating. The trick it uses for typing is to check if 'typing' is in sys.modules before checking for typing.<somthing_or_other>. I'm not sure such trickery is warranted here. Probably not, since just starting the REPL looks like it imports abc (but not typing).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Doc/library/abc.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/abc.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/abc.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/abc.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/dataclasses.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot removed the awaiting review label Oct 1, 2020
@bedevere-bot

bedevere-bot commented Oct 1, 2020

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot added the awaiting changes label Oct 1, 2020
ben avrahami added 3 commits October 2, 2020 00:24
ben avrahami
fix whitespaces
ea69ae6
ben avrahami
Merge branch 'master' of https://github.com/bentheiii/cpython into ma…
8b76715
…ster
ben avrahami
documentation fixes
61c4f40
@bentheiii

bentheiii commented Oct 1, 2020

Copy link
Copy Markdown
Contributor Author

I have made the requested changes; please review again

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot added awaiting change review and removed awaiting changes labels Oct 1, 2020
@bedevere-bot

bedevere-bot commented Oct 1, 2020

Copy link
Copy Markdown

Thanks for making the requested changes!

@ericvsmith: please review the changes made to this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot
bedevere-bot requested a review from ericvsmith October 1, 2020 21:45
iritkatriel
iritkatriel reviewed Oct 1, 2020
View reviewed changes
Comment thread Lib/test/test_abc.py

B()
self.assertEqual(B.__abstractmethods__, set())

@iritkatriel iritkatriel Oct 1, 2020 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I think you need this test case to cover the first loop:

import abc
class FooABC(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def bar(self):
        pass

del FooABC.bar
assert ('bar' in FooABC.__abstractmethods__)
assert ('bar' not in FooABC.__dict__)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@gvanrossum gvanrossum Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

@iritkatriel What does that test case accomplish other than showing that FooABC is now in an inconsistent state?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@iritkatriel iritkatriel Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

@gvanrossum In this comment I left out the update_abstractmethods, see Ben's version which he committed as test_update_del. Without this test the first loop in update_abstractmethods was not exercised.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gvanrossum and yuvalturg reacted with thumbs up emoji
All reactions
  • 👍 2 reactions
@ericvsmith

ericvsmith commented Oct 1, 2020

Copy link
Copy Markdown
Member

Since update_abstractmethods is documented as being usable as a decorator, there should be a test for that. I realize it's trivially obvious that it works, but still. Although I guess you'll need another decorator that adds an abstract method (like dataclasses used to do) in order to test it. And then use them together.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ben avrahami added 2 commits October 2, 2020 01:08
ben avrahami
additional tests
a5a9fd7
ben avrahami
rename test
5e1fffc
iritkatriel
iritkatriel reviewed Oct 1, 2020
View reviewed changes
Comment thread Lib/abc.py Outdated
abstracts = set()
# check the existing abstract methods, keep only the ones that are
# still abstract
for name in cls.__abstractmethods__:

@iritkatriel iritkatriel Oct 1, 2020 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Since you didn't check in this function that it's an ABC, it's not guaranteed that cls.__abstractmethods__ works:

>>> class FooABC: pass
... 
>>> FooABC.__abstractmethods__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __abstractmethods__

Maybe add a test for when this function is called for a non-ABC.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@ericvsmith

ericvsmith commented Oct 1, 2020

Copy link
Copy Markdown
Member

Since update_abstractmethods is documented as being usable as a decorator, there should be a test for that. I realize it's trivially obvious that it works, but still. Although I guess you'll need another decorator that adds an abstract method (like dataclasses used to do) in order to test it. And then use them together.

Change looks good. Thanks.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ben avrahami
added explicit check for ABCMeta
2f95b84
@bentheiii

bentheiii commented Oct 1, 2020

Copy link
Copy Markdown
Contributor Author

My only real concern is that dataclasses now imports abc. We've jumped through hoops to make dataclasses not import typing, because it's slow to import. I don't know if abc brings the same issues, but I'll try to do some investigating. The trick it uses for typing is to check if 'typing' is in sys.modules before checking for typing.<somthing_or_other>. I'm not sure such trickery is warranted here. Probably not, since just starting the REPL looks like it imports abc (but not typing).

If this does prove to be an issue, then we can change the isinstance(cls, ABCMeta) checking to hasattr(cls, '__abstractmethods__') (no sys.modules check necessary), and then import abc.update_abstractmethods before calling it inside the "if" block.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

rhettinger
rhettinger reviewed Oct 2, 2020
View reviewed changes
Comment thread Lib/functools.py Outdated
# Find user-defined comparisons (not those inherited from object or abstract).
roots = {op for op in _convert
if getattr(cls, op, None) is not getattr(object, op, None)
and not getattr(getattr(cls, op, None), '__isabstractmethod__', False)}

@rhettinger rhettinger Oct 2, 2020

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

The nested getattr() isn't very clear.

Also, since this is in the inner loop for a dynamic decorator, it will slow the class creation process. I didn't used to think that was important but experience with named tuples show that people care a great deal about the amount of computation that occurs during an import.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@bentheiii bentheiii Oct 2, 2020 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

would using a walrus operator be okay here?

roots = {...
         if (root:=getattr(cls, op, None)) is not getattr(object, op, None)
         and not getattr(root, '__isabstractmethod__', False)}

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ericvsmith ericvsmith Oct 2, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

That seems fine to me.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
ben avrahami added 6 commits October 3, 2020 12:05
ben avrahami
update_abstractmethods now ignores non-abstract classes
4f3d846
ben avrahami
total_ordering now only overrides abtract methods defined in supercla…
74473c8
…sses
ben avrahami
doc fix
92326c9
ben avrahami
excluded total_ordering from this issue
1e47ee4
ben avrahami
documented ABC being ABC unaware
740183f
ben avrahami
fixed potential bug with deletion of implementation
114028e
iritkatriel
iritkatriel reviewed Oct 4, 2020
View reviewed changes
Comment thread Lib/abc.py
for name in getattr(scls, '__abstractmethods__', ()):
value = getattr(cls, name, None)
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)

@iritkatriel iritkatriel Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Add tests for deeper inheritance - I think iterating over __bases__ may not be enough:

>>> class A: pass
... 
>>> class B(A): pass
... 
>>> class C(B): pass
... 
>>> C.__bases__
(<class '__main__.B'>,)
>>> B.__bases__
(<class '__main__.A'>,)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@bentheiii bentheiii Oct 4, 2020 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It actually is- we are assured that the parent's __abstractmethods__ are correct since, after a class is subclassed, its abstraction status cannot change:

class A(ABC):
  @abstractmethod
  def foo(self): pass

class B(A):
  pass

# after this point, A.__abstractmethods__ will never change, so we can rely on it always being correct when checking B's Abstraction status

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@bentheiii bentheiii Oct 4, 2020

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I added a test case regardless

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@iritkatriel iritkatriel Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I see, interesting. How about this case? I think you might need to break after the first hit for the method:

class A(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def foo(self):
        pass

class B(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def foo(self):
        pass

class C(A, B):
    pass

A.foo = lambda self: None


assert(C.foo is not abstract)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@bentheiii bentheiii Oct 4, 2020

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This is expected (albeit confusing) behavior. Since abstraction status cannot change after subclassing, A is still considered abstract, even though its method is implemented. However, since namespace resolution occurs through the __dict__ attribute, The method is defined as far as C is concerned. Indeed, if you were to call update_abstractmethods(C) after this code, C would become concrete and usable, since its foo method implemented.

In essence, abstract methods shouldn't be changed after the class is used, so this use case is not handled by this implementation.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@gvanrossum gvanrossum Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Well, I disagree with the check for empty __subclasses__(), and I think in Irit's last example the right thing to do is to call abc.update_abstractmethods(A) and then abc.update_abstractmethods(C).

Note that it's not technically true that the abstractness cannot change once subclassed -- it may be undocumented but one could just write A.__abstractmethods__ = ().

FWIW I do agree that the code should assume that the abstractness of the bases is correct. If some base lies about it, well, Garbage In, Garbage Out.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@iritkatriel iritkatriel Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I don't think abc.update_abstractmethods(A) would have any impact here, A never changed.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@gvanrossum gvanrossum Oct 4, 2020 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Sorry, I was commenting on your earlier example which has A.foo = lambda ... -- in that example it certainly would have an effect. :-)

My disagreement was with this quote from @bentheiii:

In essence, abstract methods shouldn't be changed after the class is used, so this use case is not handled by this implementation.

I agree with @iritkatriel that adding that test would be useful (I didn't verify -- maybe it's already added?).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@iritkatriel iritkatriel Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Sorry, I was commenting on your earlier example which has A.foo = lambda ... -- in that example it certainly would have an effect. :-)

Ah, in that case it raises an exception that A has been subclasses so you can't update it anymore. I think there's a test for that already but if not then there should be.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@iritkatriel iritkatriel Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I see now - you're suggesting to remove that exception. In that case this test is probably needed.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
ben avrahami
added layered test case
ba8df08
gvanrossum
gvanrossum reviewed Oct 4, 2020
View reviewed changes
Comment thread Lib/abc.py Outdated
Comment on lines +125 to +126
"""Repopulate the abstract methods of an abstract class, or a subclass of
an abstract class.

@gvanrossum gvanrossum Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I would like the summary to fit on one line.

Suggested change
"""Repopulate the abstract methods of an abstract class, or a subclass of
an abstract class.
"""Recalculate the set of abstract methods of an abstract class.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/abc.py Outdated
Comment on lines +149 to +151
if cls.__subclasses__():
raise RuntimeError("cannot update abstract methods of class after"
" subclassing")

@gvanrossum gvanrossum Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I'm not sure I like this. This function is potentially expensive. It could even be detrimental -- I could imagine some very dynamic code that's messing around dynamically with a base class and then calls update_abstractmethods() for all subclasses. So I propose to remove this check (and the documentation about it).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@bentheiii bentheiii Oct 4, 2020

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

That might be okay, However, removing this check means that we have to call update_abstractmethods on all of cls's subclasses, meaning that cls.__subclasses__() will still be called, so performance will not improve.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@gvanrossum gvanrossum Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Only if you're not sure that there are no subclasses. In @dataclass we can assume there are no subclasses, and that's so in most class decorators (which are only applicable to freshly created classes).

I recommend that update_abstractmethods() should not try to fix up subclasses -- it's just the case that an ambitious dynamic class hacker could take two documented features (this and __subclasses__()) and create something that automatically updates affected subclasses.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@bentheiii bentheiii Oct 4, 2020 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I think that could cause a problem if user chain two decorators: one that updates subclasses and one that doesn't

def decoratorA(cls):
  """implements methods and creates a subclass, calling
  update_abstractmethods on cls and its subclasses
  """
  ...

def decoratorB(cls):
  """implements methods only, not updating subclasses
  """
  ...

@decoratorB
@decoratorA
class A(SomeABC):
  ...

# A's subclass is now out of sync with decoratorB's changes

Thinking about it, the previous implementation was indeed faulty since, even if a decorator that needed to subclass cls was responsible enough to delay subclassing until after calling update_abstractmethods, that would meant that follow-up decorators simply couldn't call it.

The only sensible solution is to call update_abstractmethods on all of the class's subclasses with each call (@dataclass can't be certain that the subject class wasn't subclassed in a previous mixin decorator).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@gvanrossum gvanrossum Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

If you do that, you get what you deserve.

Please keep it simple.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@bentheiii bentheiii Oct 4, 2020

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It's implemented in the latest commit, but I must say I'm not sure what you mean. Who "gets what they deserve"?

  • Whoever implemented decoratorA (which may well be @dataclass) was right to assume there are no subclasses, since it did not create them, and wanted to avoid unnecessary checks.
  • Whoever implemented decoratorB did their due diligence by making sure it returned the class and its subclasses in a synced state.
  • Whoever wrote class A may be totally unaware that decoratorB creates subclasses or that decoratorA doesn't take them into account. Even if they did, they would need to implement a fix themselves, which would require at the least to iterate through A.__subclasses__(). Even that won't keep them 100% safe, however, since these subclasses can have subclasses of their own. Very far fetched, perhaps, but that's exactly what very dynamic code might produce.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@bentheiii bentheiii Oct 4, 2020 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Truth be told, if we do assume that decorators and tools are allowed to subclass (and perhaps other permanent operations like instancing) before calling update_abstractmethods, I'm leaning closer and closer to an automatic solution as proposed by Bar and Reymond.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/abc.py
for name in getattr(scls, '__abstractmethods__', ()):
value = getattr(cls, name, None)
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)

@gvanrossum gvanrossum Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Well, I disagree with the check for empty __subclasses__(), and I think in Irit's last example the right thing to do is to call abc.update_abstractmethods(A) and then abc.update_abstractmethods(C).

Note that it's not technically true that the abstractness cannot change once subclassed -- it may be undocumented but one could just write A.__abstractmethods__ = ().

FWIW I do agree that the code should assume that the abstractness of the bases is correct. If some base lies about it, well, Garbage In, Garbage Out.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_abc.py

B()
self.assertEqual(B.__abstractmethods__, set())

@gvanrossum gvanrossum Oct 4, 2020

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

@iritkatriel What does that test case accomplish other than showing that FooABC is now in an inconsistent state?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
ben avrahami added 2 commits October 5, 2020 01:36
ben avrahami
update_abstractmethods doesn't care about subclasses for now
29dba37
ben avrahami
doc fixes
779e6cf
gvanrossum
gvanrossum reviewed Oct 5, 2020
View reviewed changes

@gvanrossum gvanrossum left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I just don't care enough about the purely hypothetical scenario you're describing to want to complicate this function. The automatic solution also has its problems (to begin, it would require C code).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Doc/library/abc.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/abc.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/abc.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum

gvanrossum commented Oct 5, 2020

Copy link
Copy Markdown
Member

Just noted this:

@ericvsmith

My only real concern is that dataclasses now imports abc.

I looked into this, dataclasses already imports functools which already imports abc, so there's no new module being loaded. Also, abc.py is pretty compact and backed by C code -- most of the Python code is in _py_abc.py which is only loaded if _abc (the C code) cannot be loaded for some reason.

So I don't think there's a worry. @ericvsmith, all your other comments seem to have been taken care of this -- can you sign off? (Unless you want to jump into the debate about subclasses.)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ericvsmith

ericvsmith commented Oct 5, 2020

Copy link
Copy Markdown
Member

@gvanrossum: Agreed about importing abc. I'll let you and the others work out the subclass issue.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ericvsmith ericvsmith removed the awaiting change review label Oct 5, 2020
bentheiii and others added 3 commits October 5, 2020 12:56
@bentheiii @gvanrossum
Update Doc/library/abc.rst
eea97f4
Co-authored-by: Guido van Rossum <[email protected]>
@bentheiii @gvanrossum
Update Lib/abc.py
8eec6c2
Co-authored-by: Guido van Rossum <[email protected]>
@bentheiii @gvanrossum
Update Lib/abc.py
5698153
Co-authored-by: Guido van Rossum <[email protected]>
@bentheiii

bentheiii commented Oct 5, 2020

Copy link
Copy Markdown
Contributor Author

Of course, I defer judgement to the dev team, but I'd be remiss if I didn't reflect the implications of this behavior:

When this behavior is implemented, it will effectively split all class decorators into one of three categories:

  1. ABC-agnostic, who don't consider abstract superclasses as a use case. total_ordering will be of this category.
  2. ABC-aware, subclass-agnostic, who accept the possibility of an abstract superclass, and thus call update_abstractmethods. dataclass will be of this of this category, as well as, update_abstractmethods, techinally.
  3. ABC-aware, subclass-aware, who accept the possibility of an abstract superclass, and call update_abstractmethods, and also accept possibility of it having subclasses, so they also call update_abstractmethods on its subclasses (perhaps even recursively).

An important consequence is, that decorators can only be reliably stacked on top of each other only if they are of the same category. And while update_abstractmethods can be used to adapt a category 1 decorator to category 2, there are no simple means to adapt to category 3.

Of course, the mere existence of a decorator of the third category is a case so far fetched that I doubt it even exists across all python code. This is a valid reason to discount it, but the implication is that if were to ever exist, it could not be used with virtually any other decorator.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ericvsmith
ericvsmith dismissed their stale review October 5, 2020 14:14

Importing abc is not a concern: it will already be imported via functools.

gvanrossum
gvanrossum approved these changes Oct 5, 2020
View reviewed changes

@gvanrossum gvanrossum left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

You can consider us amply warned.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-bot bedevere-bot added the awaiting merge label Oct 5, 2020
@gvanrossum

gvanrossum commented Oct 5, 2020

Copy link
Copy Markdown
Member

@rhettinger any final word? @iritkatriel any comments?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@iritkatriel

iritkatriel commented Oct 5, 2020

Copy link
Copy Markdown
Member

@rhettinger any final word? @iritkatriel any comments?

A very pedantic one, which should not block this PR being merged. The doc says "It does not update any subclasses", but no test asserts this.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

bharel
bharel approved these changes Oct 6, 2020
View reviewed changes
@gvanrossum
gvanrossum merged commit bef7d29 into python:master Oct 6, 2020
@bedevere-bot bedevere-bot removed the awaiting merge label Oct 6, 2020
@gvanrossum

gvanrossum commented Oct 6, 2020

Copy link
Copy Markdown
Member

Thanks! All merged.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

shihai1991 added a commit to shihai1991/cpython that referenced this pull request Oct 9, 2020
@shihai1991
Merge remote-tracking branch 'origin/master' into bpo_39337_new_2
a69eef8
* origin/master: (147 commits)
  Fix the attribute names in the docstring of GenericAlias (pythonGH-22594)
  bpo-39337: Add a test case for normalizing of codec names (pythonGH-19069)
  bpo-41557: Update Windows installer to use SQLite 3.33.0 (pythonGH-21960)
  bpo-41976: Fix the fallback to gcc of ctypes.util.find_library when using gcc>9 (pythonGH-22598)
  bpo-41306: Allow scale value to not be rounded (pythonGH-21715)
  bpo-41970: Avoid test failure in test_lib2to3 if the module is already imported (pythonGH-22595)
  bpo-41376: Fix the documentation of `site.getusersitepackages()` (pythonGH-21602)
  Revert "bpo-26680: Incorporate is_integer in all built-in and standard library numeric types (pythonGH-6121)" (pythonGH-22584)
  bpo-41923: PEP 613: Add TypeAlias to typing module (python#22532)
  Fix comment about PyObject_IsTrue. (pythonGH-22343)
  bpo-38605: Make 'from __future__ import annotations' the default (pythonGH-20434)
  bpo-41905: Add abc.update_abstractmethods() (pythonGH-22485)
  bpo-41944: No longer call eval() on content received via HTTP in the UnicodeNames tests (pythonGH-22575)
  bpo-41944: No longer call eval() on content received via HTTP in the CJK codec tests (pythonGH-22566)
  Post 3.10.0a1
  Python 3.10.0a1
  bpo-41584: clarify when the reflected method of a binary arithemtic operator is called (python#22505)
  bpo-41939: Fix test_site.test_license_exists_at_url() (python#22559)
  bpo-41774: Tweak new programming FAQ entry (pythonGH-22562)
  bpo-41936. Remove macros Py_ALLOW_RECURSION/Py_END_ALLOW_RECURSION (pythonGH-22552)
  ...
xzy3 pushed a commit to xzy3/cpython that referenced this pull request Oct 18, 2020
@bentheiii @xzy3-cdc
bpo-41905: Add abc.update_abstractmethods() (pythonGH-22485)
9f11c9d
This function recomputes `cls.__abstractmethods__`.
Also update `@dataclass` to use it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@iritkatriel iritkatriel iritkatriel left review comments
@rhettinger rhettinger rhettinger left review comments
@gvanrossum gvanrossum gvanrossum approved these changes
@ericvsmith ericvsmith Awaiting requested review from ericvsmith
+1 more reviewer
@bharel bharel bharel approved these changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

8 participants

@bentheiii @the-knights-who-say-ni @bedevere-bot @ericvsmith @gvanrossum @iritkatriel @bharel @rhettinger
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.