#type-hinting

1 messages ยท Page 64 of 1

trim tangle
#

and the last part (surname I assume)?

oblique urchin
#

zell-stra

#

and yes that's my surname

trim tangle
#

thank u

#

my mind is at rest now

oblique urchin
mortal fractal
#

@oblique urchin removing the callable check fails 10 tests

7 tests: checking that an int literal isn't a valid type
2 tests: checking that a list of tuples isn't a valid tuple for typeddict/namedtuple *kwargs
1 test: I don't understand yet

trim tangle
#

is that mypy?

mortal fractal
#

typing.py itself

trim tangle
#

ah

mortal fractal
#

@oblique urchin the 10th I think might be a bug in cpython combined with a test case that was testing a feature not actually implemented and was only working by accident

#

I'm still investigating whether or not the cpython behavior is buggy, but I'm confident the testcase is buggy either way

mortal fractal
rough sluiceBOT
#

Lib/test/test_typing.py lines 227 to 230

def test_cannot_subclass_vars(self):
    with self.assertRaises(TypeError):
        class V(TypeVar('T')):
            pass```
mortal fractal
#
class Base:
    def __init__(self, *args):
        for arg in args:
            print(repr(arg))
        pass

b = Base()

class D(b):
    pass
#

this outputs

'D'
(<__main__.Base object at 0x7f38e9c44730>,)
{'__module__': '__main__', '__qualname__': 'D'}
oblique urchin
mortal fractal
#

so this raises a TypeError because TypeVar.__init__ is called with that output as *args, and those don't pass the callable() check in _type_check, which TypeVar runs on the *args passed to it because it expects them to be constraints

#

so this test is testing that collectively each of these

(<__main__.Base object at 0x7f38e9c44730>,)
{'__module__': '__main__', '__qualname__': 'D'}```
do not pass `_type_check`
#

which is why removing callable() causes that testcase to no longer raise a TypeError

#

it seems highly unlikely this is the codepath the author of the testcase thought they were testing

#

and frankly that cpython behavior is bizarre, remove *args from __init__ and the D class initialization error is different whether or not you set the metclass=type

oblique urchin
#

yes, I guess it's because creating a subclass of X basically just does type(X)(name, bases, ns)

mortal fractal
#

nope

#

that raises a different error

oblique urchin
#

this passes without errors for example class X(map(len, [])): pass

mortal fractal
#

which is why I thought could be a cpython bug

#

type("D", (b,), {})

oblique urchin
# mortal fractal that raises a different error

it doesn't? ```>>> type(TypeVar("T"))("T", (TypeVar("T"),), {})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/jelle/py/cpython/Lib/typing.py", line 816, in init
self.constraints = tuple(_type_check(t, msg) for t in constraints)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/jelle/py/cpython/Lib/typing.py", line 816, in <genexpr>
self.constraints = tuple(_type_check(t, msg) for t in constraints)
^^^^^^^^^^^^^^^^^^^
File "/Users/jelle/py/cpython/Lib/typing.py", line 184, in _type_check
raise TypeError(f"{msg} Got {arg!r:.100}.")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: TypeVar(name, constraint, ...): constraints must be types. Got (~T,).

mortal fractal
#

I mean in the toy code with D and stuff; I thought type("D", (b,), {}) would be same as
class D(b): pass (when the *args is removed), but they raise different runtime errors

oblique urchin
#

right, because it delegates to the metaclass of b in order to create the type

mortal fractal
#

oh, what's the correct way to do it the same then

#

I think I've confused myself on the type() dynamic creation then I don't do it often

oblique urchin
mortal fractal
#

I see, so type(b)("D", (b,), {})

#

okay I understand now

#

anyway, I really doubt this is what the testcase was intended to test, and we understand it now anyway

oblique urchin
tranquil turtle
#

!doc types.new_class

rough sluiceBOT
#

types.new_class(name, bases=(), kwds=None, exec_body=None)```
Creates a class object dynamically using the appropriate metaclass.

The first three arguments are the components that make up a class definition header: the class name, the base classes (in order), the keyword arguments (such as `metaclass`).

The *exec\_body* argument is a callback that is used to populate the freshly created class namespace. It should accept the class namespace as its sole argument and update the namespace directly with the class contents. If no callback is provided, it has the same effect as passing in `lambda ns: None`.

New in version 3.3.
mortal fractal
#

so:
removing the callable check fails 10 tests:

7 tests: checking that an int literal isn't a valid type
2 tests: checking that a list of tuples isn't a valid tuple for typeddict/namedtuple *kwargs
1 test: TypeVar instance subclassing testcase that (presumably accidentally) tested that some literals and dict instances weren't callable. I agree with you that this can/should be fixed with the __mro_entries__

rough sluiceBOT
#

Lib/test/test_typing.py lines 4262 to 4263

with self.assertRaises(TypeError):
    NamedTuple('Emp', fields=[('name', str), ('id', int)])```
mortal fractal
#

this isn't obvious what's happening; it's expecting something like name=str in the kwargs

#

so NamedTuple is passing the RHS to _type_check

#

so this test raises a TypeError because the literal [('name', str), ('id', int)] doesn't pass callable()

oblique urchin
mortal fractal
#

there were 2 test cases like that, and that's it

#

@oblique urchin okay I propose

  1. I open a bpo bug about the TypeVar testcase and propose adding mro_entries to TypeVar to fix the testcase/give it a better error; the testcase clearly isn't testing what it thinks it is

  2. open a bpo bug and/or typing ML discussion on if we can remove the callable check in type_check, pointing out that it's :
    a) both a source of existing bugs in cpython that are hard to catch (can require both introspection or uncommon stuff like using Annotated) and likely to continue to be the cause of bugs in the future
    b) doesn't make any particular technical sense to require
    c) makes an awkward (and very non-obvious) requirement to add __call__ method to all typeform classes

and then bring up the stuff it affects

mortal fractal
rough sluiceBOT
#

Lib/typing.py line 352

def __init_subclass__(self, /, *args, **kwds):```
mortal fractal
#

not that it changes anything

oblique urchin
#

yeah the first argument to __init_subclass__ should normally be called cls

mortal fractal
#

maybe I make PR without bpo for this as a typo fix

#

@oblique urchin skimming the source of typing.py I can identify other things that are bugged/impossible only because of the callable check

#
from typing import Literal, Annotated, ForwardRef, TypeVar, ParamSpec, Callable
import logging

T = TypeVar('T')
P = ParamSpec('P')

def add_logging(f: Callable[P, T]) -> Callable[P, T]:
   '''A type-safe decorator to add logging to a function.'''
   def inner(*args: Annotated[P.args, "meta"], **kwargs: P.kwargs) -> T:
       logging.info(f'{f.__name__} was called')
       return f(*args, **kwargs)
   return inner

@add_logging
def add_two(x: float, y: float) -> float:
   '''Add two numbers together.'''
   return x + y

can't annotate paramspec; why? ParamSpecArgs isn't callable()

mortal fractal
#

this is jsut what I found from a very quick skim auditing typing.py for callable() compatibility

#

I've probably missed stuff

#

I'll make issues for stuff

mortal fractal
#

@oblique urchin would we really need the stuff gobot did or can't we just raise an error?

oblique urchin
soft matrix
#

you could probably just have raise TypeError right?

mortal fractal
#

def mro_entries:
raise

soft matrix
#

you dont need a "how to fix this error"

#

yeah that should work

oblique urchin
#

oh yes, that's right. you don't need the indirection here

#

for NewType that helps give a better error but that's not necessary here since there's no obvious thing to suggest that the user meant to do

blazing nest
#

Oh no ahhh, Python 3.5 support dropped in 0.920 ๐Ÿ˜”. That doesn't support Python 3.10

little hare
#

?

#

wdym blue

blazing nest
#

Oops, I am talking about Mypy

soft matrix
#

cython issues i assume

blazing nest
#

yeah

#

I want to use Paramspec for some of the shadows

little hare
#

ah

blazing nest
#

# May be a bit hard to read but essentially means:
# > Returns a callable that takes another callable with these parameters and *some*
# > return value, then returns another callable with the same parameters but the
# > the return type is the previous 'type' parameter.
def returns(__type: _T) -> Callable[[Callable[_P, object]], Callable[_P, _T]]: ...
@overload
def cast(__t: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> _T: ...
#

These are my primary use-cases right now

#

I will probably need to drop it, or use version_info checks.

oblique urchin
#

why are you still supporting 3.5?

blazing nest
#

Cython supports everything from 2.7 to 3.10 ๐Ÿฅด

soft matrix
#

why dont you do sys.version_info checks?

blazing nest
mortal fractal
#

@oblique urchin the self->cls thing seems like a silly thing to make a PR for, would it be uncouth to include it in another typing PR?

oblique urchin
blazing nest
#

Mypy only errors on its usage though (when I put it inside Callable) ๐Ÿค”

mortal fractal
soft matrix
#

no you shouldnt do

brisk heart
#

any way to do this properly

T = TypeVar("T")
S = TypeVar("S", bound=SomethingSubscritable)

def func(cls: type[S] = SomethingSubscritable) -> Callable[[T], S[T]]:
    return lambda x: cls(x)
#

TypeVar "Type[S@func]" is not subscriptable

soft matrix
#

wait till HKT is a thing?

trim tangle
#

Can you show an example of how you'd use it?

hearty shell
#

Is SomethingSubscritable Generic?

brisk heart
#

yeah

#

I wanna return a subscripted typevar

#

somehow

trim tangle
#

That feature is called "Higher Kinded Types"

#

and Python's type system doesn't have it

mortal fractal
brisk heart
#

๐Ÿ’€

#

you gotta be kidding me

mortal fractal
#

seems clearly intended to be a type

brisk heart
#

any alternative to keep that pattern?

mortal fractal
#

well, that's definitely something someone might want runtime introspection for I guess

soft matrix
trim tangle
#

@brisk heart Can you show an example of how you actually use it? Maybe there's an alternative, simpler solution?

soft matrix
#

its something people have wanted for a while unsurprisingly

brisk heart
#

yup but it will be a bit different to the example

trim tangle
#

sure

brisk heart
#
# user uses it like this
@deco(metadata="goes here", cls=Foo)
def callback(self, thing: Foo, interaction: Interaction):
    ...

# the actual types if fully annotated
@deco(metadata="goes here", cls=Foo)
def callback(self, thing: Foo[Self], interaction: Interaction):
    ...
#

kinda like this I suppose

trim tangle
#

what is Foo?

#

what are you doing, in general?

brisk heart
#

the library provides a standard implementation of a component

blazing nest
brisk heart
#

users should have the option to change this component to their will

#

so as long as they subclass (or possibly implement a protocol?) they can use their own implementations

trim tangle
#

Can you show a more complete example?

oblique urchin
#

and maybe 3.5 too, forgot when we dropped 3.5 support

brisk heart
#

sure lemme dig something up

soft matrix
#

3.5 i think sort of works but not for generics off the top of my head (on 3.10.x)

brisk heart
#
# before
class BasicView(miru.View):
    @miru.button(label="Click me!", style=hikari.ButtonStyle.SUCCESS)
    async def basic_button(self, button: miru.Button, ctx: miru.Context) -> None:
        await ctx.respond("You clicked me!")

    @miru.button(label="Stop me!", style=hikari.ButtonStyle.DANGER)
    async def stop_button(self, button: miru.Button, ctx: miru.Context) -> None:
        self.stop() 
# after
class BasicView(miru.View):
    @miru.button(cls=BetterButton, label="Click me!", style=hikari.ButtonStyle.SUCCESS)
    async def basic_button(self, button: BetterButton, ctx: miru.Context) -> None:
        await ctx.respond(f"You clicked me {button.total_clicks} times!")

    @miru.button(label="Stop me!", style=hikari.ButtonStyle.DANGER)
    async def stop_button(self, button: miru.Button, ctx: miru.Context) -> None:
        self.stop() 
#

maybe something like this?

blazing nest
brisk heart
#

not mine, I'm just a contributor

soft matrix
oblique urchin
grave fjord
#

You can get ParamSpec in an if TYPE_CHECKING on any version of Python afaik

trim tangle
mortal fractal
brisk heart
#

well because BetterButton can be subscripted with the view

#

so if you subclass the button you can just use it as a standalone thing

#

and reference the parent view with self.view

trim tangle
#

ah, so buttons are parametrized with the view

#

then there's no way to do it, no

brisk heart
#

hmmm, won't mypy complain that it's not entire?

trim tangle
#

?

brisk heart
#

like it always complains on strict mode that I don't provide the subscript

trim tangle
#

that seems correct to me

brisk heart
#

I don't want to be bitten in the arse later

#

It absolutely is correct but now idk if it won't complain about the same thing

#

It does not currently but I can't tell if it will stay like that

soft matrix
blazing nest
#

Runtime does not matter to me as this is in a stub-file.

oblique urchin
brisk heart
#

I have the exact same problem on 3.9 btw

#

I just keep it in TYPE_CHECKING and it kinda just works out

blazing nest
oblique urchin
blazing nest
#

venv35 is the venv (standard module) I created for my Python 3.5 installation

soft matrix
#

is it in mypy/typeshed though?

blazing nest
#

Ooops, that's the one Mypy specifies as a dependency. Here's the one in Mypy's vendored typeshed:

oblique urchin
blazing nest
oblique urchin
#

do you really need to care about hypothetical users trying to type check their Cython code under Python 3.5?

blazing nest
#

Yeah that's what I am unsure of. Seeing as there is no other option available then I'll ask about it.

#

It's pretty reasonable to ask someone to use the latest update of their type checker, even though the code it's meant to run isn't gonna be under that version I'd say.

oblique urchin
#

you can also just type ignore the definition where we use ParamSpec

#

that's what we did in typeshed for a while

#

mypy will just fall back to Any for those parameters

blazing nest
#

Wouldn't I loose type information for all versions then?

soft matrix
#

no only for the versions where it doesnt work with mypy

blazing nest
#

Ah, the type: ignore comment would be on the ParamSpec import?

soft matrix
#

no it would be on the def returns line

blazing nest
#

Ah okay, gotcha.

hearty shell
#

Honestly I have no idea what higher-kinded type vars are -- my eyes glaze over when I hear that kind of talk. :-)

#

Lmao, same, Guido

#

x)

mortal fractal
little hare
#

lol why

mortal fractal
#

I think macOS tests are just failing right now on the main cpython branch in general (maybe inconsistently); heh

mortal fractal
# little hare lol why

it retriggers the tests, because the azure pipelines thing was from before the appropriate tags were added

little hare
#

ah

mortal fractal
#

@oblique urchin I found another callable() bug

rough sluiceBOT
#

Lib/test/test_types.py lines 834 to 838

def test_union_parameter_substitution_errors(self):
    T = typing.TypeVar("T")
    x = int | T
    with self.assertRaises(TypeError):
        x[42]```
mortal fractal
#

if you change 42 to int then it no longer raises a typeerror

oblique urchin
#

x[int] should work

mortal fractal
#

ah, right

#

so this probably needs a real implementation not an incidental one

#

you know, if we wanted to prevent int literals we could easily just, you know, prevent int literals in _type_check instead

#

added to the bug this additional test

#

I'll wait for feedback before doing anything else

#

maybe just the mro thing

#

yeah some socket-related test is just failing on main periodically in general right now

#

I'm still not sure if there are people that would argue you shouldn't be able to annotate paramspec

#

to me it seems like you "obviously" should be able to

mortal fractal
#

Looking at cpython main I'm not even sure what change if any caused test to start failing

dire bobcat
#

anyone got an example of the most perfect code in the world?

#

like a github project or smtihng

hasty hull
#

That is impossible to define lol

dire bobcat
#

๐Ÿ˜ก

#

you know what i mean

#

like

#

most efficint

#

most neat

#

most pep-8 compliant

#

etc

mortal fractal
#

It looks like maybe the problem with the test machines is resolved if you need to retrigger the tests or maybe it's fine

mortal fractal
#

I will try to audit typing.py for additional bugs of that sort later

#

Since I found the bug very quickly by just directly examining the code I might be able to find more

#

And if we don't remove callable we will need to fix all of them anyway

And they would be worth test cases

rustic gull
#

What's the difference between typing.Iterable and typing.Iterator? pithink

mortal fractal
#

Also they're both deprecated

rustic gull
#

Docs say they derive from abc lemon_glass Iterable has iter but Iterator has iter and next, yeah

mortal fractal
#

You want the versions in collections.abc

rustic gull
#

i nees some help i am getting an error whome should i ask ?

trim tangle
#

hey, new pyright release

Enhancement: Allow "--watch" to be used in conjunction with "--outputjson" command-line options.

#

I wonder if I could use this to speed up the playground, but sounds complicated

mortal fractal
#

cpython main is very broken right now

#

๐Ÿคฃ

#

whether or not all your tests pass is luck

#

it's not obvious to me what commit broke the tests

trim tangle
#

Initial commit

#

๐Ÿ”ฅ

brisk heart
#

I don't get this one at all

from typing import TypeVar

class Foo:
    ...

T = TypeVar("T", bound=Foo)

def func(cls: type[T] = Foo) -> T:
    return cls()

main.py:8: error: Incompatible default for argument "cls" (default has type "Type[Foo]", argument has type "Type[T]")
Found 1 error in 1 file (checked 1 source file)
#

pyright understands it tho

soft matrix
trim tangle
#

Did anyone encounter confusions betwee type aliases, TypeVar and NewType?

soft matrix
#

I was initially confused as to when you'd need a type alias to a normal (unsubscripted) type and why you wouldn't just use NewType

trim tangle
soft matrix
#

yeah

trim tangle
#

ah
yeah, there isn't much point to it

#

I have a coworker that does this, it's very annoying

soft matrix
#

surely that should be a new type

trim tangle
#

I don't see much need in NewType either to be quite honest

#

I guess it prevents one from mixing two strings together...

soft matrix
#

i think its alright in cases like this

trim tangle
#

(but you can also just do Email("not an email") in a module that doesn't own Email)

#

In Haskell they're useful because you can export the type Email but not export the constructor

#

I would rather have a proper Email object that does validation in its constructor, so that you can actually be sure that an Email object is valid

blazing nest
trim tangle
#

Isn?

#

ah

blazing nest
#

Or- for example meters vs. foot

trim tangle
blazing nest
#

Even though they're both integers, you cannot pass a meter where a method expects a foot.

trim tangle
#

I guess

#

I haven't dealt with stuff like that, maybe I'm just missing something

soft matrix
#

Well they atleast to me are useful as sudo phantom types

hasty hull
#

While the example I've used there is obvious there could be cases where it's not so obvious

#

This is also a niche use case

trim tangle
#

good point, values of different domains conceptually, but with the same representation at runtime

terse sky
#

I've found NewType pretty useful

#

A class can feel a bit boilerplatey for a single piece of data

#

You can still do validation, you'd just have a function that takes a string and returns an Email

trim tangle
#

yeah

#

I guess it's more of a tool to hint at silly errors/typos, rather than providing air tight safety (which is more like Haskell's newtype)

#

I don't think I've ever used NewType. Maybe I should try it

oblique urchin
#

we use it heavily for typed ids

terse sky
#

Yeah it's great for integers that serve as IDs and can't be mixed

#

We also use them for strings that essentially serve as IDs

#

It provides value with basically zero cost

#

With a class you'd have more cost in boilerplate so it makes you hesitate more

#

It also makes function signatures communicate waaaaay more information

hearty shell
#

One qol use of it I found was for working with external languages inside python code such as SQL or graphql, you can make a gql = NewType('gql', str) wrap your queries and not only you get that 'typed' but you can configure your IDE for syntax and completion inside the newtype

terse sky
#

How can you configure your IDE for that?

hearty shell
#

I configured it in pycharm some time ago, but it is somewhere in the section to add custom highlighting, let me see if I find it again

#

Should be under language injections

mortal fractal
#

@oblique urchin I would be okay with adding a type(typ) is int: raise in place of the not callable(typ): raise, but I don't think it should be any more complicated than that

#

And even that is stretching it I think

#

it makes a lot more technical sense than the callable check though

#

or check typ.__class__ directly, don't know if one method would be more preferred

oblique urchin
#

It doesn't make much sense to me to disallow int specifically

mortal fractal
#

In my head the justification is "types are not int indexing" line of argument, but overall I agree with you that I'd rather remove it all

oblique urchin
#

There is a risk that people get confused about list[int] vs. actual lists, but I'm not sure that risk is higher with ints

#

and besides list[] allows anything anyway

mortal fractal
#

I gave typing.py code another glance and there weren't any additional callable() issues I could find that immediately stood out other than the paramspec one I already identified

#

I haven't checked open typing PEP PRs or typing_extensions yet though

mortal fractal
#

@oblique urchin is sobolevn on irc or discord somewhere? I want to discuss a typing.py testing strategy with him casually if he would have me

soft matrix
#

this seems like a really good suggestion you dont wanna know how much stuff ive written has __call__ with raise NotImplementedError

mortal fractal
#

Please, leave a comment on the bpo with this :)

#

@oblique urchin an underlying problem not directly related to this is that typing.py is missing comprehensive test coverage of _type_check reachable from the external API. The big ways to reach this in general are usually with get_type_hints, but also Annotated now and a few weird ways. I know sobolevn has been working a lot on improving typing.py test coverage so I didn't know if he would have thoughts (or ideas) on how to increase coverage in this area

#

Or maybe interested himself in it

oblique urchin
mortal fractal
#

I didn't even know there was a core dev discord, like a private one for triagers and core?

oblique urchin
#

yes

soft matrix
#

you could probably email them and ask for them to accept your friend request ๐Ÿ˜Ž

mortal fractal
#

I will draft PRs soon

trim tangle
#

(or anyone else as well)

oblique urchin
trim tangle
#

thanks

#

smh not reading typing stuff while sleeping ๐Ÿ˜”

soft matrix
oblique urchin
trim tangle
#

ah

#

timezones ๐Ÿ˜” ๐Ÿ˜”

trim tangle
#

damn I'm so sorry man

#

you can use IPigeonMail in the meantime

soft matrix
#

this looks very nice from an initial glance

#

one completely unreasonable idea i just had would somehow be embedding pyright playground into this so you dont have to work with the screenshots and people can experiment with the code themselves

trim tangle
#

screenshots are definitely janky

acoustic thicket
#

you'd need a check for email being None too, right?

trim tangle
#

the type says it can't be None

#

oh

#

right

acoustic thicket
#

oh i meant the variable in the 2nd block

trim tangle
#

yeah

#

I see now

#

thank you

#

@acoustic thicket fixed

acoustic thicket
#

nice

#

looks good

soft matrix
#

does anyone here know why overloads on property dont work?

trim tangle
#

can you show an example?

soft matrix
#
from typing import Generic, overload, TypeVar

T = TypeVar("T")

class This(Generic[T]):
    @property
    @overload
    def something(self: "This[int]") -> None:
        ...
    @property
    @overload
    def something(self: "This[str]") -> bool:
        ...```something like this as far as im aware doesnt work
trim tangle
#

ah

soft matrix
#

but i might be wrong

trim tangle
#

If this has some complex logic, I'd make a method

soft matrix
#

it doesnt though

mortal fractal
#

looks like that check was also being used to prevent tuple literals like this

        # with self.assertRaises(TypeError):
        #     ClassVar[int, str]
trim tangle
mortal fractal
#

re @soft matrix @oblique urchin

soft matrix
#
    @property
    def community_url(self) -> str | None:  # TODO if defaults for TypeVars happen make this Generic over Literal[Type]
        """The SteamID's community url.

        e.g https://steamcommunity.com/profiles/123456789.
        """
        suffix = {
            Type.Individual: "profiles",
            Type.Clan: "gid",
        }
        try:
            return f"https://steamcommunity.com/{suffix[self.type]}/{self.id64}"
        except KeyError:
            return None
trim tangle
#

@soft matrix Can you show more code?

soft matrix
#

i want to be able to have this know that community urls only exist for Individual or Clan accounts

trim tangle
#

seems complicated

#

why inherit from an ID?

#

a user has an id, not is an id

soft matrix
#

i was just going through my pyright errors and was thinking this would be possible if the class was generic and overloads on properties worked (and id do it if defaults for TypeVars existed)

#

cause it makes the most sense to me to not have to do user.steam_id.community_url and instead just user.community_url

trim tangle
#

Can you explain what the class is supposed to do?

soft matrix
#

its the basic logic for how a steam id is supposed to be handled and all the things you can generate from one

trim tangle
#

What is a steam ID?

soft matrix
#

something like 76561198248053954

trim tangle
#

but what is it?

#

the identifier of a user?

soft matrix
#

yes

trim tangle
#

What are "type", "universe", and "instance"?

soft matrix
#

its like a snowflake in discord terminology except it encodes more info

#

type is the type of account the id is associated with like a game server, user, clan
universe is for valve devs to screw around with (basically everything is Universe.Public)
instance is a number from like 1 to 3 and idk what its actually for

trim tangle
#

So there are multiple types of account?

#

What are they?

soft matrix
#
    Invalid        = 0   #: Used for invalid Steam IDs.
    Individual     = 1   #: Single user account.
    Multiseat      = 2   #: Multiseat (e.g. cybercafe) account.
    GameServer     = 3   #: Game server account.
    AnonGameServer = 4   #: Anonymous game server account.
    Pending        = 5   #: Pending.
    ContentServer  = 6   #: Valve internal content server account.
    Clan           = 7   #: Steam clan.
    Chat           = 8   #: Steam group chat or lobby.
    ConsoleUser    = 9   #: Fake SteamID for local PSN account on PS3 or Live account on 360, etc.
    AnonUser       = 10  #: Anonymous user account. (Used to create an account or reset a password)
    Max            = 11  #: Max of 16 items in this field
trim tangle
#

So steam IDs are not just for users, but all sorts of things?

soft matrix
#

yep

trim tangle
#

Which of these types will be actually useful to the users of your library?

#

ContentServer, Pending, Max don't seem quite useful, but correct me

#

maybe there are 2-3 which are most useful?

soft matrix
#

Individual, GameServer, ContentServer, Clan, Chat

#

are the ones that currently inherit from SteamID

trim tangle
#

why do users of the library need the "universe" thing?

#

(do they?)

soft matrix
soft matrix
trim tangle
#

alright

trim tangle
soft matrix
#

well they have different things associated with them GameServers have players and rules, Individuals have games and friends, ContentServers have files, Clans and Chat have members and text channels

trim tangle
soft matrix
#

yeah but SteamID is meant to be like a raw version of all of the subclasses

trim tangle
#

Wdym "raw version"?

soft matrix
#

like some functions accept SteamID over a User/Clan/GameServer

trim tangle
#

You're already using subclassing to signify the type of an ID

trim tangle
#

If they accept SteamID, they should be able to work with a User/Clan/GameServer

soft matrix
#

or i could just have```py
class SteamID(Generic[TypeT]):
@overload
@property
def community_url(self: SteamID[Type.Individual] | SteamID[Type.Clan]) -> str: ...
@overload
@propery
def community_url(self) -> None: ...

trim tangle
#

What is the point?

#

Why would I call a pure function that always returns None?

#

Why do you need a generic? Just have a subclass for each type

#

You are overengineering this

soft matrix
#

maybe i should just make it raise

#

that might be more reasonable

trim tangle
#

I still don't get why you need a generic?

soft matrix
#

well i dont if it raises

trim tangle
#

A community URL isn't something an ID has. A community URL is something an individual has, or a clan has

mortal fractal
#

wow this is kinda gross

trim tangle
#

And an ID is also something an individual/clan/... has, not what it is

mortal fractal
#

Is it even possible to make add support for tuple literals to typing in python (like, for some future undetermined purpose) without breaking runtime introspection?

trim tangle
#

hm?

soft matrix
#

the way i see it is its like discord.Object or Snowflake idr

#

def func() -> (int, str)?

mortal fractal
#

like, imagine I do Final[(int, str)], but this is the same (at runtime!) as Final[int, str] which is disallowed, and there are runtime types that take one or multiple arguments

#

like this makes the introspection code a hot mess

trim tangle
mortal fractal
#

unless you give up disallowing Final[int, str] at runtime

trim tangle
#

isn't typing introspection code a hot mess already?

mortal fractal
#

but im trying to fix some of the hot mess right now

trim tangle
#

ah

#

I tried it, and it all seems very undocumented and messy

#

all these special attributes and crap

mortal fractal
#

check this issue out I opened ๐Ÿ™‚

trim tangle
#

This restriction is usually met in typeform instances by implementing a __call__ method that raises at runtime
i... what

#

cheeses

#

Why is typing such a mess? I expected it to mostly be no-op stuff that can be subscripted?

soft matrix
#

yeah its gross

trim tangle
#

time to design a new version of typing ๐Ÿ™‚

#

typing2

soft matrix
#

it used to be a lot worse

trim tangle
#

@soft matrix I think you can open an issue on Pyright and ask how to make an overloaded property

mortal fractal
#

and now the msg argument doesn't do anything

trim tangle
soft matrix
mortal fractal
#

@soft matrix @oblique urchin what is your opinion, should Final[str, int] be disallowed at runtime? we would have to disallow tuple literals as types. That's basically the only option

#

because types are used in __getitem__ and there's not a clean way to know if you passed a tuple or a comma separated list

soft matrix
#

i dont think it should be allowed if at runtime if any of the args are type checked at runtime

mortal fractal
#

sorry, so you think we should disallow Final[str, int]?

soft matrix
#

yes

#

id prefer it however if there was no checking of anything

mortal fractal
#

I think we've basically already committed to disallowing tuples as a type

#

Union[typeform] -> typeform, but Union[int, str] -> Union[int, str]

soft matrix
#

yeah

mortal fractal
#

so I can replace the callable check with a check for a tuple literal

mortal fractal
leaden oak
#

not a good start to the month for github

mortal fractal
mortal fractal
muted hemlock
#

how to ship a python library to pypi with stubs, i generated stubs to typings/umago/ and added py.typed in root. but can't see the pyi on going to defination

#

in vs code

mortal fractal
#

So did ya'll see why all the cpython tests are failing?

#

Apparently some of the tests relied on being able to send a POST to example.com (the actual webserver), and that stopped being reliable

#

I'd love to see the logs of random requests on example.com's webserver

trim tangle
#

Where can I read about the Never type? I haven't found it anywhere

#

was there a pep for it?

#

and how is it different from NoReturn?

#

I found this in Pyright release notes:

Enhancement: Added support for typing_extensions.Never and typing.Never type and unified the underlying handling of Never and NoReturn, which are synonyms.

#

wait... what's the point of adding a synonym?

#

seems like a strange change

covert dagger
trim tangle
#

having two bottom types is kinda confusing... I don't get what the difference is yet

covert dagger
#

yeah I didn't really get it either

#

the only reason I see is that NoReturn is a bad name in places other than return type of function

trim tangle
#

I agree that it's a confusing name

covert dagger
#

and renaming it to Never would be breaking

soft matrix
#

I think at some point it is gonna be removed

pastel egret
#

They're intended to be equivalent, it's that NoReturn is a bad name in those other cases.

soft matrix
#

yeah but I still think NoReturn should be removed cause there should be one obvious way to do it

trim tangle
#

"there should be one obvious way to do it" and "backwards compatibility" are in a bit of a clash ๐Ÿ™‚

soft matrix
#

it can be deprecated

trim tangle
#

right

#

it should've been "There should be one obvious way to do it, but we are humans, and humans are imperfect. So if we find a new way that's significantly better, we will deprecate the old obvious way."

#

or maybe the real thing that's happening is that people are becoming more Dutch?..

#

let us propose a more nuanced Zen of Python that is invoked via import this.depends

trim tangle
#

depends on what you want to do with that response

hearty shell
#

If you are only parsing it later then, if you know the structure of the json ahead of time you can make a TypedDict or just return dict[str, Any]

acoustic thicket
#

TypedDict

hearty shell
#

Hey, does anyone know why you cant alias Annotated in mypy? I tried multiple ways but mypy never accepts it

#

The only was I found around that was to from typing import Annotated as Foo

trim tangle
#

Depends on your type checker. In Pyright, you can do py Json = Union[int, str, float, bool, None, list["Json"], dict[str, Json]] mypy doesn't support recursive type aliases yet, so ```py
Json = Union[int, str, float, bool, None, list[object], dict[str, object]]

But I'm not sure how useful the type is, unless what you want is precisely "any JSON value"
#

Since you probably want to parse this afterwards anyway, I'd just use object

trim tangle
#

yes

mortal fractal
#

@soft matrix oof I might have gotten your instance subclassing PR killed by accident ;(

#

Sorry

mortal fractal
#

I don't think I can make a better argument than I already did that the error is worthwhile @soft matrix :\

oblique urchin
#

No need to jump to saying it will be rejected, just give it time

mortal fractal
#

ok

soft matrix
trim tangle
#

Interesting

upbeat wadi
#

That would be a helpful feature

boreal ingot
#

Would really help with the issue of having to duplicate parameters in a child class if you want to have good typing

trim tangle
#

Yeah

#

Aiohttp ๐Ÿ˜ฌ

chrome dust
#

How do annotate forwarding kwargs

def foo(a: int = 1, b: str = ''):
  ...

def bar(**kwargs):
  foo(**kwargs)
soft matrix
acoustic thicket
#

ParamSpec?

chrome dust
#

ParamSpec could not work out how to bind it to a function that was wasnt a func argument

maiden mesa
#

What's better, using ```python
from typing import List

mylist: List[int] = [1,2]orpython
mylist: list[int] = [1,2]```
I know there isn't really any big difference but is there any minor difference between these two?

hearty shell
#

If you are coding a library and what to be mindful of older versions of python use List, otherwise it is deprecated

soft matrix
#

at this point with 3.6 being deprecated, id always go with list[int]

hearty shell
#

There is virtually no difference to the type checker, and at runtime the only difference is that the __annotations__ will have a typing.List object instead of list

maiden mesa
#

oh alright, thanks

blazing nest
void panther
#

3.7+ allows you to use the future import to ignore the error it'd produce

trim tangle
#

!pep 585

rough sluiceBOT
#
**PEP 585 - Type Hinting Generics In Standard Collections**
Status

Accepted

Python-Version

3.9

Created

03-Mar-2019

Type

Standards Track

trim tangle
#

^ see for more info

maiden mesa
#

great, thanks!

little hare
trim tangle
#

I reordered the messages

trim tangle
#

shameless plug moment

little hare
#

let's also not pay attention to that fact that I'm always watching all messages here either

#

ty

mortal fractal
#

gratz @soft matrix

soft matrix
#

thanks :)

mortal fractal
trim tangle
oblique urchin
#

my coworkers have been asking for even stricter checks, like making if int an error

trim tangle
#

le huh

#

I do dislike implicit boolean checks

#

that would be interesting.

#

what about or and friends though?

#

it is basically an implicit if

oblique urchin
#

I largely use the same behavior for those

buoyant swift
#

you disable all coercing to bools?

oblique urchin
#

no, currently it's just for types that are always truthy (like Iterable)

trim tangle
#

I wish there was a way to write flake8 rules like that. But alas, it has no knowledge of types

oblique urchin
#

the int thing produced a ton of false positives, I may explore it again at some point though

trim tangle
#

well, [] is an iterable that is not truthy ๐Ÿค”
but the check is questionable, with that I agree

little hare
little hare
#

!pypi libcst using this personally to check errors in code

rough sluiceBOT
#

A concrete syntax tree with AST-like properties for Python 3.5, 3.6, 3.7 and 3.8 programs.

terse sky
#

I'm definitely against all truthiness, personally, at a language feature level. Pointless feature IMHO that optimizes writing and pessimizes reading.

little hare
#

oh.

terse sky
#

once a language already has it, and many consider it idiomatic, the question is should you still go against the grain

little hare
terse sky
#

Literally today I wrote if x where x was a list

#

i felt bad

trim tangle
#

naming 100 ๐Ÿ˜Ž

terse sky
#

but I dont' love if len(x) != 0 either

little hare
#

or did they...

terse sky
#

x wasn't the actual variable name ๐Ÿ™‚

trim tangle
#

ah

#

๐Ÿ™‚

buoyant swift
#

what about a isempty method? a lot of langs seem to have them

terse sky
#

I'd like to be able to write x.is_empty and x.is_not_empty

little hare
buoyant swift
#

oh, jinx

terse sky
#

yeah, was going to say, Kotlin has it.

little hare
#

this is what i was using

#

brb gonna go update my code and test it

terse sky
#

Even better would be if Kotlin did it as a property, which it doesn't for literally no reason

#

saves you ()

#

it's a cool example of extension properties, because you can obviously implement is_not_empty in terms of is_empty, so you only ahve to implement the former once and then it's available on all collections

#

you could also simply empty is_empty in terms of .size == 0 since all Collections have to have a cheaply-available size attribute

hearty shell
#

If you made a method that returned an Option, as if empty or not would that not still lead to the same issue?

terse sky
#

what do you mean?

hearty shell
#

If Iterable should return an Option, are you talking about readability?

#

Nvm, just read again, never thought about the consequences of using truthiness checks on variables such as int and iterables

terse sky
#

yeah, truthiness gets dicey when nesting is involved

#

dicey/confusing, maybe I should say

#

suddenly you have all these things that can become bool and since you'renot being explicit, you can choose the wrong one very easily and you won't get complaints

#

though you can have similar issues, at a smaller scale, in languages that opt for the null/none model instead of the optional/maybe model

#

even being relatively explicit

fierce ridge
#

i don't mind truthy/falsy as long as it's generally consistent

#

like javascript isn't... [] is truthy but '' is falsy

#

why?!?!

#

people's custom __bool__ implementations... might or might not be good

#

although i agree, an explicit .is_empty() function would be nice @terse sky

#

although idk if we really need it to be a property, seems unpythonic

terse sky
#

I actually have barely used properties in python

#

They're rather painful to write compared to their equivalents in other languages IMHO

#

So I'm not even sure what the criteria is

#

Typically, reading a property should be non mutating and super cheap

#

is_empty would seem to fit this

fierce ridge
#

the general recommendations that i see are that you usually shouldn't use properties except for compatibility with interfaces that only specify attributes

terse sky
#

Yeah maybe this comes back to their awkardness

fierce ridge
#

i think it's more about explicit vs implicit

#

why would you hide a function call behind not-a-function-call?

hallow flint
terse sky
#

Well it's all function calls really

#

But I know what you mean

#

In the end it always can be hidden behind a function call anyway, and it's really not any of the users business :-) as long as principle of least surprise isn't violated

fierce ridge
#

is it? i mean yeah technically you can override __getattribute__ but

#

i'm not sure what the benefit is to save a ()

#
if my_list.is_empty():
    ...
#

i guess?

#

i think the general school of thought is that len itself can be abstracted to some extent with __len__ and len(my_list) == 0 is more or less the same

#

might be nice if Sequence had .is_empty as a mixin method

terse sky
#

Except len(my_list) == 0, at least traditionally, is fiscouraged

fierce ridge
#

is it?

#

pep 8 suggests using if my_list

#

but

buoyant swift
#

yeah, the general advice is to prefer the truthy check

#

which is honestly kinda ๐Ÿฅด

fierce ridge
#

fwiw pep 8 is like... old

#

really old

#

i suppose that there might be a good use case for "empty" that isn't governed by len()

#

in which case you'd maybe want 2 different methods

#

but... meh

#

there is definitely a good argument for there being a .length() or .size() method instead of the toplevel len() + .__len__ method

#

but i think len()+.__len__() is because __len__() was added later

hearty shell
#

Does anyone what decides how a TypeVar is displayed? Specifically what makes it plus and what makes it minus. I couldn't really find any mention of it anywhere

T`1   T`-1   T`-2   T`2
oblique urchin
#

that's just some mypy internal logic

hearty shell
#

Oh, I just wanted to know if there is any structure to it

oblique urchin
#

probably, but to figure it out you'll have to read mypy source code ๐Ÿ™‚

hearty shell
#

Humm, I think I kinda got what determines the the sign. I wouldnt know where to start on reading the source code for that so I will live with not knowing about the numbers x)

#
T = TypeVar('T')
K = TypeVar('K')
V = TypeVar('V')

class M(Mapping[T, K]):
    @classmethod
    def foo(cls, v: V, t: T) -> K: ...
    
    @staticmethod
    def bar(v: V) -> K: ...

def m(t: T, k: K) -> T: ...
main.py:18: note: Revealed type is "def [T, K] () -> __main__.M[T`1, K`2]"
main.py:19: note: Revealed type is "def [T, K, V] (v: V`71, t: T`1) -> K`2"
main.py:20: note: Revealed type is "def [T, K, V] (v: V`-1) -> K`2"
main.py:21: note: Revealed type is "def [T, K] (t: T`-1, k: K`-2) -> T`-1"
mortal fractal
#

and then I closed it

trim tangle
errant silo
#

how to typehint kwargs in a Callable?

trim tangle
soft matrix
#

you need to use a callback protocol eg

class Callback(Protocol):
    def __call__(self, *, kwarg_types: Something) -> ReturnType: ...
trim tangle
#

yep

soft matrix
#

ideally one day ```py
(str, x: int, y: float) -> bool

hearty shell
#

Wasn't there something about using TypedDict instead?

trim tangle
soft matrix
#

its not mentioned in the draft pep

oblique urchin
hearty shell
#

Ahh I see

soft matrix
#

Callable[[**SomeTypedDict], T] i dont see why that wouldnt work

oblique urchin
#

Though I guess we could make Callable[[Unpack[TD]], ...] work

soft matrix
#

yeah

#

maybe its worth mentioning

oblique urchin
#

your variant would be a syntax error ๐Ÿ™‚

soft matrix
#

i thought they were updating syntax

#

oh wait that means supporting kwargs in getitem :))))

oblique urchin
#

I feel like that syntax change would be a harder sell

soft matrix
#

ig thats not gonna happen then lol

oblique urchin
#

Maybe with a compelling typing use case it would go through

#

But Callable[[**SomeTypedDict], T] isn't even indexing, it's ** in a list literal

#

So that would be yet another new syntax

soft matrix
#

oh yeah

hearty shell
#

But shouldn't unpack be equivalent to * according to 646?

oblique urchin
#

Single star unpack

hearty shell
#

Callable[[*TD], T] == Callable[[Unpack[TD]], T]

oblique urchin
#

Not double star

hearty shell
#

and the consequently * on both

blazing nest
#

Yess

#

That would've been quite nice, was it deferred or completely rejected

soft matrix
#

it was completely rejected cause it was too hard to teach or something along with only benefiting typing checking

blazing nest
#

Numpy could've probably gotten a use? logo_panda3d grumpchib

fierce ridge
#

it's pretty useful in R

oblique urchin
soft matrix
#

@oblique urchin what happened to the pep for using +/- for TypeVar variance?

oblique urchin
soft matrix
#

oh that sucks, hope they are well

errant silo
#

linter allows Callable[[*param_types], return_type] but not Callable[param_types, return_type] even tho they both work why??

soft matrix
#

because they are different?

#

[*param_types] is a list param_types might not be

errant silo
#

param_types is typed as list and is one

hearty shell
#

It is just part of the Callable syntax, your first argument has to either be a list of types or a typing.ParamSpec

errant silo
#

๐Ÿ‘€ it even allows Callable[[int], int] but not Callable[int, int] even tho both work and is same, it always wants the first one to be enclosed in square brackets irrelevant of its type?

hearty shell
#

and by list I mean literal list, not a variable of type list

errant silo
#

ooh

hearty shell
#

it is so you can differentiate the argument and the return type

#

Maybe... Or it is for implementation, either way x)

errant silo
#

maybe they forgot to update the typehint ๐Ÿ˜†

hearty shell
#

What you mean?

errant silo
#

allow lists and obj of instance type to be passed as first argument??

#

cos both works proper

oblique urchin
#

If it works at runtime that doesn't mean a static type checker should accept it

hearty shell
#

If by working you mean it doesnt cause issue at runtime, than anything works

#

as long as it is valid python syntax

soft matrix
#

it doesnt work with collections.abc.Callable at runtime

errant silo
#

ooh

hearty shell
#

but it just isnt what was decided as

errant silo
oblique urchin
errant silo
#

for Callable

hearty shell
#

What makes you say it is converting?

#

what is converting?

errant silo
hearty shell
#

Nope, it is deprecated

#

actually, it might be the newer one

#

but

soft matrix
#

!d collections.abc.Callable

rough sluiceBOT
soft matrix
#

is the one you want

errant silo
#

oh

hearty shell
#

then support for generics was added to the collections

#

not sure on the timeline

soft matrix
#

not typing.Callable cause its deprecated past 3.9.2(???)

trim tangle
#

yep, typing.Callable is deprecated by PEP 585

soft matrix
#
In [5]: collections.abc.Callable[int, str]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [5], in <module>
----> 1 collections.abc.Callable[int, str]

File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/_collections_abc.py:436, in _CallableGenericAlias.__new__(cls, origin, args)
    434     args = (*t_args, t_result)
    435 elif not _is_param_expr(t_args):
--> 436     raise TypeError(f"Expected a list of types, an ellipsis, "
    437                     f"ParamSpec, or Concatenate. Got {t_args}")
    438 return super().__new__(cls, origin, args)

TypeError: Expected a list of types, an ellipsis, ParamSpec, or Concatenate. Got <class 'int'>
```and this then doesnt work with [int, int] at runtime
errant silo
#

oh

trim tangle
errant silo
#

why is the typing version and collections.abc different?

hearty shell
#

Because first there were the collections, for user to subclass

trim tangle
#

Maybe you could add something about these imports actually being deprecated, not just adding an alternative?
(also I have a compulsion to plug flake8-pep585 hyperlemon )

hearty shell
#

when support for typing was being added there was no support for class subscription, eg "Class[]"

#

so then typing.Callable, typing.List and so on were created for the use of type ckeckers

#

later on support for class subscription was added retroactively to the abcs, hance no more need for the typing.X ones

errant silo
#

Oh

hearty shell
#

So while typing.X are technically newer, they are deprecated xD

fierce ridge
#

note that some things in typing are not deprecated, e.g. typing.Any, because they don't have equivalents elsewhere

trim tangle
#

oh yes, not everything in typing is deprecated

#

although deprecating Any would be a power move lol

fierce ridge
#

i wish they would have moved typing.NamedTuple to collections

trim tangle
fierce ridge
#

i really should start using pyright, not having a properly recursive JSON type really makes type annotations difficult to use for "real" data

#

how does haskell handle that? some language extension for heterogeneous collections?

#

i know heterogeneous collections are possible in idris 2 but i admittedly don't know how they're implemented

trim tangle
soft matrix
#

using pyright with JSON still means you have to type ignore every line cause you arent handling each case

fierce ridge
fierce ridge
#

as in, you have to make asserts about the types before taking out any data point?

errant silo
#

oh thats why my program broke

isinstance(Callable, Callable)                
True
soft matrix
#

assuming you have typeCheckingMode = "basic"

fierce ridge
#

that's the same as in haskell though, you'd have to explicitly pattern match

trim tangle
fierce ridge
#

"parse don't validate" is a really really important paradigm in functional programming with static types

trim tangle
#

yeah

fierce ridge
#
parseUserInfo :: JSON -> Either InvalidUserObject User
#
parseUserInfo :: JSON -> Either InvalidUserObject User
parseUserInfo JDict userData = ...
parseUserInfo JList _ = Left $ mkInvalidUserObject [JsonWrongType JList]
parseUserInfo JStr = Left $ mkInvalidUserObject [JsonWrongType JStr]
parseUserInfo JInt = Left $ mkInvalidUserObject [JsonWrongType JInt]
parseUserInfo JNull = Left $ mkInvalidUserObject [JsonWrongType JNull]

i guess

#

idk if that's even valid haskell but that's the idea

trim tangle
#

well, this is going to be abstracted by some nice parsing library with some monad shit, but yeah

fierce ridge
#

right

#

at which point it becomes opaque and impossible to understand for 99% of programmers ๐Ÿ˜†

trim tangle
#

Just to make sure:

  • if I specify TypeVar("Foo", A, B, C), it can hold a type that is either a subtype of A, a subtype of B or a subtype of C. But I can't assign Union[A, B]
  • if I specify TypeVar("Foo", bound=Union[A, B])
    right?
trim tangle
fierce ridge
#

yeah but some people have an aptitude of this kind of thing

#

dropping out of school and still being "successful" isn't the norm!

#

anyway, being forced to do this imo is a Good Thing, but it's alien to a lot of python devs. i think ocaml is great here, ultra-abstracted stuff isn't that common and people tend to use more explicit pattern matching

trim tangle
#

oh I just meant that I'm not very bright, generally

fierce ridge
#

meh, i'm dumb as a salt rock

#

we're all stupid in our own way

hearty shell
oblique urchin
fierce ridge
trim tangle
oblique urchin
#

You can still pass a subtype of the resolution

fierce ridge
#

i've found that doesn't work in the past

trim tangle
#

๐Ÿ…ฑ๏ธruh I'm writing a tutorial on type variables but I don't know how they work

#

amazing

nova venture
#

TypeVar("T", A, B) can be either A or B, including subclasses. and Typevar("T", Union[A, B]) can be either A, B, Union[A, B], including subclasses

fierce ridge
#

i thought it explicitly didn't let you use subclasses without bound=

#

am i crazy

hearty shell
#

I am getting confused now

#

what about TypeVar("T", bound=Union[A, B])

#

I didnt even know you could bind to an Union

nova venture
#

ah I forgot the bound, I think Typevar("T", Union[A, B]) is disallowed

#

as in the *args has to be length 2 or more

#

let me see

oblique urchin
hearty shell
#

Ah so it is the other way around

#

so

oblique urchin
#

TypeVars aren't very well specified in general though

trim tangle
oblique urchin
acoustic thicket
trim tangle
trim tangle
trim tangle
#

so what should the right behaviour be?

oblique urchin
#

the only allowed solutions for T are A and B

trim tangle
#

oh wait, I just read the screenshot properly

#

I'll file a bug

fierce ridge
#
from typing import TypeVar

class Base1: pass
class Base2: pass
class Thing1A(Base1): pass
class Thing1B(Base1): pass
class Thing2A(Base2): pass
class Thing2B(Base2): pass

TheBase = TypeVar('TheBase', Base1, Base2)

def f(x: TheBase) -> TheBase:
    return x

f(Thing1A())
f(Thing2B())
#

this solves so so many problems i had with bound=Union[Base1, Base2]

oblique urchin
#

I feel like those kinds of typevars mostly create problems ๐Ÿ™‚

fierce ridge
#

i remember i tried using bound=Union and it didn't really work, lots of inference and variance problems

#

i think they have a lot of valid use cases, e.g. a function that operates on either str or bytes and returns the same

#

and a lot of things related to numpy and pandas

nova venture
#

wait in the screenshot what's bad about x being assigned to MyStr instead of str?

hearty shell
#

MyStr is a subclass of str

acoustic thicket
#

the typevar gets resolved to str and not MyStr

nova venture
#

yeah, but why not let it be resolved to MyStr?

#

related ?: If TypeVar("T", bound=Union[str, bytes]) was used in screenshot, then it would be resolved to MyStr right?

acoustic thicket
#

uhhhh how do you share the mypy play link again

oblique urchin
#

but then the body of the function wouldn't typecheck

nova venture
#

why doesn't it want every subtype of Union[str, bytes] to be addable with every other subtype, instead of the particular subtype chosen and itself?

#

so only MyStr + MyStr being needed in this case, not str + AnyStr and bytes + AnyStr

terse sky
#

because it's a generic function

boreal ingot
# acoustic thicket

!e that makes sense ```py
class MyStr(str): ...

print(type(MyStr("h") + MyStr("i")))```

rough sluiceBOT
#

@boreal ingot :white_check_mark: Your eval job has completed with return code 0.

<class 'str'>
oblique urchin
#

looks like we found a mypy bug

terse sky
#

x and y have the same type

trim tangle
#

and a pyright bug

terse sky
#

the function declaration guarantees it

trim tangle
#

double kill

boreal ingot
hearty shell
#

no

#

wait

trim tangle
acoustic thicket
hearty shell
#

but you cant concat bytes and str

nova venture
#

I think quicknir is right, should error

terse sky
#

but you can't deduce to bytes + str

trim tangle
terse sky
#

both arguments are always the same type

#

that said, the function annotation is still wrong

hearty shell
#

but mypy doesnt know that because it is bound to an union

oblique urchin
#

oh wait, the mypy one is right. I missed that @halcyon nexus's example used bound=Union

nova venture
#

theoretically if mypy or pyright looked at the call sight then it might not error in above case

hearty shell
#

it cant resolve

terse sky
#

the return type from teh body isn't really compatible with the annotation

nova venture
#

but just looking at declaration site it is not always allowed

hearty shell
#

it only resolves if it is TypeVar('T', Text, bytes)

terse sky
#

the return type annotation is saying that the exact type that's passed in is what's returned

#

however, the bound is not sufficient to guarantee that

hearty shell
#

otherwise it is agnostic as to what the other T in the argument is no?

terse sky
#

having str as an upperbound for a type T means that adding two T's returns a str

#

not that adding two T's returns a T

#

it might even mean that adding a T and a str returns a str, in fact

nova venture
terse sky
#

you really don't want to combine inheritance with binary operators

#

in most cases

boreal ingot
terse sky
#

right.

boreal ingot
#

you might imagine it looks like py def __add__(self, other): return str(self.value + other.value) just subclassing that will still have it return str

terse sky
#

inheritance only works as you'd expect on the first argument; trying to extend inherited arguments into generics, you're going to have problems

nova venture
#

because a subclass of str might not have a one-arg constructor? so it'd be sometimes impossible to construct an instance of the subclass after concatenation?

terse sky
#

subclass of str are only guaranteed to have the same API as string, on the more derived type

nova venture
#

in the MyStr case they could just do type(self)(self.value + other.value) but I see how that would not generalize

terse sky
#

__add__ takes a str and returns a str

#

so if all you know about a type T is that it's bounded by str, you can't guarantee that adding two T's gives you a T

boreal ingot
terse sky
#

You can only guarantee that T + str -> str

nova venture
#

assuming the Self annotation existed in the past, why not define __add__ as (self: Self, other: Self) -> Self

#

instead of (self: Self, other: str) -> str?

trim tangle
#

yeah

#

2 + 3.5 == 5.5

#

tuple[A] + tuple[B] == tuple[A, B]

nova venture
#

ah I see

terse sky
#

You don't really want to do T = TypeVar('T', upper_bound=Union[str, bytes])

#

python doesn't provide a "string-like" interface but in the end that's what you want

#

I guess the easiest way is probably to define a generic protocol, if that would work

hearty shell
#

Humm, so in this example

T = TypeVar('T', Text, bytes)

def concat(x: T, y: T) -> T: return x + y
class MyStr(str): ...
a = concat(MyStr(), MyStr())

The reason it is fine is because T resolves to str right?

terse sky
#

hmm is python able to handle this properly?

#

In e.g. Kotlin you can mention the type variable in the bound:

fun <T : Comparable<T>> T.coerceAtLeast(minimumValue: T): T
hearty shell
#

well, mypy just resolves a to str

terse sky
#

You need this for interfaces that relate to binary operations

#

You want to say "I want a T comparable to other T's". In this case, you want a U that is addable to other U's.

#

How do you formulate this in python?

#
from typing import Protocol, Generic, TypeVar

T = TypeVar('T')

class Addable(Protocol, Generic[T]):
    def __add__(self, t: T) -> T:
        ...
        
U = TypeVar('U', bound=Addable[U])

def foo(u1: U, u2: U) -> U:
    return u1 + u2
#

quoting the U passed to Addable doesn't help

oblique urchin
#

maybe a self type on Addable.__add__?

hearty shell
#

I mean, I think you just have to upper bound by the class you want, inst that what you are doing there?

terse sky
hearty shell
#

You are saying you want (T, T) -> T exactly, but mypy just resolves to the common supertype among all T, but you want it to resolve to the exact same

trim tangle
#

(I also initially submitted the issue with the name Incorrect like a complete idiot)

terse sky
#

Well, I want to be able to define a type variable, where that variable can be passed to the constraint

#

this is supported in pretty much every constrained generics system, I think

oblique urchin
#

T = TypeVar("T", A, B, contravariant=True) I have little idea what that even means :S

trim tangle
#

let me think if this makes sense for a bit...

hearty shell
#

No, it still doenst bound it

trim tangle
hearty shell
#

Resolves to str and doesnt cause an error

#

Yeah not sure what to make of it

oblique urchin
#

variance definitely doesn't do anything on generic functions

spiral fjord
#

str.__add__ returns a str, so a would be str right?

oblique urchin
#

it's only meaningful with generic classes

hearty shell
#

But then how do you make so (T, T) -> T has to have all T be the same type?

#

and not only share a common supertype

trim tangle
#

what's the difference?

spiral fjord
#

Don't subclass str and make your own add

hearty shell
hearty shell
trim tangle
#

came for the ducks, stayed for the types

oblique urchin
#

or for the cats

terse sky
#

No

trim tangle
#

definitely not for the pacmans

terse sky
#

I was saying that in other languages, you can have generic interfaces

#

and you can pass the argument itself

#

the type argument itself, into the generic interface

#

this doesn't force exactly (T, T) -> T, derived types are fine

#

what's not fine is (T, str) -> str

#

You need this mechanism in order to have constraints that make sense on two argument functions. Like +, <, etc

#

As a simple example: how would you constrain a sort function that doesn't take a comparator?

trim tangle
hearty shell
#

So def concat(x: T, y: T) -> T: where you pass MyStr to both x, and y and then get a str as a return is fine?

terse sky
#

No

spiral fjord
#

It should be None here since MyStr returns None when you try to instantiate it.

trim tangle
oblique urchin
terse sky
#

Right, it depends what T is

hearty shell
#

no it wont?

trim tangle
rough sluiceBOT
#
Bad argument

Converting to "int" failed for parameter "pep_number".

hearty shell
#

!e
class MyStr(str): ...
pirnt(type(MyStr()))

rough sluiceBOT
#

@hearty shell :x: Your eval job has completed with return code 1.

001 | Traceback (most recent call last):
002 |   File "<string>", line 2, in <module>
003 | NameError: name 'pirnt' is not defined. Did you mean: 'print'?
hearty shell
#

well, I cant type

#

but it doesnt xF

terse sky
#

Now I'm curious what python's type stubs look like for sort

spiral fjord
#

I had a typo my bad

rough sluiceBOT
#

stdlib/builtins.pyi line 820

@overload```
spiral fjord
#

!e

class MyStr(str): ...
print(type(MyStr() + MyStr()))```
rough sluiceBOT
#

@spiral fjord :white_check_mark: Your eval job has completed with return code 0.

<class 'str'>
terse sky
#

now I have to find SupportsRichComparison ๐Ÿ™‚

oblique urchin
#

Yeah I think it's not completely precise

soft matrix
#

its a _typeshed type i think

rough sluiceBOT
#

stdlib/_typeshed/__init__.pyi line 54

SupportsRichComparison = Union[SupportsDunderLT, SupportsDunderGT]```
terse sky
#

how do you do that so fast ๐Ÿ™‚

oblique urchin
hearty shell
terse sky
#

Okay, so we can see that the way python does this is hot garbage

#
class SupportsDunderLT(Protocol):
    def __lt__(self, __other: Any) -> bool: ...
#

where is your statically typed god now

soft matrix
#

whats wrong with that?

oblique urchin
terse sky
#

^

oblique urchin
#

just that they're comparable to something

trim tangle
soft matrix
#

oh

terse sky
#

you shouldn't need to fall back to Any, in order to constrain sorting a list

soft matrix
#

see and this is why we need a Not type

trim tangle
#
from typing import TypeVar, Generic


class A: ...
class B: ...
class AB(A, B): ...


T = TypeVar("T", A, B)


class F(Generic[T]):
    def __init__(self, thing: T) -> None:
        self.thing: T = thing


f = F(AB()) 
reveal_type(f)
reveal_type(f.thing)
``` Is it defined anywhere that `f` should be `F[A]` and not `F[B]`?
terse sky
#

what does Not have to do with it?

terse sky
#

the way this is solved in basically all languages is with a generic constraint

#

but it seems like in python, you can't pass the type argument back to the generic constraint, making this useless. Maybe it works better with another type checker?

#

Here's Kotlin's sort:

fun <T : Comparable<T>> MutableList<T>.sort()

Notice that T is being passed to Comparable

trim tangle
# oblique urchin evil

If you do T = TypeVar("T", B, A) it resolves to F[B] instead. But the inheritance order doesn't seem to matter

soft matrix
#
class list[T](Sequence[T]):
    def sort(self: Self[T & SupportsCMP]) -> None: ...
```would this solve it?
acoustic thicket
#

is that... valid python?

soft matrix
#

no

terse sky
#

I mean you can already write what is intended using valid python

soft matrix
#

so much of this doesnt work but i think you can guess what it means

terse sky
#

mypy just refuses to handle it

#
from typing import Protocol, Generic, TypeVar

T = TypeVar('T')

class Addable(Protocol, Generic[T]):
    def __add__(self, t: T) -> T:
        ...
        
U = TypeVar('U', bound=Addable[U])

def foo(u1: U, u2: U) -> U:
    return u1 + u2
#

this example si with adding not comaprison but it's teh same concept

#

this is the spiritual equivalent to the Kotlin code I showed before

#

but mypy chokes on this U = TypeVar('U', bound=Addable[U])

#

quoting the U there doesn't help either

spiral fjord
#

very odd, but if you drop the Text, bytes constaint it does give the MyStr type

terse sky
#

not surprising I guess since it doesn't handle recursive types

#

if someone has pyright installed maybe they could try running that code example through pyright

soft matrix
#

thats not gonna work with pyright

trim tangle
#

Yeah, you can't use generic type aliases as a bound

#

That would be higher kinded types

terse sky
#

pretty sure it's not higher kinded types

spiral fjord
#
from typing import TypeVar, Text, Tuple

T = TypeVar('T', "MyStr", Text, bytes)

def as_tup(x: T, y: T) -> Tuple[T, T]: return x, y

class MyStr(str):...

a = as_tup(MyStr("a"), MyStr("a"))
reveal_type(a)```
Like this it does work, if you add the MyStr to the TypeVar
#

I guess it changed it to str because it was constrained to str or bytes

terse sky
#

Java, Kotlin, etc, do not have higher kinded types, and they handle this perfectly, ergo this is not higher kinded types ๐Ÿ™‚ Without even getting into definitions

trim tangle
#

oh wait, I misread it

#

yeah this is not HKT

acoustic thicket
#

Higher kinded types?

trim tangle
#

Addable to itself would be

from typing import Protocol, Generic, TypeVar
from typing_extensions import Self

class Addable(Protocol):
    def __add__(self, t: Self, /) -> Self:
        ...

T = TypeVar("T", bound=Addable)

def foo(u1: T, u2: T) -> T:
    return u1 + u2

x = foo(1, 2)
``` @terse sky
terse sky
#

Like, this is exactly the kind of thing that makes me overall meh on python static type system. It's very expressive, in some places it has all these super fancy features, and then in another places you find a very basic omission that prevents it from correctly typing sort

trim tangle
#

seems like the / is important

terse sky
#

that's pretty strange (the /)

soft matrix
trim tangle
#

o right

trim tangle
oblique urchin
trim tangle
spiral fjord
#

3.11 also has the sweet callable type hint syntax

oblique urchin
#

and there's a serious chance it will be rejected

trim tangle
#

yeah it's in draft

hearty shell
#

God damnit, I got static for a second

trim tangle
#

damn, you couldn't accept self?

spiral fjord
#

I build the demo on my work pc, planning on messing around with it a bit tomorrow

oblique urchin
#

the callable syntax one isn't

trim tangle
acoustic thicket
hearty shell
#

Omg ๐Ÿ˜‚

#

I am just bound now to being sad

trim tangle
oblique urchin
acoustic thicket
#

Hmph

spiral fjord
#

I find it less complicated than Callable tbh

oblique urchin
acoustic thicket
#

Id be willing to sacrifice patma for it ๐Ÿฅด

hearty shell
#

Guess we will just have to live with this

#

x)

trim tangle
#

oof

#

what an atrocity

dapper yacht