#type-hinting

1 messages · Page 46 of 1

trim tangle
#

If you have something other than print, you could do something like this: ```py
from collections.abc import Callable

def copy_type[T](from_: T) -> Callable[[T], T]:
return lambda x: x

---

def my_func(*args: object, **kwargs: object) -> None:
# shh, no printing this time
pass

my_print = copy_type(print)(my_func)

#

why do you want this though? do you really want to overwrite print?

hasty phoenix
#

Without this the code gets littered with

if enabled:
   do_something_when_enabled()

which looks ugly if this is done very many times in a function.

hasty phoenix
#

Why is mypy complaining about the return type? Isn't __eq__() always returning bool?

def foo(a: Any, b: Any) -> bool:
    return a == b  # mypy: Returning Any from function declared to return "bool"
trim tangle
#

== doesn't have to return a boolean. For example, if a and b are numpy arrays, then a == b is also a numpy array (an array of boolean with elementwise equality)

#

However, object.__eq__ pretends that doesn't exist and is typed as (object, object) -> bool

#

maybe operator results on Any are just always inferred as Any

mossy rain
hasty phoenix
#

Is it customary to declare __eq__with Any or to use the expected use-case for comparison? In principle anything can be compared, right?

class A:
    # Declare `other` as `Any` or `A`?
    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, A):
            return False
        return some_comparison_of(self, other)
restive rapids
mossy rain
#

*Liskov substitution principle (right spelling per mypy)

hasty phoenix
#

What's the difference between using object as type vs Any ? Everything inherits object doesn't it?

mossy rain
restive rapids
mossy rain
#

And that

#

Any is somewhat stricter and more open at the same time

hasty phoenix
hasty phoenix
#

Thanks, this makes sense

mossy rain
#

np

trim tangle
#

any always "has" all the attributes and methods you could imagine

viscid spire
#

That reply doesn't make sense

#

!e Actually it does.

from typing import Protocol
print(type(Protocol))
rough sluiceBOT
rare scarab
viscid spire
#

Same with ABCs, you just inherit

#

Or did you mean are they also an instance of the metatype? The answer is yes.

trim tangle
viscid spire
#

I don't understand

trim tangle
# viscid spire I don't understand

!e

from abc import ABC, abstractmethod

class Fruit(ABC):
    @abstractmethod
    def eat(self) -> None: pass

class BananaMeta(type): pass
class BaseBanana(metaclass=BananaMeta): pass

class Banana(BaseBanana, Fruit):  # TypeError: metaclass conflict
    def eat(self) -> None: print("nomnomnom")
rough sluiceBOT
# trim tangle !e ```py from abc import ABC, abstractmethod class Fruit(ABC): @abstractmet...

:x: Your 3.14 eval job has completed with return code 1.

001 | Traceback (most recent call last):
002 |   File "/home/main.py", line 10, in <module>
003 |     class Banana(BaseBanana, Fruit):  # TypeError: metaclass conflict
004 |         def eat(self) -> None: print("nomnomnom")
005 | TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
trim tangle
#

!e
vs: ```py
from abc import abstractmethod
from typing import Protocol

class Fruit(Protocol):
@abstractmethod
def eat(self) -> None: pass

class BananaMeta(type): pass
class BaseBanana(metaclass=BananaMeta): pass

class Banana(BaseBanana):
def eat(self) -> None: print("nomnomnom")

fruit: Fruit = Banana() # ok
fruit.eat()

rough sluiceBOT
fiery canyon
#

@mossy rainI should be able to del the ABC with no problem, right?

#

I just like deling anything that users shouldn't interact with (:

mossy rain
# viscid spire That reply doesn't make sense

You said that protocol itself doesnt fail there (tbh your reply was a little unspecific, but i got that from my original msg), so i said that the checkers i ran over the script dont fail either

mossy rain
fiery canyon
viscid spire
mossy rain
mossy rain
fiery canyon
#

Gotta be consistent 🫡

jolly cipher
#

but it may not be worth it in the long run

#

you can always accidentally break something that needs these symbols as free variables
these "leaks" only truly matter if you have an __init__.py that doesn't define __all__, since then your leaks pollute somebody who wildcard-imports your package; it's easier to just define __all__

brazen jolt
#

definitely, and with how dynamic python is, your users could still technically get to the class if they really tried

jolly cipher
#

^

#

python is not java. it's consenting adults

brazen jolt
#

!e ```python
class Foo:
def foo(): ...

class Bar(Foo): ...

del Foo

Foo = Bar.bases[0]
print(Foo)

rough sluiceBOT
jolly cipher
#

there's a ton of ways to do that

#

but the cool thing is -- it's not your responsibility

brazen jolt
#

yeah, library users should respect conventions and not do things like that, or if they do, then they should know it's their responsibility to maintain and that they might face unexpected behavior as you change your internals

jolly cipher
#

relying on hacks always backfires because it introduces backward compatibility problems where they should never appear

restive rapids
jolly cipher
#

exposing to breakage more than necessary

jolly cipher
#

i shouldn't have mentioned java :) barely touched it

fiery canyon
brazen jolt
#

the purpose of __all__ isn't to prevent others from importing private things

fiery canyon
#

I know

brazen jolt
#

it's to stop wildcard imports

#

if you have some internals that shouldn't be exposed when someone does from yourlib import *

jolly cipher
brazen jolt
#

also, some lang servers / type checkers can warn you about importing internals

#

it's a useful tool if you have some internal variables that aren't _ prefixed that shouldn't be exposed

mossy rain
#

yeah, but you can always modify everything in Python. Even of some custom __getattr__ or __setattr__ exists, they could ve overwritten with some new functionality.

#

still del'ing stuff, and making a __all__ is best practice to try and avoid stuff like that (especially for inexperienced users)

tropic glen
#

How come I get an error when a function typed to return [A|B|C] returns [sA|sB|sC] where sX is a subclass of X?

#

surely Python's type system understand that if Y is an acceptable type for something bound to X, then list[Y] is an acceptable type for something bound to list[X]?

restive rapids
# tropic glen surely Python's type system understand that if Y is an acceptable type for somet...

Many programming language type systems support subtyping. For instance, if the type Cat is a subtype of Animal, then an expression of type Cat should be substitutable wherever an expression of type Animal is used.
Variance is the category of possible relationships between more complex types and their components' subtypes. A language's chosen var...

restive rapids
tropic glen
#

Lol the semantics of return type are obviously different to the semantics of argument type

#

I assume this is just a case of needing to work around python's halfarsed type system again

restive rapids
#

do you actually need the type to be list (which is mutable), or an immutable sequence would do too? then you wouldn't have issues with invariance

tropic glen
#

oh so I might have stored the list of subclass type and the calling function can modify it

#

can't help but feel you could have said that less snarkily lol

restive rapids
tropic glen
#

no worries

#

and I'm sure being educated makes me more prone to feel snarkedl ol

#

I think it feels halfarsed because it's a language that used to love duck typing

#

and formalising it adds all these hard errors that would previously have been subtle semantic edgecases

restive rapids
tropic glen
#

hm

#

I guess it feels like the type system innovation didn't come with the basic language changes I would have expected

restive rapids
brazen jolt
#

Like @restive rapids suggested here, you probably shouldn't need a list here, python has collections.abc.Sequence which can represent an immutable sequence type, you can pass a list to it, but the funciton that takes that type should not make any modifications to that list

trim tangle
#

To answer your question, you can annotate the list explicitly if the type inferred from the list literal is wrong ```py
def make_lists_or_strings() -> list[int | str]
things: list[int | str] = [1, 2, 3]
if random.random() > 0.5:
things.append("banana")
return things

brazen jolt
#

for example, consider this: ```python
from collections.abc import Sequence

def foo(x: Sequence[str | int]):
x.append("f") # type-error: Sequence type doesn't support 'append'

# however, you can get the elements inside of the sequence and do stuff with them:
print(x[2])  # valid
#

and you can now call it with a list, since list is a subtype of Sequence, so this call is valid: foo(["hi"))

tropic glen
#

yeah that's definitely what I should have been doing

#

maybe I can ask another question: what's a good way to do discriminated union matching?

#

seems like I always end up having to cover the hypothetical base case even if I just raise

restive rapids
brazen jolt
#

You can encode your discriminator as a more specific type

#

eg an enum

restive rapids
#

i usually do

type U = A | B
@dataclass
class A:
  ...
@dataclass
class B:
  ...

and then

match u:
  case A():
    ...
  case B():
    ...

if needed, or just overload methods if possible

brazen jolt
#

a type-checker shouldn't complain about a missing base case if you exhausted all possible options to discriminate on

tropic glen
#

but it seems to still think you could fall off the end of an exhaustive if/elif

restive rapids
tropic glen
#

for every possibility

restive rapids
#

can you show a case where you have an issue?

tropic glen
#

hm I'll try to find one

brazen jolt
#

yeah, it's pretty hard to understand what you mean here, a type-checker shouldn't force you into adding a base case if your conditions exhausted all of the possible options for the type

#
def foo(union_val: A | B | C) -> None:
    if isinstance(union_val, A): return
    if isinstance(union_val, B): return
    reveal_type(union_val)  # union_val should be inferred as 'C', no need for another if + base case
feral wharf
#

Is reportShadowImports (still) a thing in pyright? i can't find much about it other than it being in someone's config but it's being reported as an unknown rule

brazen jolt
feral wharf
#

Ooh

bleak imp
#

Is there way way to add extra typing info to a function call in-line? Like how if you have a generic class, you can do Foo[int](x) to tell the type checker that the generic is int, is there any way to do the same thing with a function?

rough sluiceBOT
trim tangle
#

nope

#

why do you want this though?

bleak imp
# trim tangle why do you want this though?

:( I’m making/using a rust like result type, and I’m trying to add a method for mapping ok/err, but if I chain these maps with lambdas the inference stops working and I get unknown types

bleak imp
trim tangle
#

what a nice error message

bleak imp
#

Yep, the generic soup makes for such fun error messages

trim tangle
bleak imp
#

The tuple passed into the second lambda is (self output, (other modified input, other output))

trim tangle
#

Try rewriting this using proper functions instead of lambdas. You'll see the mistake

#

(you cannot express what you have with Result.map, you need Result.then as well)

wind skiff
#
@overload
async def insert_account(
    self, account: Account, *, fields: tuple[AccountField, ...] = ...
) -> Account: ...

@overload
async def insert_account(self, account: Account, *, fields: None = ...) -> None: ...

async def insert_account(
    self, account: Account, *, fields: tuple[AccountField, ...] | None = None
) -> Account | None:

foo = await repo.insert_account(account) # type of foo reported by tyep checker is Account but should be None

why is this happening

#

only when i pass fields explicitly as None then the type of foo becomes None

brazen jolt
#

in your overloads, both have a default value (elipsis), omit the default value for fields where it doesn't match the one from the implementation

wind skiff
brazen jolt
#

you should, because for the None case (2nd overload) it does have a default

wind skiff
#

yeah so what do i do to fix?

brazen jolt
#

the issue here is that with your first overload, you have: ```python
@overload
async def insert_account(
self, account: Account, *, fields: tuple[AccountField, ...] = ...
) -> Account: ...

#

notice that there's fields: tuple[AccountField, ...] = ...

#

which means you're telling the type-checker that fields has a default value and doesn't need to be specified

#

with elipsis there, type-checker thiks the default value will just be the type you typed it to be (tuple[AccountField, ...])

#

just omit the default there

#

e.g. make it just: ```python
@overload
async def insert_account(
self, account: Account, *, fields: tuple[AccountField, ...]
) -> Account: ...

#

that way fields becomes required

wind skiff
#

oh so if i do = ... that means the default value is the type i give to fields?

brazen jolt
#

for this overload to apply

trim tangle
wind skiff
#

yeah thats what i knew

brazen jolt
wind skiff
#

i thought if the argument is optional in the original function you have to keep it like that in every overload 💀

trim tangle
#

The important point is that overloads are checked in order

brazen jolt
wind skiff
#

what even the order matters

brazen jolt
#

well in this case it does

#

think about what the type-checker would do here

wind skiff
#

thats why i remember this working before

brazen jolt
#

if you tried to match the type signature of the call

#

you call the function with just Account as the first arg

wind skiff
#

when i pass fields=None explictly how does it match that?

brazen jolt
#

so it goes to check which overload applies there

#

it first evaluated the overload that was defined first

wind skiff
#

i have told it that fields is None when optional right

brazen jolt
#

and for that, yeah, the fields is optional, the overload matches

#

so the type checker stops there and thinks that it found the right overload

trim tangle
#

If possible, I would write overloads in a non-overlapping way, so that you don't have to think about their order ```py
@overload
async def insert_account(self, account: Account, *, fields: tuple[AccountField, ...]) -> Account: ...
@overload
async def insert_account(self, account: Account, *, fields: None = ...) -> None: ...

wind skiff
#

if we say fields: None = ... does it mean when fields is optional it is None or does it mean match this overload when field is optional and None

trim tangle
#

It will match the second overload when you call insert_account(account) and insert_account(account, fields=None)

brazen jolt
wind skiff
#

then why did it match a overload which had optional ... but type tuple[AccountField] when the function clearly has a default of None? does it just check what is the first overload which is optional?

brazen jolt
#

it has no idea that the actual default for fields is None at that point

#

it just goes one by one and checks whether one of the overloads can match the way the function was called

wind skiff
#

oh that makes sense, then it just sees the first optional overload

trim tangle
#

Why does it return None if you don't pass any fields though? Why not always return an Account?

wind skiff
#

fields define what you wanna get back from the db

#
@overload
async def insert_account(
    self, account: Account, *, fields: tuple[AccountField, ...]
) -> Account: ...

@overload
async def insert_account(self, account: Account, *, fields: None = ...) -> None: ...

async def insert_account(
    self, account: Account, *, fields: tuple[AccountField, ...] | None = None
) -> Account | None:
    """Insert an account."""
    cols, vals = account.to_columns_and_values()
    stmt = sql.insert(self.table).columns(*cols).values(*vals)
    if fields is not None:
        stmt.returning(*fields)
        return await self.driver.select_one(stmt, schema_type=Account)

    await self.driver.execute(stmt)
    return None
trim tangle
wind skiff
trim tangle
#

Why read the fields of what you just inserted though? 🤔

#

server-side defaults?

wind skiff
brazen jolt
#

some dbs auto-fill defaults or use increments / autogenerated IDs, etc. I can see wanting to obtain that back as useful, though unless you really care about performance here, I'd probably choose to return an entire instance back every time

wind skiff
#

i am not using a orm here so i need to do the filling and all myself

brazen jolt
#

(or rather, either an entire instance or nothing, not a partial instance)

trim tangle
#

yeah I forgot about server generated things

#

I think it always makes sense to specify the exact fields you need. You never know when a table gets a column with large values

wind skiff
#

yeah

#

thats why

trim tangle
#

especially if you only want the ID

brazen jolt
#

I suppose that's fair yeah

wind skiff
#

when the record gets big its not a good idea to always return *

bleak imp
# trim tangle Try rewriting this using proper functions instead of lambdas. You'll see the mis...

Ah, I see. I figured it out, I should be doing map to the result of other.parse, instead of the outer map's result.
The one issue with that is I need a flatten op. That isn't too hard to make as a standalone function, but I'd also like it to be a method, but am having trouble with the self types. Could I get some help? code (still using basedpyright playground but the link is now too long for a discord message). The flatten standalone function works, but the methods do not.

#

Huh, interestingly just moving the flatten function upwards and doing flatten = flatten in both Ok and Err works. I wonder if I should do that for all of the methods? It doesn't seem to give errors for wrong self types though, which isn't the best, but does at least work

#

Ah, I got it, it looks like using the classes own generics is what makes it not work, if I make the method have two new generics it works out

bleak imp
#

The extra funny part is that even now that I have all these advanced result methods working, for basically anything complex I'm not even using then and just going back to match since the errors are so unreadable xD

#

And in fact, that has just given me another revelation: For a bunch of these non-parser-internal parsers I've been struggling on, since I was smart and made it so that they work exactly like the parser internal methods, I can also just write them the same way using mostly only matches to get a much more sensical error output.

digital pewter
#

eek sorry

def bar(xs: list[int] | list[float]): ...

def foo[T: (int, str)](xs: list[T]): 
    bar(xs)

we're saying this is valid for mypy?

trim tangle
#

yes

digital pewter
#

it's flagging for me :\

trim tangle
#

s/float/str

digital pewter
#

yes i happen to be

#

a fool

#

but you did say it is valid 😉

#

thanks again

trim tangle
#

i have more bugs than mypy

#

btw @digital pewter , the reason mypy doesn't error is that, when encountering constrained typevars, it just instantiates the function with all the possible combinations and type checks them separately. Here is one side effect

class Ye: pass
class No: pass

class Foo[A: (Ye, No), B: (Ye, No), C: (Ye, No)]:
    def __init__(self) -> None:
        reveal_type(self)
``` ```
main.py:6: note: Revealed type is "__main__.Foo[__main__.Ye, __main__.Ye, __main__.Ye]"
main.py:6: note: Revealed type is "__main__.Foo[__main__.Ye, __main__.Ye, __main__.No]"
main.py:6: note: Revealed type is "__main__.Foo[__main__.Ye, __main__.No, __main__.Ye]"
main.py:6: note: Revealed type is "__main__.Foo[__main__.Ye, __main__.No, __main__.No]"
main.py:6: note: Revealed type is "__main__.Foo[__main__.No, __main__.Ye, __main__.Ye]"
main.py:6: note: Revealed type is "__main__.Foo[__main__.No, __main__.Ye, __main__.No]"
main.py:6: note: Revealed type is "__main__.Foo[__main__.No, __main__.No, __main__.Ye]"
main.py:6: note: Revealed type is "__main__.Foo[__main__.No, __main__.No, __main__.No]"
#

So it synthesizes

def foo1(xs: list[int]): 
    bar(xs)
# and
def foo2(xs: list[str]): 
    bar(xs)
``` and both work
digital pewter
#

oh that's surely a very fast algorithm

#

cool example!

trim tangle
#

Well, the most common usecase for constrained typevars seems to be (str, bytes), so it's only 2 instances

#

so just don't represent a chess board like that and it should be fine

#

speaking of exponential growth, enum.IntFlag will cache all the flag combinations as you're creating them

#

so if you have 32 bits in the enum, you could end up with 2**32 enum instances cached

#

IIRC it has to do that because enum instances must be comparable with is

bleak imp
#

Why aren't type checkers able to rule out the never branch of this code? This happens with pyright, mypy, and ty, so am I missing something? Both Ok and Err are final, so there's no issue where there could be a possible subclass that inherits from both. Both T and E should be covariant, which ty confirms, so as far as I can tell Err[Never] should not be constructible. So why is it that none of these type checkers can determine that the Err branch of this code is unreachable? And more probably, what am I missing here?

trim tangle
#

For example: imagine you're making a stub file. How do you express that?

bleak imp
#

Hm. I think I see, for example, list[Never] is not empty, since it has a valid element of [], so as far as the type checkers know Err[Never] could be inhabited despite having Never

trim tangle
#

something like that, yes

bleak imp
#

:/

feral wharf
#

I think this is the correct channel for this.. I can't see my pyright is failing with the same stuff on every commit when it works compleletly fine locally..

failing action's log: https://paste.pythondiscord.com/TJFQ
failing action from workflow:

- name: "Run Pyright @ ${{ matrix.python-version }}"
  uses: jakebailey/pyright-action@v2
  with:
    warnings: false
    verify-types: "bugcord"
    ignore-external: true
    annotate: ${{ matrix.python-version != '3.x' && 'all' || 'none' }}
#

figured out why we ain't seeing it locally

#

because we aren't setting the verifytypes flag

#

still not sure why it's throwing those warnings tho

feral wharf
#

wait, is this not the point of TypeIs?

Return type of TypeIs ("Sequence[_BoundPage | _BoundV2Page | _SelectBoundPage | _SelectBoundV2Page]") is not consistent with value parameter type ("_BoundPage | _BoundV2Page | _SelectBoundPage | _SelectBoundV2Page")
#

oh i think i need TypeGuard

oblique urchin
feral wharf
#

im confused

oblique urchin
#

the error suggests you have a function like def guard(x: _BoundPage) -> TypeGuard[Sequence[_BoundPage]]: (plus some more kinds of pages)

#

that only makes sense if a _BoundPage can also be a Sequence[_BoundPage]

#

which is possible in theory but seems weird

feral wharf
#

oh yeah that's wrong

#

I basically want a function that returns True if input is a sequeue of a valid obj, so that I can do [0] on it. The following seems to work

def _is_sequence_of_pages(
    page: BoundPage | BoundV2Page | SelectBoundPage | SelectBoundV2Page, /
) -> TypeGuard[Sequence[_BoundPage | _BoundV2Page | _SelectBoundPage | _SelectBoundV2Page]]:
    if isinstance(page, Sequence) and not isinstance(page, (str, bytes, bytearray)):
        return True

    return False
type _BoundPage = str | discord.Embed | discord.ui.Button[Any] | discord.ui.Select[Any] | dict[str, Any] | File
type BoundPage = _BoundPage | Sequence[_BoundPage]

type _BoundV2Page = (
    str
    | discord.ui.ActionRow[Any]
    | discord.ui.Container[Any]
    | discord.ui.File[Any]
    | discord.ui.MediaGallery[Any]
    | discord.ui.Section[Any]
    | discord.ui.Separator[Any]
    | discord.ui.TextDisplay[Any]
    | dict[str, Any]
    | File
)
type BoundV2Page = _BoundV2Page | Sequence[_BoundV2Page]

type _SelectBoundPage = _BoundPage | PaginatorOption[Any]
type _SelectBoundV2Page = _BoundV2Page | PaginatorOption[Any]

type SelectBoundPage = _SelectBoundPage | Sequence[_SelectBoundPage]
type SelectBoundV2Page = _SelectBoundV2Page | Sequence[_SelectBoundV2Page]
oblique urchin
#

oh wait the one without an underscore includes Sequences

feral wharf
#

ya

fiery canyon
#

Didn't expect this to work lol

#

Not sure I like that though, users might be confused on whether it's a dict or an obj

#

It's either that, or this..

drowsy wadi
#

Inferred child class type is missing type annotation and could be inferred differently by type checkers

#

If you give it these, does it become happy?

wind skiff
# drowsy wadi If you give it these, does it become happy?

yeah i guess, for example it errors for the following

global_password_hasher = PasswordHasher()


def provide_password_hasher() -> PasswordHasher:
    """Provide password hasher."""
    return global_password_hasher

but doesnt error for the following

global_password_hasher: PasswordHasher = PasswordHasher()


def provide_password_hasher() -> PasswordHasher:
    """Provide password hasher."""
    return global_password_hasher
#

(btw i am working with him so i am aware of the problem)

drowsy wadi
#

Hmm.

#

Is this project open source / source available?

hallow widget
#

Not sure if this a good topic/help channe for Pydantic but either way I'm working with a yaml file:

---
id: example_tweak
name: Example tweak
actions:
  - !registryValue:
    path: 'HKLM:\SOFTWARE\Example'
    name: 'ExampleValue'
    # value: ["value1", "value2", "value3"]
    value:
      - value1
      - value2
      - value3
    type: REG_MULTI_SZ

It contains information on how to automate certain tasks e.g. setting registry values and keys, I read the yaml like so:

from aegis.models import Tweak
import yaml

class YamlHandler:
    def __init__(self, file_path: str):
        self.file_path = file_path

    def read_yaml(self) -> Tweak:
        with open(self.file_path, "r", encoding="utf-8") as file:
            data = yaml.safe_load(file)
            print(data)
            return Tweak(**data)

Here is the tweak model:

from aegis.actions.base import Action

class Phase(str, Enum):
    SPECIALIZE = "Specialize"
    DEFAULT_USER = "DefaultUser"
    FIRST_LOGON = "FirstLogon"
    USER_ONCE = "UserOnce"

@dataclass(config=ConfigDict(arbitrary_types_allowed=True))
class Tweak:
    id: str
    title: str | None = Field(default=None, alias="name")
    description: str | None = None
    phase: Phase = Field(default=Phase.SPECIALIZE)
    enabled: bool = True
    category: str | None = "uncategorized"
    depends_on: list[str] = Field(default_factory=list)
    build: str | int | None = None
    actions: list[Action] = Field(default_factory=list)
rough sluiceBOT
hallow widget
rough sluiceBOT
hallow widget
#

But if you look at line 35:

value: list = Field(default_factory=list)

This ends up not recognizing the list of strings, sample output:

actions=[
  RegistryValueAction(path='HKLM:\\SOFTWARE\\Example', name='ExampleValue', type=RegistryValueType.REG_MULTI_SZ, value=[])
]
```py

But if I use the `Any` type from `typing` lib:
```py
value: Any

I get this:

actions=[
  RegistryValueAction(path='HKLM:\\SOFTWARE\\Example', name='ExampleValue', value=['value1', 'value2', 'value3'], type=RegistryValueType.REG_MULTI_SZ)   
]
#

Not sure what the problem could be and I could get away with using Any but it's of course not ideal.

trim tangle
#

It's really strange that it produces an empty list instead of an error pithink

trim tangle
hallow widget
#

This is my own YAML format, I have yet to make a JSON Schema to get intellisense and stuff but it's my own.

#

I handle that !registryValue tag as well:

import yaml

class Action:
    yaml_tag: str = None

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        tag = getattr(cls, "yaml_tag", None)
        if tag:
            for variant in (tag, f"{tag}:"):
                yaml.add_constructor(variant, cls._from_yaml, Loader=yaml.SafeLoader)
            yaml.add_representer(cls, cls._to_yaml, Dumper=yaml.SafeDumper)

    def __repr__(self):
        attrs = ', '.join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{self.__class__.__name__}({attrs})"

    @classmethod
    def _from_yaml(cls, loader, node):
        values = loader.construct_mapping(node)
        return cls(**values)
    
    @classmethod
    def _to_yaml(cls, dumper, data):
        return dumper.represent_mapping(cls.yaml_tag, data.__dict__)
    
    # abstract methods
    def execute(self):
        raise NotImplementedError("Subclasses must implement the execute method.")
    
    def backup(self):
        raise NotImplementedError("Subclasses must implement the backup method.")
    
    def rollback(self):
        raise NotImplementedError("Subclasses must implement the rollback method.")
trim tangle
#

apparently pydantic doesn't support discriminated union where the field is in the outer container... bummer

hallow widget
#

Wait but doesn't it fail because if I were to use list it would expect that no matter the type so even if the type was REG_DWORD which does not accept a list it would still expect that?

#

I'm gonna try make use of a model_validator because I kinda want this syntax:

value:
  - value1
  - value2
  - value3
hallow widget
#
@dataclass
class RegistryValueAction(Action):
    yaml_tag = u"!registryValue"

    path: str
    name: str
    value: Any
    type: RegistryValueType

    @model_validator(mode="after")
    @classmethod
    def check_value(cls, data):
        reg_type = data.type
        if reg_type in {
            RegistryValueType.REG_DWORD,
            RegistryValueType.REG_QWORD,
            RegistryValueType.REG_DWORD_LITTLE_ENDIAN,
            RegistryValueType.REG_QWORD_LITTLE_ENDIAN
        }:
            if isinstance(data.value, str) and data.value.isdigit():
                data.value = int(data.value)
            elif not isinstance(data.value, int):
                raise TypeError(f"Value must be an integer for type {reg_type}")
        elif reg_type in {
            RegistryValueType.REG_SZ,
            RegistryValueType.REG_EXPAND_SZ,
            RegistryValueType.REG_LINK
        }:
            if isinstance(data.value, int):
                data.value = str(data.value)
            elif not isinstance(data.value, str):
                raise TypeError(f"Value must be a string for type {reg_type}")
        elif reg_type == RegistryValueType.REG_MULTI_SZ:
            if not (isinstance(data.value, list) and all(isinstance(i, str) for i in data.value)):
                raise TypeError(f"Value must be a list of strings for type {reg_type}")
        elif reg_type == RegistryValueType.REG_BINARY:
            if isinstance(data.value, str):
                    data.value = data.value.encode()
            elif not isinstance(data.value, (bytes, bytearray)):
                raise TypeError(f"Value must be bytes for type {reg_type}")
        elif reg_type == RegistryValueType.REG_NONE:
            if data.value is not None:
                raise TypeError(f"Value must be None for type {reg_type}")
        return data

I'm just validating it with a model_validator, instead of relying on type hinting I guess.

#

I don't like this approach but I really want this syntax to work:

value:
  - value1
  - value2
  - value3
#

Plus I found discriminated unions extremely confusing because I'm not sure how I would even use a discriminator I haven't worked enough with Pydantic to understand.

#

And I don't want to have to nest it like this:

payload:
  type: REG_MULTI_SZ
  value: [value1, value2]

I want it like flat:

type: REG_MULTI_SZ
value: [value1, value2]
trim tangle
#

yeah I don't know pydantic well enough to figure out how to make it work

hallow widget
#

I'm not sure if my code now follows like good practice because the value parameter accepts Any but as long as I validate which I do I think it should be fine I suppose.

trim tangle
#

your situation is exactly what discriminated unions are supposed to solve

#

pydantic just doesn't implement that

little hare
#

I have questions about this

nitpick: dict[str, object] (avoid Any unless really necessary)

#

What do people think about using object over Any or Any over object

#

This seems to be advice, but for invariant generics it doesn't

trim tangle
little hare
#

but in convarient objects its kind of annoying and mistyped

#

I'd rather have unspecification rather than my typechecker complaining that I'm using an attribute that doesn't exist in a dictionary

trim tangle
# little hare somewhat

Any means that you are allowed to do anything with the object:

foo: dict[str, Any] = ...

bar = foo["bar"]
bar.quack()  # allowed
bar += 1 # also allowed
baz: int = bar  # also allowed

For "statically typed code", it's in the same drawer of tools as typing.cast and # type: ignore, it turns off type checking for part of your program

#

If you are annotating the parameter of a function, you are correct that dict[str, object] is probably not ideal. If you intend to only read from the value, use Mapping[str, object]

#

dict[str, Any] is okay if you're dealing with untyped JSON and you're totally fine if the code is making an incorrect assumption about its shape

#

So what I meant was: "when it makes no difference otherwise, use object instead of Any to prevent future mistakes"

fiery canyon
#

Yea I use dict[str, Any] where I'm too lazy to parse the dict

#

Not even about being lazy sometimes, there's times where I think it's genuinely useless to type a dict

#

For example a class that takes dict parameter to make self attrs from its keys

#

Depends where and when.

viscid spire
fiery canyon
#

Why does it matter

#

Or how is it better

feral wharf
#

Or just define a TypedDict for the known keys, like a good codebase would Shrug

fiery canyon
#

Just not so necessary

#

That's something I wouldn't do even if I decided to parse all objects ngl

feral wharf
#

It is necessary for a fully typed library

#

Else yeah do whatever

fiery canyon
#

Main reason I have objects such as the ones here unparsed is recursive objects, feel like it'd be pointless to make full on objects for my small project

#

Maaaybe I'll make a TypedDict for the top level keys

feral wharf
#

(Public) Discord objects are (mostly) fully documented too

#

Stop being lazy smh

fiery canyon
#

Well yea it's just that I'll "have" to make classes for everything lol (take flags and color for example)
not the worst thing ever but adds a big portion of code that feels unrelated to the main thing

#

I might just skip the types 💀

feral wharf
#

And? It's not like it'll slow down your code or anything. It'll give a nicer experience to people using your lib and potentially extending it or even maintain it.

Embrace the OOP.

fiery canyon
#

hmm maybe ill try then

feral wharf
#

Wait you get full objects with the events?

fiery canyon
#

Nawr would rather only make what I need

feral wharf
#

Fair

fiery canyon
#

Is there a way to tell type checker that the object that will subclass this does have id?

#

This worked, is that ideal?

feral wharf
#

How would Mentionable be an instance of HasID there

restive rapids
#

HasID is a protocol, so isinstance(x, HasID) is basically hasattr(x, "id")
Mentionable by itself shouldn't be instantiated though, so i think it should be an ABC

feral wharf
#

Oh right

#

But why not add the id classvar to the Mentionable class instead

fiery canyon
#

My intention is for classes to subclass Mentionable

feral wharf
#

And those classes will not have an id attribute?

fiery canyon
#

they will

restive rapids
#

methinks something like

from abc import ABC, abstractmethod


class Mentionable(ABC):
    @property
    @abstractmethod
    def id(self) -> int:
        ...
    @id.setter
    @abstractmethod
    def id(self, new: int) -> None:
        ...
    @property
    def mention(self) -> str:
        return f"<@{self.id}>"

class User(Mentionable):
    def __init__(self, id: int):
        self.id = id

User(42)

sadly without a setter you'd have to go through something like

self._id = id
@property
def id(self):
  return self._id

idk
ts sucks 🥀

restive rapids
# fiery canyon or this

if you just do id: int in Mentionable, then it is not an error to instantiate it by itself, even though its wrong

fiery canyon
#

eh the typing works

feral wharf
#

Then it's a mixin no?

fiery canyon
#

(:

fiery canyon
#

Welp that took a while

#

Will be more if I parse/make classes for Color, Flags

feral wharf
fiery canyon
#

Did I get the concept of TypedDict right

#

AKA making those even for tiny dicts

feral wharf
#

Usually they're in a separate file

#

But yes

#

I feel like banner and avatar could be one class called Asset or something

fiery canyon
#

Yea i wont keep everything thrown in one file, i think

#

Ngl I looked in dpy code for reference for some stuff lol

#

But yeah what they did there is complete overwork for what I'm trying to achieve

#

Except for the basic stuff

stiff acorn
#

Can I (and should I) bump a typeshed PR that's been sitting for about ~2 weeks?

stable fjord
#

looks reasonable to bump

terse sky
#

I upgraded my python environment, and I'm now getting an error on pandas.to_pickle

#

Even though it was just fine before

#

I can see to_pickle defined in pandas-stubs

#

How would I go about debugging this?

#

The error is just that "to_pickle is not a known module attribute of pandas"

#

The code works at runtime, to be clear

terse sky
#

mypy

#

Sorry

trim tangle
terse sky
#

2.3.2

#

Pandas is 2.3.3

#

I wonder if the versions need to be identical. But then that should have been expressed by version constraints

trim tangle
#

there's 2.3.2.250827 and 2.3.2.250926

terse sky
#

I have the latter

terse sky
#

It's in another file

trim tangle
#

If it's supposed to be exported from the pandas module itself, then it needs to be in this stub file

terse sky
#

I assumed a star import somewhere

#

Looks like there are none

#

So the stubs are bugged I guess?

trim tangle
#

maybe the stubs were bugged before?

terse sky
#

I didn't have the error before though

trim tangle
#

Seems like the pandas package doesn't document or advertise the fact that to_pickle is exported from the root pandas package

#

(on the docs site)

rough sluiceBOT
#

pandas/__init__.py line 149

to_pickle,```
trim tangle
#

and lists it in __all__

terse sky
#

Ok cool for a sec I thought it wasn't public API

#

Going to revert pandas-stubs and see

terse sky
#

Yeah it used to work

#

This is pretty dumb IMHO

#

pandas-stubs should be type checking what pandas offers

#

Not making its own judgement calls

#

pandas has this in all, even if it's not choosing to document it

trim tangle
#

that could be for backwards compatibility

#

but at the same time, it's not deprecated 🤷‍♂️

terse sky
#

Yeah

#

If it's not deprecated in the actual code then removing this is incorrect

#

IMHO

#

It's also like... What's the actual benefit of this

#

For the pandas-stubs project

#

Miniscule

#

But creates pointless work for users of pandas

trim tangle
#

seems like they wanted to deprecate it in a quick follow up in 2022, but then they just kept procrastinating

#

python has an unusual kind of deprecation called "is planned to be deprecated"

terse sky
#

Yeah, I can certainly appreciate that

#

But there was really no need to rush removal from the stubs

trim tangle
#

certainly

#

especially now that we have warnings.deprecated

terse sky
#

Debating whether to create an issue for this

#

I'm not really sure how to handle this

trim tangle
#

if it's planned to be deprecated, maybe just change your code to import it from the correct module?

terse sky
#

It's not just an import

#

They do add some functionality

#

And it's hard to justify actually changing code when it works fine and they may simply one day deprecate it

trim tangle
#

hm?

#

it's re-exported from pandas.io.pickle to pandas

terse sky
#

Ah, I see

#

But does the stub cover that is the question

trim tangle
#

nope

terse sky
#

So it doesn't help me at all does it

trim tangle
#

🥲

terse sky
#

Lol

#

Like... Am I really going to copy and paste this function into my codebase

#

Because one day it might be deprecated

#

Seems crazy

#

Alternative I guess is there's probably a way to suppress this specific error codebase wide in mypy

#

Those are the only two real options

#

I could stay on the old stubs file for now but that's not really a long term solution

terse sky
#

Is there actually a way to do this? Suppress all mypy errors for a particular function

#

Inlining the function into my codebase is definitely a pain in the ass

terse sky
#

That's awful

#

I was looking into whether I can have my own stubs file that can add more stubs

trim tangle
#

external stubs will suffer from this kind of problem, when they're a second class sort of thing

rare scarab
#

Have you considered fixing the function's types?

trim tangle
#

?

rare scarab
#

If you don't like vendoring stubs, fix it upstream

terse sky
terse sky
rare scarab
#

It's a very long conversation. I'll stay out of it.

terse sky
#

So I don't believe it is second class, but I could be wrong

terse sky
#

I guess I will open an issue, talked to someone else who's opinion I trust on these matters who agrees this is not the way to do things

rare scarab
#

So the stubs removed it instead of adding @typing.deprecated? Is it even deprecated yet?

terse sky
#

It's not even deprecated

#

In the actual project

#

And the stubs just removed it

trim tangle
#

I guess the advantage is that you can release them at a different cadence?

terse sky
#

Yeah

#

I guess I will just pin the stubs and open an issue.

#

Maybe I'll get lucky

drowsy wadi
terse sky
#

How can I fix the functions types if it's not there anymore 🤔

feral wharf
rare scarab
feral wharf
#

not if I stringify them?

rare scarab
#

I believe discord.ui.Button's generic args are important at runtime.

fiery canyon
feral wharf
feral wharf
fiery canyon
#

What's the point of conditional typing imports?

#

I mean it's not like it'll take much more memory for users without type checker

feral wharf
#

circular imports and I don't need them at runtime

fiery canyon
#

hmm alright then

bleak imp
#

Why do mypy, pyright, and pyrefly all treat Callable as always having a __name__? ty is the only one that works correctly on this, since this code does give a NameError at runtime, so why are all the other type checkers consistently not reporting it?

from typing import Callable

def foo(func: Callable[..., object]) -> None:
    print(func.__name__)

class A:
    def __call__(self) -> None:
        pass

foo(A())
viscid spire
#

I think the general issue is that there is no way to type that something is a function specifically (which do have __name__) in a generic way

#

so I'm guessing that they said "Callables are functions"

bleak imp
#

Hm, but you can make a callable protocol with a __name__ attribute

viscid spire
#

you could yeah

oblique urchin
viscid spire
#

I'm glad to see that **P accepts [] and ...

drowsy wadi
feral wharf
#

can we overload on optional parameters with a required order using a protocol, or in general?

class SetItemCallbackProto(Protocol):
    @overload
    def __call__(
        self, interaction: Interaction, view: BaseView, item: ViewItem
    ) -> Coroutine[Any, Any, Any]: ...

    @overload
    def __call__(
        self, interaction: Interaction, view: BaseView
    ) -> Coroutine[Any, Any, Any]: ...

    @overload
    def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: ...

    def __call__(
        self,
        interaction: Interaction = ...,
        view: BaseView | None = ...,
        item: ViewItem | None = ...,
    ) -> Coroutine[Any, Any, Any]: ...
#

this "works", but it only cares about the type of the first param and whether there aren't any extra params

_SelectItemCallbackType = Callable[[], Coroutine[Any, Any, Any]]

_SetItemCallbackType_Interaction = Callable[[Interaction], Coroutine[Any, Any, Any]]
_SetItemCallbackType_InteractionView = Callable[[Interaction, V], Coroutine[Any, Any, Any]]
_SetItemCallbackType_InteractionViewItem = Callable[[Interaction, V, I], Coroutine[Any, Any, Any]]

SetItemCallbackType = (
    _SelectItemCallbackType

    | _SetItemCallbackType_Interaction
    | _SetItemCallbackType_InteractionView
    | _SetItemCallbackType_InteractionViewItem
)
#

usage:

class Foo:
  def set_callback(func: <type>) -> None:
    ...

inst = Foo()

async def my_callback():
# async def my_callback(interaction: Interaction):
# async def my_callback(interaction: Interaction, view: View):
# async def my_callback(interaction: Interaction, view: View, item: Item):
  ...

inst.set_callback(my_callback)
#

wait a sec

#

V and I are typevars

#

okay that works kinda

#

it doesn't handle subclasses??

#

bruh

trim tangle
trim tangle
feral wharf
#

Not really

trim tangle
# feral wharf Not really
class Animal: pass

class Cat(Animal):
    def meow(self): print("meow")

class Dog(Animal):
    def bark(self): print("woof")

def handle_animal_callable(fn: Callable[[Animal], None]) -> None:
    fn(Cat())
    fn(Dog())

def handle_just_cats(cat: Cat) -> None:
    cat.meow()

handle_animal_callable(handle_just_cats)
``` do you see why the last call is problematic?
feral wharf
#

Oh

#

Yes

#

But, what if I only care about the class

trim tangle
trim tangle
feral wharf
feral wharf
#

Since Animal already implements all the methods I need

trim tangle
#

are you ever going to call this function?

trim tangle
#

If the function must accept an arbitrary animal, it cannot be annotated as accepting only cats

#

If you are inspecting annotations at runtime and deciding what argument to pass based on the annotation, then the best you can do is probably using Any as the argument type. And then validating the argument at runtime

feral wharf
#

The passed function will be called with the three optional arguments

feral wharf
hasty phoenix
#

__getitem__ when used on bytes return int when the size is 1. How can I type annotate that?

#

I think I can answer that myself:

    @overload
    def __getitem__(self, index: int) -> int: ...
    @overload
    def __getitem__(self, index: slice) -> bytes: ...
    def __getitem__(self, index: int|slice) -> bytes|int:
viscid spire
#

seems right

hasty phoenix
#

I wanted to ask how to extend this to a method get() in an ABC. But I think I might have found the solution while prepping the question. Overloads must be repeated in the subclass too it seems:

class A(ABC):
    @overload
    def get(self, size: Literal[1] = 1) -> int: ...
    @overload
    def get(self, size: int) -> int|bytes: ...
    @abstractmethod
    def get(self, size: int = 1) -> bytes|int:
        """ Get size bytes """

class B(A):
    @overload
    def get(self, size: Literal[1] = 1) -> int: ...
    @overload
    def get(self, size: int) -> int|bytes: ...
    def get(self, size: int = 1) -> bytes|int:
        if size == 1:
            return 42
        else:
            return b'hello'
viscid spire
#

indeed, otherwise, you are saying that the return type does not depend on the input type in the subclass

trim tangle
#

that seems like an unfortunate interface though

trim tangle
hasty phoenix
trim tangle
#

That's gotta be a very annoying interface to consume as a user. After you provide the size, you have to check whether you've got bytes or int back.

hasty phoenix
#

Is __getitem__(self, index: int|slice) -> bytes|int any better?

viscid spire
#

your case is a bit different, because you are changing the return type based on type or subtype of the input

trim tangle
#

even if the slice is empty or of size 1

viscid spire
#

it will not get caught by Literal[1]

hasty phoenix
trim tangle
viscid spire
#

so in practice, you mostly have a non-overloaded function

#

which returns int | bytes variably

viscid spire
trim tangle
#

redis infamously added async to their type annotations in this way (pseudocode):

class Client:
    def hlen(self, name: str) -> int | Awaitable[int]:
``` If I had to use that more than twice, I would actually ragequit and write my own stubs. Or a new library. Or use a different programming language
viscid spire
#

insane

trim tangle
#

I mean, libraries aren't required to play well with type checkers. In that case they could've just left it as Any or something

hasty phoenix
#

It isn't always easy to know what's right to do in typing, and devs aren't always to keen on fixing typing annotations is my experience

trim tangle
trim tangle
#

ah

#

then you don't have a better option indeed

viscid spire
#

refactor

trim tangle
#

This kind of special casing is extremely annoying, even if you never use a type checker. It means you cannot write code in a generic way

#

e.g. you cannot just compute a length through some generic way (e.g. how much items do I need to get to fill up my buffer?) and use the result as is

#

and all you get from this special case is the ability to write foo.get(1) instead of foo.get(1)[0] or foo.get_one()

hasty phoenix
#

jep

raw valve
#

Is there any way to make this work?

class MyClass:
    def _target_method(self, a: int, b: str) -> str:
        return f"Target: {a}, {b}"

    def delegating_method(self, *args: P.args, **kwargs: P.kwargs) -> R:
        # Here, we're assuming the target method is known and its signature is what P represents.
        # In a more general case, you might pass the target method as an argument.
        return self._target_method(*args, **kwargs)

pylance says P has no meaning in this context

#

It seems you cannot use ParamSpec without callable? Meaning its really only for a decorator. No way to say a method delegates its arguments to another method?

rare scarab
#

Make a decorator that returns the input function type

raw valve
#

I dont want to bc in my context _target_method is an abstract method on an abc

raw valve
#

nothing so I cant do it. There's no way to type hint that is there?

rare scarab
#
def forwards[R, **P](fn: Callable[P, R]) -> Callable[[Callable[..., R]], Callable[P, R]]:
    def decorator(func):
        return func
    return decorator
#

Basically a no-op type assertion decorator

raw valve
#

I decorate _target_method?

rare scarab
#

Yes, pass in the function you're calling

raw valve
#

interesting

#

let me try

#

To be clear bc I am using python 3.10

from typing import ParamSpec, TypeVar

R = TypeVar("R")
P = ParaSpec("P")

def forwards(func: Callable[P, R]) -> Callable[[Callable[..., R]], Callable[P, R]]:
    def decorator(func):
        return func
    return decorator

class MyClass:
    @forwards
    def _target_method(self, a: int, b: str) -> str:
        return f"Target: {a}, {b}"

    def delegating_method(self, *args: P.args, **kwargs: P.kwargs) -> R:
        # Here, we're assuming the target method is known and its signature is what P represents.
        # In a more general case, you might pass the target method as an argument.
        return self._target_method(*args, **kwargs)
#

you're saying that should work?

rare scarab
#

Use it like @forwards(_target_method)

raw valve
#

I think I finally get you

from typing import ParamSpec, TypeVar

R = TypeVar("R")
P = ParaSpec("P")

def forwards(func: Callable[P, R]) -> Callable[[Callable[..., R]], Callable[P, R]]:
    def decorator(func):
        return func
    return decorator

class MyClass:
    
    def _target_method(self, a: int, b: str) -> str:
        return f"Target: {a}, {b}"

    @fowards(_target_method)
    def delegating_method(self, *args: P.args, **kwargs: P.kwargs) -> R:
        # Here, we're assuming the target method is known and its signature is what P represents.
        # In a more general case, you might pass the target method as an argument.
        return self._target_method(*args, **kwargs)

Still getting same warning from pylance though

rare scarab
#

You won't get type hinting inside the method, but you'll get proper types when you call it.

raw valve
#

ok so the P has no meaning in this context will stay

#

Alright I guess I can live with that, thanks

rare scarab
#

Just remove it

raw valve
#

You dont need it?

rare scarab
#

Nope. You can just use Any

#

The decorator will fix the types for callers

raw valve
#

now if _target_method is an abstract method that subclasses will implement, this wont work right?

rare scarab
#

As long as it's defined

raw valve
#

The type checker is smart enough for that? Interesting

#

This was very educational and that no op decorator is slick

rare scarab
#

No, the decorator is me holding the type checkers hand

#

And also technically lying

raw valve
#

I just thought the type checker would some reason only lookat the initially passed abstractmethod on the ABC

rare scarab
#

The decorator makes the type checker think the type of the function is the same as the one you passed in.

#

But in reality, the decorator is no-op

raw valve
#
from typing import TypeVar, ParamSpec, Callable
from abc import ABC, abstractmethod

R = TypeVar("R")
P = ParamSpec("P")

def forwards(func: Callable[P, R]) -> Callable[[Callable[..., R]], Callable[P, R]]:
    def decorator(func):
        return func
    return decorator

class Foo(ABC):

    @abstractmethod
    def compute(self, *args, **kwargs) -> int:
        ...

    @forwards(compute)
    def __call__(self, *args, **kwargs) -> int:
        return self.compute(*args, **kwargs)

class Bar(Foo):

    def compute(self, a:int, b: int) -> int:
        return a+b

b = Bar()
b("a", 1) # No rejection
#

That doesn't get rejected but I'd assume it would by my understanding of what u said

viscid spire
raw valve
#

eh no

#

it works if I explicitly define compute hints on Foo

#

well yeah, so you're both right

#

It ignores the subclass but if the abstract has the same method signature then it works

raw valve
#

but if i Know the method signature then __call__ bit is useless

viscid spire
#

maybe don't do this compute --> _call_ thing

raw valve
#

im leaning towards that

#

Im really exploring right now

viscid spire
#

fairnuff

raw valve
#
from typing import TypeVar, ParamSpec, Callable
from abc import ABC, abstractmethod

R = TypeVar("R")
P = ParamSpec("P")

def forwards(func: Callable[P, R]) -> Callable[[Callable[..., R]], Callable[P, R]]:
    def decorator(func):
        return func
    return decorator

class Foo(ABC):

    @abstractmethod
    def compute(self, a: int, b:int) -> int:
        ...

    @forwards(compute)
    def __call__(self, *args, **kwargs) -> int:
        return self.compute(*args, **kwargs)

class Bar(Foo):

    def compute(self, a:int, b: int) -> int:
        return a+b

b = Bar()
b("a", 1) # rejected
trim tangle
#

@raw valve There's no way to automatically "pick up" the signature of the method inherited by a subclass. You could do something like: ```py
class MyClass**P, R:
@abstractmethod
def _target_method(self, *args: P.args, **kwargs: P.kwargs) -> R: ...

def delegating_method(self, *args: P.args, **kwargs: P.kwargs) -> R:
    return self._target_method(*args, **kwargs)

class Impl(MyClass[[int, str], bool]):
def _target_method(self, foo: int, bar: str) -> bool:
return foo == len(bar)

viscid spire
#

pyright still complains about __call__ in Foo in Melen's code above

raw valve
viscid spire
trim tangle
raw valve
#

boy the world of typing is new to me

#

alright ill play with this

viscid spire
#

Typescript makes relating function types together easier

#

but that's easier to do because it defines its own grammar, instead of being built into runnable JS

trim tangle
#

typescript really makes you feel sadness when you go back to python's typesystem

raw valve
#

Yeah well I'm trying to apply typing and use it a gauge on if i'm going doing a good path or not on design

#

like if I cannot type hint, probably bad

#

idk how valid that is though

#

Cuz like some pretty major libraries do whacky shit that cant be type hinted

#

like pandas

trim tangle
#

If it's very important that the code is statically analyzed as much as possible, then this is a decent metric. It also means that you probably picked the wrong programming language

#

Otherwise, I think it's just a symptom of type hints not being as powerful as we'd like them to be

viscid spire
#

typehints make it easier to write good python code

#

it's not a "I will use python because it has typehints"

trim tangle
raw valve
#

i'm also worried about making barrier to entry too high, if I do all this generic stuff idk if others at will be able to do it

#

I'm struggling and im the lead

trim tangle
#

oh, it's a real work project?

raw valve
#

yeah im spit balling an interface right now

trim tangle
raw valve
#

nope

trim tangle
#

are your teammates familiar with at least one statically typed language?

#

or just Python?

raw valve
#

most are a couple years out of college at best and have no real programming experience outside the job which has been just python

#

i mean im only 6 years at work but i do open source contributions and am reading discourse constantly

#

so i know a good bit of the language fundamentals

viscid spire
#

that will make it tricky

trim tangle
trim tangle
raw valve
#

We've been running mypy (in CI) and working towards its on some older projects and am trying to do it 100% clean going forward

#

but yeah its fairly new to us all

trim tangle
#

You can throw the mypy docs at the teammates, specifically "First steps" and "The type system reference"

drowsy wadi
#

I suppose... the rest of my life has taught me that this is an unusual trait thinkster

raw valve
#

asumming we did the typing.overload decorator correctly

fiery canyon
#

Let's say an API response has 2 possible data structures, and I have a TypedDict for each.
The data is passed to a class - there, how can I validate which TypedDict am I actually parsing? Since it's conditional

rare scarab
#

Is the union discriminated? Meaning a key whose value determines the type.

fiery canyon
#

No

rare scarab
#

Check for the presense of one of the keys

fiery canyon
#

Actually hm maybe?

#

I mean this necessarily happens

#

In my context

trim tangle
fiery canyon
#

Figured I'd make this file for users to be able to use some enums

#

So far I've only parsed user

#

But I'm making good progress in the objects framework

feral wharf
#

Hot

fiery canyon
#
  1. Is Any the appropiate type annotation for other in __eq__? Saw some other code use object.
  2. How's this (I mean to ask if it's good enough) for a minimal Color class in context of the Discord API? xD
#

Note that my library doesn't have any built in methods to send anything therefore I did not include any from_x classmethods.

novel notch
fiery canyon
novel notch
#

Then I would type it as Color and maybe not even bother to runtime check the type of other.

rare scarab
#

You're already using isinstance on it

novel notch
#

You want the user to see a type error/warning as soon as possible (in their editor).

rare scarab
#

__eq__ is supposed to support all objects.

#

if x == y raises a TypeError, that's a bug.

restive rapids
rough sluiceBOT
# restive rapids !e ```py class A: def __hash__(self): return 0 def __eq__(self, other): ...

:x: Your 3.14 eval job has completed with return code 1.

001 | Traceback (most recent call last):
002 |   File "/home/main.py", line 8, in <module>
003 |     d[B()]
004 |     ~^^^^^
005 |   File "/home/main.py", line 4, in __eq__
006 |     if not isinstance(other, A): raise TypeError("A.__eq__ other is not an A")
007 |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
008 | TypeError: A.__eq__ other is not an A
restive rapids
#

(here's an example of why: there might be things using __eq__ that are not that obvious, but you dont expect them to error when the types mismatch)

rare scarab
#

Save your type choosing for methods not implemented by default, like __lt__

#

In that case, return NotImplemented instead of TypeError (IIRC)

#

Seems __eq__ can also return NotImplemented

restive rapids
rare scarab
#

Unless the object somehow returned NotImplemented on itself

fiery canyon
rare scarab
#

For simple classes, you don't need to worry about NotImplemented

fiery canyon
#

do i just type to Color and thats it

restive rapids
rough sluiceBOT
rare scarab
restive rapids
fiery canyon
#

What does notimplemented even mean as a value

restive rapids
fiery canyon
#

errr okay

#

So would this be the common practice that I should do?

trim tangle
#

Yes

fiery canyon
#

A bit annoying that the typechecker doesn't like when I do
... = Obj(data["key"]) if data.get("key) else None

#

So I have to do that instead..

restive rapids
#

the first one could be PublicUserFlags(data.get("public_flags", 0)), i think
the other ones i guess stay as-is

fiery canyon
#

I want the whole attr to be nullable, not the class to be modified based on that

#

Ah the first one

rare scarab
fiery canyon
rare scarab
#

Is your public_flags key nullable?

fiery canyon
#

Alright

restive rapids
fiery canyon
#

Many things there are marked as optional keys (not necessary nullable) when i've never seen or cant think of a context where they dont appear

rare scarab
#

just to be safe then

fiery canyon
#

just put it for them to compare a fetched user's flags to another's

#

🙏🏻

#

It's just that logically flags are not supposed to be None, why would a user have no flags? I do not know because the Discord dev docs don't tend to say why some keys may be optional. Nullables are a different case.

rare scarab
#

For public flags, it might be better to do PublicUserFlags(data.get("public_flags") or 0)

fiery canyon
#

Thanks didn't think of that

#

Another correction thumbsup bool keys being optional is just weird to me. Who knows.

rare scarab
#

default value is already None, which makes False

fiery canyon
#

o yea

#

Man oh man I got a long way to go parsing all those objects. Parsing guild will most definitely take the longest

#

Guess it's worth it at the end

rare scarab
fiery canyon
#

I'm not going to make a new wrapper for the whole api/gateway xD

rare scarab
#

!pip discord-webhook

rough sluiceBOT
fiery canyon
#

Nah that's just webhooks

rare scarab
#

Or are you trying to receive webhooks?

fiery canyon
#

Yeah

#

No one has released a lib for it yet, surprisingly (:

#

Those are all just normal "webhooks". totally different

#

I got it on Github, once I feel confident enough with it and once I know how to publish a library properly, I'll put it there

#

It's funny because in the beginning when I heard Discord released this, I wanted to use it but I didn't know much back then. I was frustrated that there was no library for it (yes I waited a while until I could say this)
For the initial implementation on my framework, I figured out pretty much how to set it up with Flask
Then moved to FastAPI
Then started doing this

fiery canyon
#

Hm idk the docs are fine enough

#

But thx

rare scarab
#

In case you want to generate code

fiery canyon
#

Nawr my lib does not send anything

feral wharf
fiery canyon
feral wharf
#

It's very different than what you're doing

fiery canyon
feral wharf
#

That's literally an extension for dpy where it subclasses dpy's classes and calls its stuff

fiery canyon
#

This would've been useful if I found out about it earlier

feral wharf
#

Lol

fiery canyon
little hare
#

-# i mean, could be a fork

feral wharf
#

Way too bloated to fork and modify it just to receive those events

fiery canyon
#

What iiiiif I refrained from using enums for any x_type (e.g. ChannelType) and would use such logic instead (this logic = properties that are bool checks. Alternatively I can be super duper lazy and return string of the x_type CE_Muehehehehe

#

Eh cuz I feel like I have no good way of implementing public usage of the enums for users

#

At first I thought about an "extras.py" top level file with all of those enums, but idk

fiery canyon
#

What should I do to fix this? One thing that comes to mind is just using Any

#

Or Ill just turn off strict type checker lul

drowsy wadi
#

Idk; what determines the type of Container?

#

Is this one of those things you fix by specifying discord.ui.Containerint or whatever?

fiery canyon
fiery canyon
drowsy wadi
#

Well, maybe throw a LayoutView in there and go from there.

#

Play in the space; don't be afraid yakketysax

#

Except of Any. Be afraid of Any.

fiery canyon
fiery canyon
#

Finally

#

I removed fields that seem to be absent in my context, I don't want to include something that's never populated or never present

#

So yea I hope I got it right

fiery canyon
#

Is there a way to make typechecker acknowledge that guild is not None if is_guild_install?

restive rapids
# fiery canyon Is there a way to make typechecker acknowledge that guild is not None if `is_gui...

atleast pyright doesnt seem to narrow from attributes like this:

from typing import reveal_type

class P[A, B]:
    a: A
    b: B
def f[E: (P[int, str], P[str, int])](e: E):
    if isinstance(e.a, int):
        reveal_type(e.b) # says str | int even though it should really be only str

so the best i can come up with is

from typing import Literal, TypeIs, reveal_type

class Guild:
    name: str

class _ApplicationAuthorized[IsGuildInstall: bool, GuildType: Guild | None]:
    is_guild_install: IsGuildInstall
    guild: GuildType

type ApplicationAuthorizedWithGuild = _ApplicationAuthorized[Literal[True], Guild]
type ApplicationAuthorizedWithoutGuild = _ApplicationAuthorized[Literal[False], None]

type ApplicationAuthorized = ApplicationAuthorizedWithGuild | ApplicationAuthorizedWithoutGuild

def is_guild_install(aa: ApplicationAuthorized) -> TypeIs[ApplicationAuthorizedWithGuild]:
    return aa.is_guild_install

def test(aa: ApplicationAuthorized):
    if is_guild_install(aa):
        reveal_type(aa) # _ApplicationAuthorized[Literal[True], Guild]
    else:
        reveal_type(aa) # _ApplicationAuthorized[Literal[False], None]
fiery canyon
#

so i take theres no way practically?

restive rapids
# fiery canyon so i take theres no way practically?

practically you should only check if aa.guild is not None. Why would there be a guild in a non-guild install? Or why would a guild install not have a guild? is_guild_install and guild is not None provide the same information, im not sure why they coexist

fiery canyon
#

Although it looks weird if you use that and there's the check

tawny kite
#

i hope u are going well
is there anyone hiring dev?

viscid spire
rough sluiceBOT
#

6. Do not post unapproved advertising.

9. Do not offer or ask for paid work of any kind.

viscid spire
#

also please keep discussions to the topic of the channel.

slender basin
#

Hey. I have a situation like -

def f(**kwargs):
    if kwargs['some_val'] > 0:
        other_func()

When I try to type-annotate the argument as **kwargs: Any, to appease pyright, flake8 gets annoyed with ANN401 Dynamically typed expressions (typing.Any) are disallowed.
I don't control the full layout of kwargs (so TypedDict isn't really an option). Is there some idiomatic way to appease both the type-checkers and the linters, or do I have to resort to disabling one of them on such cases?

trim tangle
spiral fjord
#

Can't you make a typeddict with only the params you need?

#

Looks like it just rejects the extras

#

Do you not know how your function is being called at all? Or do you know it and is it just different between calls

#

in the later case you can use NotRequired

#
class Kwargs(TypedDict):
    x: int
    y: NotRequired[str]


def example(**kwargs: Unpack[Kwargs]):
    if kwargs["x"] > 0:
        print("Big X")```
oblique urchin
#

If you don't want to use a TypedDict, you can also use object, but then you'll have to do if isinstance(kwargs["some_val"], int) first

trim tangle
#

The current type system is confused as to whether typeddicts are open or closed in some contexts

#

For example: ```py
from typing import TypedDict, Unpack

class Kwargs(TypedDict):
x: int
y: str

class BetterKwargs(TypedDict):
x: int
y: str
impostor: bool

def example(**kwargs: Unpack[Kwargs]) -> None:
...

d1: BetterKwargs = {"x": 1, "y": "a", "impostor": True}
d2: Kwargs = d1

example(x=1, y="a", impostor=True) # not allowed by either
example(**d1) # allowed by pyright not by mypy
example(**d2) # allowed by both

spiral fjord
#

I wonder why pyright allows **d1

trim tangle
#

well, BetterKwargs is-a Kwargs

slender basin
#

Thanks everyone. I'll just disable the linter on kwargs lines for now.

trim tangle
#

How would you know how to call it?

feral wharf
#

yes

#

inspect.signature(func).parameters

#

already got that part

class _ProxyItemCallback:
    def __init__(
        self, func: SetItemCallbackType, item: ViewItem, parameters_amount: int
    ) -> None:
        self.func: SetItemCallbackType = func
        self.item: ViewItem = item
        self.parameters_amount: int = parameters_amount

    def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
        if self.parameters_amount == 1:
            return self.func(interaction)
        elif self.parameters_amount == 2:
            return self.func(interaction, self.item)
        elif self.parameters_amount == 3:
            return self.func(interaction, self.item, self.item.view)
        else:
            raise TypeError("callback must accept 1 to 3 parameters")


# self.callback = _ProxyItemCallback(func, self, len(params))  # type: ignore
trim tangle
#

(What's V and I though?)

feral wharf
#

these

I = TypeVar("I", bound="Item", covariant=True)

T = TypeVar("T", bound="ItemInterface", covariant=True)
V = TypeVar("V", bound="BaseView", covariant=True)
M = TypeVar("M", bound="BaseModal", covariant=True)

current situation:

class Item(Generic[T]):
  ...


class ViewItem(Item[V]:
  async def callback(self, interaction):
    ...

  # new
  def set_callback(self, func: SetItemCallbackType, /):
    ...


class ModalItem(Item[M]):
  ...
trim tangle
feral wharf
#

correct

trim tangle
# feral wharf correct

Would this work? ```py
V = TypeVar("V", bound="BaseView")
I = TypeVar("I", bound="Item[Any]")

_Coro = Coroutine[object, Any, Any]
SetItemCallbackType = ( # SetItemCallbackType is a generic type alias with two parameters, V and I
Callable[[], _Coro]
| Callable[[Interaction], _Coro]
| Callable[[Interaction, V], _Coro]
| Callable[[Interaction, V, I], _Coro]
)
...

V_co = TypeVar("V_co", bound="BaseView", covariant=True)

class ViewItem(Item[V_co], Generic[V_co]):
def set_callback(self, func: SetItemCallbackType[V_co, "ViewItem[V_co]"], /):
...

feral wharf
#

I wouldn't be a subclass there, would it?

#

V_co twice?

trim tangle
feral wharf
#

like would something like the following still work

# library
class Button(ViewItem[V]):
  ...

# user
button = Button(...)

async def func(interaction: Interaction, view: View, button: Button):
  ...

button.set_callback(func)
trim tangle
#

Ah, you want to subclass ViewItemFurther

#

What lowest Python version are you targeting?

feral wharf
#

yeah the classes above the base classes
my bad for not mentioning that

#

3.10

trim tangle
#

Are you already depending on typing-extensions somewhere?

feral wharf
#

fortunately yes, it's a required dep

trim tangle
#

If so, you can do ```py
from typing_extensions import Self

class ViewItem(Item[V_co], Generic[V_co]):
def set_callback(self, func: SetItemCallbackType[V_co, Self]):
...

feral wharf
#

ahh

#

let's see

trim tangle
#

Personally, I would just keep it simple and always require accepting those 3 arguments. If the user doesn't need them, they can just do py async def my_callback(interaction, _view, _item) async def my_callback(interaction, view, _item) async def my_callback(interaction, _view, _item) async def my_callback(interaction, _view, item) # doesn't seem possible with your approach (it also prevents various edge cases when the function uses *args/**kwargs and such without using @functools.wraps)

#

(But some people like that, and that's your library of course)

feral wharf
#

well this method is supposed to be used a quick way to add a callback to an item, the more "proper" way is subclassing and overriding the callback method, but I agree. I'll ask around.

#

the above seems to work!

#

thank you (again) rooLove

trim tangle
#

Another thing: I would always provide generic parameters to generic types. i.e.: always do list[Any] instead of list to make it clear when an Any is being invoked.

#

Pyright has a reportMissingTypeArgument configuration option, not sure about mypy

feral wharf
#

agreed

hardy linden
#

Is there a decent, not-terrible-looking way to implement branching logic based on combinations of types, where those types come from user-defined TypeIs functions?

My use-case involves needing to satisfy the overload conditions of a function. It's got two arguments, and the return type varies based on the combination of input types:

@overload
def myfunc(x: Foo, y: Foo) -> T1: ...
@overload
def myfunc(x: Foo, y: Bar) -> T2: ...
@overload
def myfunc(x: Bar, y: Foo) -> T3: ...
@overload
def myfunc(x: Bar, y: Bar) -> T4: ...
def myfunc(x: Foo | Bar, y: Foo | Bar) -> T1 | T2 | T3 | T4: 
    # Implementation here

Notably, Foo and Bar are not actual classes, they're type aliases for certain constructions of builtins (in my case, certain arrangements of nested tuples). However, I do have a TypeIs:

def is_foo(obj: object) -> TypeIs[Foo]:
    ...

Now, TypeIses return bools under the hood, so what I'd like to do is something like this:

match is_foo(x), is_foo(y):
    case True, True:  # return a T1
    case True, False:  # return a T2
    case False, True:  # return a T3
    case False, False:  # return a T4
    case _: typing.assert_never(x, y)

But that doesn't work. Well... it does work at runtime, but not in the type-checker: within each case, the types of x and y don't get narrowed; they're still both Foo | Bar.

What I can do is:

if is_foo(x):
    if is_foo(y):
        # return a T1
    # return a T2
if is_foo(y):
    # return a T3
# return a T4

But that code is, IMO, difficult to read, difficult to understand, and also just... ugly, frankly.

The kicker is that this example uses only two variables with two types; adding more variables or types makes the combinatorics go nuts.

There's got to be a better way... right?

rare scarab
#

No match/case or mappings in the type system yet

#

What you have with the overloads is the best we can do right now

hardy linden
#

Blagh. Okay, understood.

#

I'm not that frustrated with the overload definitions, but is there any better alternative to the if-statement structure?

I'm not even allowed to do:

if is_foo(x) and is_foo(y):
    # return a T1
if is_foo(x) and not is_foo(y):
    # return a T2
# etc. etc. with remaining combinations

because even within that if block, neither x nor y gets narrowed to the proper type.

(This would require a bunch more calls to is_foo, but the resultant code has a consistent pattern to it and feels much easier to read as a result. I suppose I could wrap is_foo in a functools.lru_cache or something.)

rare scarab
#

How is is_foo() implemented?

#

Can it be replaced with a @runtime_checkable Protocol?

hardy linden
#

I'm not familiar with that; let me read the docs

rare scarab
#

The basic goal is to replace your TypeIs with an isinstance call, which will work for match/case too

#
match x, y:
  case Foo(), Foo(): return T1()
  case Foo(), _: return T2()
  case _, Foo(): return T3()
  case _, _: return T4()
hardy linden
trim tangle
#

(I think recommending runtime_checkable is a bad idea)

rare scarab
#

We could help with the ideal solution if you shared how the typeis is implemented

hardy linden
#

My actual use-case is differentiating between:

  • Foo: Sequence[int | float]
  • Bar: Sequence[Sequence[int | float]]
    • i.e., Bar: Sequence[Foo]

I was previously doing:

def is_foo(obj: object) -> TypeIs[Foo]:
    return (
        isinstance(object, Sequence)  # I forget this exactly
        and all(isinstance(x, (int, float)) for x in obj)
    )

but that's slow as hell in practice, so I started shortcutting it:

def is_foo(obj: Foo | Bar) -> TypeIs[Foo]:
    return isinstance(obj[0], (int, float))
rare scarab
#

That is kind of... um..

hardy linden
#

It's not safe whatsoever, but all of the code is mine, so I'm trusting that it works 🙃

#

I profiled the code with the rigorous checking enabled and the repeated isinstance calls had the largest tottime in the entire script; switching to the shortcutted version took a quarter of the time to execute

rare scarab
#

yes, you changed it from O(n) to O(1)

#

Everywhere you go, you'll find someone who recommends against type narrowing of collections

hardy linden
trim tangle
#

the only soundness hole in here is if the collection is empty

#

otherwise it's fine

#

(because float cannot be a Sequence)

hardy linden
#

Good point, yeah. In practice, that shouldn't actually happen; I believe I may be able to safely change Foo and Bar to tuple[int | float, ...] and tuple[tuple[int | float, ...], ...] respectively, but I'd need to fix any places that broke upstream if the callers are using lists, etc.

oblique urchin
rare scarab
#

You could recreate this with ```py
Numeric = int | float
match a, b:
case [Numeric(), *_a], [Numeric(), *_b]: ...

Does this syntax match sequences, or just lists?
trim tangle
oblique urchin
trim tangle
#

!e
Yeah, it's a thing apparently...

from collections.abc import Sequence

class Wtf(int, Sequence[int]):
    def __len__(self) -> None:
        return len(str(self))

    def __getitem__(self, idx: int) -> int:
        return int(str(self)[idx])

answer = Wtf(42)
print(len(answer))
print(answer.count(4), answer.count(9))
rough sluiceBOT
hardy linden
rare scarab
#

Just tested it myself. ```py

match 0,:
... case []: print("was a list")
... case (
,): print("was a tuple")
... case _: print("what?")
...
was a list

hardy linden
#

yeah, found it:

There is no difference if parentheses or square brackets are used for sequence patterns (i.e. (...) vs [...]).

trim tangle
#

yes, it just checks if it's a sequence (but not a str or bytes)

hardy linden
rare scarab
#

does ruff/black normalize ()/[] in match/case?

trim tangle
rough sluiceBOT
trim tangle
#

Here's an annoying thing...

#
from collections.abc import Sequence

def foo(x: Sequence[object] | bool) -> int:
    match x:
        case [*_]: return 420
        case True: return 69
        case False: return 57
#

this type checks with pyright but it's a bug

#

if I call foo("a string") it will return None...

hardy linden
#

I can do this, though:

case [int() | float(), *_a], [int() | float(), *_b]:
rare scarab
#

Sure.

#

or maybe float() works fine.

#

Nope.

trim tangle
#

It feels like pyright is getting mypyed too 😔

#

issued get added at a much faster pace than they get fixed

#

maybe microsoft decided to divert resources elsewhere, predicting that everyone will switch to ty/pyrefly anyway

oblique urchin
trim tangle
#

ah right

trim tangle
rare scarab
#

Force feed them their own dogfood

trim tangle
#

Trolling Microsoft is cool, though trolling Eric who's the presumably the only person maintaining a 120k line TypeScript codebase is not very cool

rare scarab
#

Is he still at microsoft?

trim tangle
#

Nope

#

(according to Jelle above)

rare scarab
#

English was always my worst subject at school.

#

Yes, I'm a native speaker.

trim tangle
#

Good. I'm a native headphones

rare scarab
#

I'm also a native microphone.

brisk hedge
terse sky
#

yeah, str being Sequence[str] is pretty terrible. I don't really understand why people defend python not having a character type, but here we are

rare scarab
#

meanwhile rust has 12 different string types

terse sky
#

yes, though a lot of that is a consequence of not being a GC language

#

so there's differing types based on ownership concerns

rare scarab
#

and the many different ways to represent a string

terse sky
#

and also different types (or differently qualified types) for mutability or lack thereof, not sure if you were including that

#

yeah, I'm not sure what happens in python if you want to interact with the filesystem and a filename is not valid unicode

#

not something I've ever had to deal with

rare scarab
#

it raises an OSError

terse sky
#

does that basically stop you from using e.g. pathlib almost at all then?

rare scarab
#

Why would it?

terse sky
#

I mean like if you had files like that in such a directory, and you wanted to iterate over the directory and then delete files conditional on something

#

some concrete use case like that

#

then when you iterate over the files, my impression from what you're saying is that you'll raise OSError immediately upon entering the for loop

rare scarab
#

open() supports bytes

terse sky
#

yeah, it does not throw OSError immediately on construction at least

#

pathlib seems to work fine actually

#

i was able to delete a file using Pathlib after iterating over it

#

I guess it will be more awkward to do certain things

rare scarab
#

And can you represent that file as a string?

terse sky
#

When I print it I just get a blank

rare scarab
#

but repr()?

terse sky
#

not an exception

rare scarab
#

unprintable characters doesn't mean unrepresentable

terse sky
#

repr spits out the escaped bytes

#
In [1]: from pathlib import Path

In [2]: x = list(Path(".").iterdir())

In [3]: repr(x[1])
Out[3]: "PosixPath('\\udc80\\udc81\\udc82')"
#

so I would say pathlib does pretty well here overall, without introducing the complexity of another string type - you could definitely argue that this is perfectly reasonable trade-off over what Rust does

rare scarab
#

It's technically still unicode

terse sky
#

then maybe this is a bad example? I did a quick google search for creating a filename with an invalid unicode filename

rare scarab
#

\udc80 is just a surrogate character

trim tangle
#

Oh yes, this is stinky

terse sky
#

hmm, then yeah, probably a bad example

trim tangle
#

Python strings are allowed to have surrogate characters. And apparently they are used to encode invalid UTF-8 sequences when working with OS paths

terse sky
#

I do think a lot of people are going to have entire careers and never once come across dealing with invalid unicode filenames

terse sky
#

I don't understand unicode well enough to know what all the ramifications of that are

#

introducing a new type though does add a non-trivial amount of friction for what's basically a niche case

trim tangle
#

Could just be bytes

rare scarab
#

Path doesn't support bytes

terse sky
#

it would be a lot less ergonomic still, wouldn't it. 99% of teh time you want to go between strings and paths in python

#

so you'd constantly be converting still

trim tangle
#

Yeah, I meant before that (chronologically). Maybe Python 3 changed too many things into unicode

terse sky
#

I mean the argument is - that conversion doesn't have to succeed, so you should do it explicitly etc etc

#

I think python and rust's approach here are both reasonable, fwiw, can see the arguments both ways.

#

the lack of a char type though is not something I can do that with

rare scarab
#

It really depends on how you define a char

terse sky
#

there's multiple ways to define a char, and there's arguments for all of them, and I'd be okay with almost anything

#

but i'm not okay with not having one at all and having a string as an array of str or what not

rare scarab
#

It's too late now 😔

terse sky
#

yeah. I mean I guess before typing existed and when python was just the happy-go-lucky-it-just-works language, there probably didn't seem to be any need

#

but today you write x: Sequence[str] and passing "hello" to x type checks, it's eye watering to put it mildly

rare scarab
#

and before collections.abc.Sequence was widely used

terse sky
#

I've definitely hit this before, too. I can't remember if I decided ot live with it, or just use list[str] instead

rare scarab
#

I hate this. ```py

set(dir(())) - set(dir(""))
{'class_getitem'}

trim tangle
#

I do wonder how Windows managed to even create a file with these characters if the system encoding is Win1252, it doesn't have б, а, н

rare scarab
#

it probably falls back to utf8

#

it's done this since windows 1.0 I think, at least it did for edit charset detection

terse sky
#

wild

#

I am happy I don't deal with unicode at work 😛

trim tangle
#

Actually, with python3.14 on linux it does give the raw UTF-8 bytes for files having non-ascii names

#

maybe it's a Windows XP thing or a Python 3.4 thing

rare scarab
#

I'm down for blaming microsoft.

trim tangle
#

again?

rare scarab
#

never gets old

terse sky
#

the real question is why do you have easy access to python 3.4

trim tangle
#

and I don't have a more recent windows VM

terse sky
#

why do you have windows XP as a VM though

#

I could understand not having it at all

#

but what is XP good for at this point

#

is it in the same folder as your punchard VM

trim tangle
#

I used it to play heroes of might and magic 3 for extra nostalgia points

#

and then for some reason I installed python on it

terse sky
#

although it does run on newer windows too

#

I'm pretty I've played that game on my desktop

#

on W11

trim tangle
#

yeah, it does work

trim tangle
#

My grand plain is to introduce variance using examples of previously known types, before teaching how to make your own generic classes

#

Otherwise it's too many concepts at once

torpid cosmos
#

Hey there

#

Does anybody here know AI and ML with python?

viscid spire
trim tangle
torpid cosmos
#

Uhm r u a bot?

#

Just asking

#

Ur username seems straight forward

trim tangle
#

Yes. I am Bender Rodriguez

viscid spire
torpid cosmos
#

Hi

trim tangle
#

but for generic types, that's sort of why the rest of the chapter exists

viscid spire
#

fair enough

#

I agree with doing a simple negative example without union

viscid spire
trim tangle
viscid spire
#

same at the "How do you type the add_event_handler method? Your first guess might be this:" part, where it clips the return typehint

#

of course some of these lines are just quite long and there's not much to do about it, except perhaps multilining a function definition

#

for example, at ChatBot.add_event_handler

trim tangle
#

I also dislike the design of the # type: ignore[rule1, rule2, ...] feature because it has to be on the same line, potentially making it comically large. TypeScript did it correctly

#

yeah I might multiline some things to make it more digestible on mobile

viscid spire
#

e.g. what black does for this class

class ChatBot:
    def add_event_handler[E: Event](
        self, event_type: type[E], handler: Callable[[E], None]
    ) -> None: ...
#

some of the LSP popups unfortunately are just long and not quite multiline-able, like this one which is 146 chars long:

Argument of type "(event: NewMessage) -> None" cannot be assigned to parameter "handler" of type "(Event) -> None" in function "add_event_handler"
trim tangle
#

For real. Pyright yapping to much

#

I was thinking of adding some fancy thing where, during build time, a fancy IDE-like popup is added to the code example automatically

viscid spire
#

icic

trim tangle
#

But maybe it's too much work and it's already understantable

viscid spire
#

ok I'll leave further notes to someone else

jade viper
#

What are the use cases of these type hints?

var: Any
var: Unknown
var: object

Should someone ever use object?

rare scarab
#

A type is unknown if it can't be resolved, such as an invalid type or a missing import.

jade viper
#

Got it, thanks

jade viper
#

I understand that one turns off type checking

#

But what does type checking for any type mean?

rare scarab
#

Most Any uses can be replaced with Object

#

var: Any; var.foo() passes
var: object; var.foo() fails

jade viper
#

Makes sense

rare scarab
#

Annotating as object requires narrowing

jade viper
#

Thank you!

novel notch
#

I have a function that retrieves a parameter, most parameters are integers, but a few are strings. So mypy will correctly warn if I don't check for int or str. One way to tell mypy the typ is using asserts:

size = get_parameter("FOO_SIZE")
assert isinstance(size, int)
...

Is it possible to supply the return type as a second parameter to get_parameter("FOO_SIZE", int) and also let this argument have default value int?

def get_parameter(n, return_type=int) -> return_type:
   ...
  1. Can this function be written? How?
  2. How to restrict return_type to only int or str?
feral wharf
#

Not sure if it should be T: (int, str) = int

little hare
feral wharf
#

If that's used at runtime, yes absolutely

novel notch
feral wharf
#

Wait no it doesn't have to be used at runtime

novel notch
#

For my usecase static analysis (not runtime) is enough.

little hare
#

do you need subclass support?

#

or just str and int only?

feral wharf
#

You can import TypeVar from typing_extensions instead
-# I'll let qtit handle the rest

novel notch
#

To start with str and int is fine. How much more complicated would it be with subclass support is interesting

#

Do I need to import extra package for this to work? 🫢🙈

little hare
#

then Union[type[int], type[str]] would work iirc

novel notch
#

What would be the total result of the function definition and typevar declarations?

little hare
feral wharf
little hare
#

oh my god i am tired

#

I

#

i didn't notice the big ol return_type

#

I'd probably end up writing an overload because i forgot about generics again

#

which also wouldn't have subclass support

feral wharf
#
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from typing_extensions import TypeVar

    T = TypeVar("T", bound=type[int] | type[str])

def get_parameter(n: str, rtype: T) -> T: ...

I usually don't blindly write typing code either, but rather let Pyright hold my hand through it; so definitely experiment with that above lul