Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to annotation type?
#1
Question 
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.)
Reply
#2
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.
Reply
#3
(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??
Reply
#4
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')
Reply
#5
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)
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Decorators @ annotation drcl 3 453 Feb-24-2024, 06:12 AM
Last Post: Gribouillis
  How to extract and label audio using timestamp annotation Mergorine 0 3,466 Nov-30-2020, 04:44 PM
Last Post: Mergorine
  Type hinting - return type based on parameter micseydel 2 2,526 Jan-14-2020, 01:20 AM
Last Post: micseydel
  Function Annotation got NameError: name 'xxx' is not defined Lance 6 5,360 Oct-23-2019, 03:13 AM
Last Post: Lance

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020