Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-112319: Allow special protocol members #112340

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

randolf-scholz
Copy link
Contributor

@randolf-scholz randolf-scholz commented Nov 23, 2023

@bedevere-app

This comment was marked as duplicate.

@bedevere-app

This comment was marked as duplicate.

Comment on lines +1864 to +1868
cls.__protocol_attrs__ = (
_get_local_protocol_members(namespace) | _get_parent_members(cls)
)
# local_attrs = _get_local_protocol_members(namespace)
# cls.__protocol_attrs__ = local_attrs.union(*map(_get_cls_members, bases))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure which variant is better...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only difference between _get_cls_members and _get_parent_members is that the former checks cls.__mro__[:-1] and the latter only cls.__mro__[1:-1].

I would expect the _get_parent_members to be better for deeply inherited classes, and the _get_cls_members to be better for classes with many disjoint parents.

Lib/typing.py Outdated Show resolved Hide resolved
Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

haven't looked deeply yet, just a few comments from an initial skim:

Comment on lines -1678 to +1686
'__init__', '__module__', '__new__', '__slots__',
'__subclasshook__', '__weakref__', '__class_getitem__',
'__match_args__',
'__module__', '__slots__', '__match_args__', '__qualname__',
})
_SPECIAL_CALLABLE_NAMES = frozenset({
'__init__', '__new__', '__subclasshook__','__class_getitem__', '__weakref__',
})

# These special attributes will be not collected as protocol members.
EXCLUDED_ATTRIBUTES = _TYPING_INTERNALS | _SPECIAL_NAMES | {'_MutableMapping__marker'}
EXCLUDED_MEMBERS = EXCLUDED_ATTRIBUTES | _SPECIAL_CALLABLE_NAMES
Copy link
Member

Choose a reason for hiding this comment

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

Please keep the diff as minimal as possible. As far as I can tell, splitting the _SPECIAL_NAMES frozenset into a _SPECIAL_NAMES set and a _SPECIAL_CALLABLE_NAMES set shouldn't make any difference here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For my solution to work, I need both, because for things defined directly inside the protocol body, I only exclude EXCLUDED_ATTRIBUTES, but not _SPECIAL_CALLABLE_NAMES. (Hence, we can test for __class_getitem__). However, for general classes we need to exclude it (otherwise test.test_typing.ProtocolTests.test_collections_protocols_allowed fails).

See the difference in function body between _get_local_members and _get_local_protocol_members.

Lib/typing.py Outdated Show resolved Hide resolved
Lib/typing.py Outdated Show resolved Hide resolved
Lib/typing.py Show resolved Hide resolved
Lib/typing.py Outdated Show resolved Hide resolved
Lib/typing.py Outdated Show resolved Hide resolved
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
@bedevere-app

This comment was marked as duplicate.

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
@bedevere-app

This comment was marked as duplicate.

Lib/test/test_typing.py Outdated Show resolved Hide resolved
Lib/test/test_typing.py Show resolved Hide resolved
Lib/test/test_typing.py Outdated Show resolved Hide resolved
Lib/test/test_typing.py Outdated Show resolved Hide resolved
@bedevere-app

This comment was marked as duplicate.

@bedevere-app

This comment was marked as duplicate.

@bedevere-app

This comment was marked as duplicate.

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
@bedevere-app

This comment was marked as duplicate.

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
@bedevere-app

This comment was marked as resolved.

attrs |= getattr(base, "__protocol_attrs__", set())
else:
attrs |= _get_local_members(base.__dict__)
return attrs


def _get_protocol_attrs(cls):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

After the modifications here, _get_protocol_attrs is not used anymore, and it will even give some wrong results (e.g. if the Protocol defines __class_getitem__). So maybe just delete it?

Copy link
Member

Choose a reason for hiding this comment

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

If it's not used anywhere anymore, then yes, it should be deleted. It's an undocumented, private function; if anybody's using it and their code is broken by us deleting it, that's on them. I can't find any uses of it on grep.app anyway, so I don't think anybody will have their code broken by us deleting it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

@runtime_checkable Protocol doesn't check if __class_getitem__ exists.
2 participants