Is there an update on Typing.Optional on python 3.9 or am I doing something wrong?

大城市里の小女人 提交于 2021-01-27 13:41:26

问题


While I was coding for help command for my bot in Discord.py, This function was not working as I expected.

def syntax(command):
    cmd_and_aliases = '|'.join([str(command), *command.aliases])
    params = []
    for key, value in command.params.items():
        if key not in ('self', 'ctx'):
            params.append(f'[{key}]' if 'NoneType' in str(value) else f'<{key}>')
    params = ' '.join(params)
    return f'`{cmd_and_aliases} {params}`'

What I wanted for this function was, returning and printing names and aliases to call up an discord command and what parts are necessary and what parts are not to run that command properly. As the Optional[str] in Typing can be translated as Union[str, None], What I wanted to get was what their could be and if it can be None, then it will be appended to the params list wrappend in square bracket, or else in angle bracket.

`name|aliases <necessary part> [optional part]`

This is what the function would originally would have returned, but both the necessary part and optional part are shown as wrapped in angle brackets.

@command(name='slap', aliases=['hit'])
async def slap_member(self, ctx, member: Member, *, reason: Optional[str] = 'for no reason'):

For example, for this command the result would have been like below.

slap|hit <Member> [reason]

But what I ended was getting was:

slap|hit <Member> <reason>.

Is there something I have done wrong or is there any update that would have made this function broken? If there is something I did wrong, please help me by showing what I can do otherwise.


回答1:


The "Why"

This behavior has indeed changed in 3.9; I believe this commit specifically is the cause. It looks like these changes begun in this commit, but I believe the manifestation of your particular issue is due to the former.

I'm not deeply familiar with the plumbing of the typing module, but from what I can gather, the representation of Optional is now special-cased and returns itself as-is, i.e. typing.Optional[x] instead of "expanding" to typing.Union[x, NoneType]. They still have the same "type", however, but the important bit here is that their "type" has changed to _UnionGenericAlias, whose __repr__ is:

def __repr__(self):
    args = self.__args__
    if len(args) == 2:
        if args[0] is type(None):
            return f'typing.Optional[{_type_repr(args[1])}]'
        elif args[1] is type(None):
            return f'typing.Optional[{_type_repr(args[0])}]'
    return super().__repr__()

Possible Solutions

First, I'd like to point out that you can use command.clean_params instead of command.params (which strangely isn't documented, from what I can tell). It functions the same, except it automatically excludes self and ctx so you don't have to check for it. I'll be using it in my solutions below.

I came up with two possible solutions. The first is quite close to what you have, in that we do a simple string check for typing.Optional:

for key, value in command.clean_params.items():
    optional = str(value).startswith('typing.Optional')
    params.append(f'[{key}]' if optional else f'<{key}>')

Now I personally am not fond of string checks when it comes to determining types (Generics in this case), so I would opt for something a bit different. As described in PEP 585, we can do runtime introspection on Generics, so let's take advantage of that.

for key, value in command.clean_params.items():
    optional = value.__origin__ is Union and value.__args__[1] is type(None)
    params.append(f'[{key}]' if optional else f'<{key}>')

Another benefit of this approach is that it will work in Python 3.8 (and I think 3.7 as well), whereas the string checking version will obviously only work in 3.9. You could leverage sys.version_info and have multiple string checks, but that seems like too much hassle in comparison.



来源:https://stackoverflow.com/questions/65771191/is-there-an-update-on-typing-optional-on-python-3-9-or-am-i-doing-something-wron

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!