Python Forum
How to annotation type? - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: General Coding Help (https://python-forum.io/forum-8.html)
+--- Thread: How to annotation type? (/thread-40056.html)



How to annotation type? - gamecss - May-25-2023

There is a simple function.
def func(a: AnyStr, *b: AnyStr) -> Tuple[Optional[AnyStr], AnyStr]:
    for _b in b:
      if a.startswith(_b):
        return (_b, a.removeprefix(_b))
    return (None, a)
Then, I added the ability to convert AValue instances (_b) to str or bytes for some reason.
@dataclass
class AValue:
    value: str
val = AValue("abcd")
def func(a: AnyStr, *b: AnyStr | AValue) -> Tuple[Optional[AnyStr | AValue], AnyStr]:
    for _b in b:
      if isinstance(_b, AValue):
        # ..... some code convert _b into AnyStr (_tb) that match a
      else:
        _tb = _b
      if a.startswith(_tb):
        return (_b, a.removeprefix(_tb))
    return (None, a)
However, type annotations are not perfect. If I write result = func('abcd', 'a', 'b'), type checkers still think result[0] maybe Optional[AnyStr | AValue]. How to annotate result[0] that type checkers will think it's Optional[AnyStr] if there is only AnyStr in b?
a, _ = func("Abcd", AValue("A"), "b", b"c")
reveal_type(a)  # Optional[AValue | str]
b, _ = func("Abcd", "A", b"b")
reveal_type(b)  # Optional[str]
I tried this:
T = TypeVar('T', bytes, str, AValue)

def func(a: AnyStr, *b: T) -> Tuple[Optional[T], AnyStr]:
    for _b in b:
      if isinstance(_b, AValue):
        # ..... some code convert _b into AnyStr (_tb) that match a
      else:
        _tb = _b
      if a.startswith(_tb):
        return (_b, a.removeprefix(_tb))
    return (None, a)
It's not perfect that type checkers will think func("abcd", AValue("efgh"), b"ijkl") (b"ijkl"'s type should be same as "abcd") is ok.
What should I do? (Sorry for my poor english.)


RE: How to annotation type? - snippsat - May-25-2023

Example with first function,the Tuple[Optional is not needed if use Python 3.10 or newer.
There a two return,so need to address both in Return type.
# pre.py
from typing import AnyStr

def func(a: AnyStr, *b: AnyStr) -> tuple[AnyStr, AnyStr] | tuple[None, AnyStr]:
    for _b in b:
        if a.startswith(_b):
            return _b, a.removeprefix(_b)
    return None, a

a = 'test123'
b = 'test1'
print(func(a, b))
Test using mypy.
G:\div_code\hex
λ mypy pre.py
Success: no issues found in 1 source file

G:\div_code\hex
λ python pre.py
('test1', '23')
So now it work for string and bytes.

Don't know about your converting in last part,would add that in new function that if needed.
Will get less readable if clutter to much in and the addition difficulty to get Type hint to work together.


RE: How to annotation type? - gamecss - May-25-2023

(May-25-2023, 10:47 AM)snippsat Wrote: Example with first function,the Tuple[Optional is not needed if use Python 3.10 or newer.
There a two return,so need to address both in Return type.
# pre.py
from typing import AnyStr

def func(a: AnyStr, *b: AnyStr) -> tuple[AnyStr, AnyStr] | tuple[None, AnyStr]:
    for _b in b:
        if a.startswith(_b):
            return _b, a.removeprefix(_b)
    return None, a

a = 'test123'
b = 'test1'
print(func(a, b))
Test using mypy.
G:\div_code\hex
λ mypy pre.py
Success: no issues found in 1 source file

G:\div_code\hex
λ python pre.py
('test1', '23')
So now it work for string and bytes.

Don't know about your converting in last part,would add that in new function that if needed.
Will get less readable if clutter to much in and the addition difficulty to get Type hint to work together.

Because I'm trying to make a tcp packet parse system that can parse both str and bytes. All available packet identification is a AValue (for example). If user want to parse a packet, he had to encode himself.
So is it possible??


RE: How to annotation type? - snippsat - May-25-2023

Not knowing enough about the task and input,but str and bytes can never mix so can eg force convert back and forth.
Now before parse with func do not need AnyStr as ensure_str will always make it str.
Then later can eg convert to bytes again.
# pre.py
def func(a: str, *b: str) -> tuple[str, str] | tuple[None, str]:
    for _b in b:
        if a.startswith(_b):
            return _b, a.removeprefix(_b)
    return None, a

def ensure_str(a, b):
    try:
        a = a.decode()
        b = b.decode()
        return a, b
    except AttributeError:
        return a, b

def ensure_bytes(res: tuple) -> tuple:
    return tuple([s.encode() for s in res])

if __name__ == '__main__':
    a = b'test123'
    b = b'test1'
    a_1, b_1 = ensure_str(a, b)
    print(func(a_1, b_1))
    res = func(a_1, b_1)
    print(ensure_bytes(res))
Test.
G:\div_code\hex
λ mypy pre.py
Success: no issues found in 1 source file

G:\div_code\hex
λ python pre.py
('test1', '23')
(b'test1', b'23')



RE: How to annotation type? - gamecss - Jul-27-2023

I finally made it using typing.overload:
@overload
def func(a: AnyStr, *b: AnyStr) -> Tuple[Optional[AnyStr], AnyStr]:
    ...

@overload
def func(a: AnyStr, *b: AValue) -> Tuple[Optional[AValue], AnyStr]:
    ...

@overload
def func(a: AnyStr, *b: Union[AValue, AnyStr]) -> Tuple[Union[AValue, AnyStr, None], AnyStr]:
    ...

def func(a: AnyStr, *b: Union[AValue, AnyStr]) -> Tuple[Union[AValue, AnyStr, None], AnyStr]:
    for _b in b:
      if isinstance(_b, AValue):
        # ..... some code convert _b into AnyStr (_tb) that match a
      else:
        _tb = _b
      if a.startswith(_tb):
        return (_b, a.removeprefix(_tb))
    return (None, a)