#type-hinting
1 messages · Page 46 of 1
No, it was a made up example. But I have a similar use-case where a function is conditionally run on some flag with alternative do-nothing functions which accepts everything.
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.
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"
Hm... strange ```py
from typing import Any
reveal_type(object.eq) # (object, object) -> bool
def f(x: object, y: object) -> None:
reveal_type(x == y) # shows: object
def g(x: Any, y: Any) -> None:
reveal_type(x == y) # shows: Any
== 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
yes, any operation with Any is inferred as such iirc
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)
use object and return NotImplemented when other is not an A, so that type(other) could still implement __eq__ that'd work with A
The (probably wrong spelling but idc) "Listkov substitution prinicple" would be broken
*Liskov substitution principle (right spelling per mypy)
What's the difference between using object as type vs Any ? Everything inherits object doesn't it?
Any does not in the eye of type checkers. objects always have some attributes, e.g. __name__, Any does not
if you use Any, you're allowing the implementation to do anything with it, which is not desirable, you want an opaque thing that you cant really do anything specific with until you make sure its an A
That would then be liskov for the implicit class object which A is based on, right?
yeah, i suppose so
Thanks, this makes sense
np
any always "has" all the attributes and methods you could imagine
That reply doesn't make sense
!e Actually it does.
from typing import Protocol
print(type(Protocol))
:white_check_mark: Your 3.14 eval job has completed with return code 0.
<class 'typing._ProtocolMeta'>
What about the implementing classes?
Same with ABCs, you just inherit
Or did you mean are they also an instance of the metatype? The answer is yes.
I think unalivejoy meant that, if you already have an incompatible metaclass and want to satisfy an ABC, you can replace an ABC with a protocol, which you can implement explicitly without inheriting from it
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")
:x: Your 3.14 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File [35m"/home/main.py"[0m, line [35m10[0m, in [35m<module>[0m
003 | class Banana(BaseBanana, Fruit): # TypeError: metaclass conflict
004 | def eat(self) -> None: print("nomnomnom")
005 | [1;35mTypeError[0m: [35mmetaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases[0m
!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()
:white_check_mark: Your 3.14 eval job has completed with return code 0.
nomnomnom
@mossy rainI should be able to del the ABC with no problem, right?
I just like deling anything that users shouldn't interact with (:
I see
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
Yeah, thats what i originally did in the PR, you said its used by some other internal part though?! If thats not the case anymore, sure, go for it.
The class _AnyEvent is, but I don't think deling the subclassed (ABC) will make it not work
I think I misunderstood the topic earlier
Oh, you wanna del the subclasses, sure that works
ah yeah, idk, but i hope there isnt a lot of confusion now, sorry for confusing you, mb
Yessir :D
Gotta be consistent 🫡
i see where you're coming from
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__
definitely, and with how dynamic python is, your users could still technically get to the class if they really tried
!e ```python
class Foo:
def foo(): ...
class Bar(Foo): ...
del Foo
Foo = Bar.bases[0]
print(Foo)
:white_check_mark: Your 3.14 eval job has completed with return code 0.
<class '__main__.Foo'>
there's a ton of ways to do that
but the cool thing is -- it's not your responsibility
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
relying on hacks always backfires because it introduces backward compatibility problems where they should never appear
(java has powerful reflection too)
exposing to breakage more than necessary
fair
i shouldn't have mentioned java :) barely touched it
I do have __all__ and that doesn't stop someone from importing something they shouldn't use
the purpose of __all__ isn't to prevent others from importing private things
it's to stop wildcard imports
if you have some internals that shouldn't be exposed when someone does from yourlib import *
explicitly define*, would describe it
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
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)
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]?
https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
https://peps.python.org/pep-0483/#covariance-and-contravariance
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...
do you think list[int] is a list[int | str]?
def f(xs: list[int | str]):
xs.append("hi")
def g():
xs: list[int] = [42]
f(xs) # you think that since int <: int | str, then list[int] <: list[int | str]
# well, now our list[int] has a string in it
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
how so?
maybe you should educate yourself on a topic before calling the type system "halfarsed"
there are a couple issues with it, but variance is pretty standard
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
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
yeah i guess i could, sorry
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
it still loves duck typing, but now we have constructs to express some of that duck typing statically
hm
I guess it feels like the type system innovation didn't come with the basic language changes I would have expected
mhm. its purely "bolted on top" of a dynamic language
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
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
yeah that makes sense
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"))
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
what exactly do you mean by that? because there's no builtin discriminated union
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
a type-checker shouldn't complain about a missing base case if you exhausted all possible options to discriminate on
well I don't know the name for the technique but python can figure out that inside a branch checking a variable has a certain type
but it seems to still think you could fall off the end of an exhaustive if/elif
ah, well, narrowing is not exactly specified so it depends on the typechecker you use
isinstance and match should work fine though
for every possibility
can you show a case where you have an issue?
hm I'll try to find one
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
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
it was removed in v1.1.406:
https://github.com/microsoft/pyright/commit/7905b1936a0f42642c56bae9060d9f3112faddd0
Ooh
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?
!pep 718
:( 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
can you show an example?
Here's the actual code I'm having issues with playground
I did have a small repro but I can't find it, and am having trouble remaking it, so it might be a while until I can
what a nice error message
Yep, the generic soup makes for such fun error messages
In the second lambda, what is x[1][0] supposed to be?
The parser returns the modified input in the ok branch, so that’s the modified input from the then parser
The tuple passed into the second lambda is (self output, (other modified input, other output))
No, the second element of the tuple is a ParserOutput
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)
@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
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
so i shouldnt do fields: None = ...?
you should, because for the None case (2nd overload) it does have a default
yeah so what do i do to fix?
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
oh so if i do = ... that means the default value is the type i give to fields?
for this overload to apply
= ... means that the argument is optional
yeah thats what i knew
kind of, it just tells the type checker that there is some default value there, and since overloads are checked from the order they're defined in, if the overload states that a value has a default, then it doesn't need to be specified, and the overload applies
i thought if the argument is optional in the original function you have to keep it like that in every overload 💀
The important point is that overloads are checked in order
if you changed the order and made the None overload first, it would work
what even the order matters
thats why i remember this working before
if you tried to match the type signature of the call
you call the function with just Account as the first arg
when i pass fields=None explictly how does it match that?
so it goes to check which overload applies there
it first evaluated the overload that was defined first
i have told it that fields is None when optional right
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
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: ...
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
It will match the second overload when you call insert_account(account) and insert_account(account, fields=None)
when you define an overload, the type checker doesn't cross-compare with the implementaion types, it just considers the overloads in order they were defined
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?
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
oh that makes sense, then it just sees the first optional overload
Why does it return None if you don't pass any fields though? Why not always return an Account?
if fields are None a returning clause isnt added to the sql statement and there is nothing to return
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
so it's going to return an Account with some fields potentially missing? What if fields is ()?
the repository layer expects either some fields or None, but yeah thats a good point i can just make the check if not fields, but anyways the service layer will never do that
i mean whats the point of a returning clause? mostly to read auto generated columns like id's created at, when an account is being inserted the Account(...) doesnt have the created_at set but if fields spenicfies created_at then it will fetch that autgenerated field
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
i am not using a orm here so i need to do the filling and all myself
(or rather, either an entire instance or nothing, not a partial instance)
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
especially if you only want the ID
I suppose that's fair yeah
when the record gets big its not a good idea to always return *
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
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.
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?
yes
it's flagging for me :\
s/float/str
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
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
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?
seems like they don't understand the fact that Err[Never] is itself empty
For example: imagine you're making a stub file. How do you express that?
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
something like that, yes
:/
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
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
unless your Page class is a sequence of itself that error indicates your type guard function doesn't make sense
im confused
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
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]
the parameter type of _is_sequence_of_pages seems wrong there then
oh wait the one without an underscore includes Sequences
ya
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..
anyone?
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?
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)
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)
Please react with ✅ to upload your file(s) to our paste bin, which is more accessible for some users.
Now to validate the registryValue action I have this model:
Click here to see this code in our pastebin.
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.
It's really strange that it produces an empty list instead of an error 
Is this your own YAML format? Or do you have to adhere to an existing format?
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.")
If you can change it to something like this: ```yaml
path: 'HKLM:\SOFTWARE\Example'
name: 'ExampleValue'
payload:
type: REG_MULTI_SZ
value: [value1, value2, value3]
apparently pydantic doesn't support discriminated union where the field is in the outer container... bummer
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
I'm not sure I understand
@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]
yeah I don't know pydantic well enough to figure out how to make it work
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.
your situation is exactly what discriminated unions are supposed to solve
Serde (a Rust library) supports this with adjacently tagged mode: https://serde.rs/enum-representations.html#adjacently-tagged
pydantic just doesn't implement that
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
do you understand the difference?
somewhat
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
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"
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.
In that case, I would use mapping unpacking instead of a dict parameter
Or just define a TypedDict for the known keys, like a good codebase would 
Just not so necessary
That's something I wouldn't do even if I decided to parse all objects ngl
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
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 💀
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.
hmm maybe ill try then
Wait you get full objects with the events?
Also, you may be interested in a package like the following if you don't feel like typing them classes yourself: https://github.com/Bluenix2/discord-typings
Nawr would rather only make what I need
Fair
Is there a way to tell type checker that the object that will subclass this does have id?
This worked, is that ideal?
How would Mentionable be an instance of HasID there
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
My intention is for classes to subclass Mentionable
And those classes will not have an id attribute?
they will
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 🥀
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
eh the typing works
Then it's a mixin no?

Usually they're in a separate file
But yes
I feel like banner and avatar could be one class called Asset or something
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
Can I (and should I) bump a typeshed PR that's been sitting for about ~2 weeks?
For reference: https://github.com/python/typeshed/pull/14866
looks reasonable to bump
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
error from who?
What version of pandas-stubs do you have?
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
there's 2.3.2.250827 and 2.3.2.250926
I have the latter
It's in another file
If it's supposed to be exported from the pandas module itself, then it needs to be in this stub file
I assumed a star import somewhere
Looks like there are none
So the stubs are bugged I guess?
maybe the stubs were bugged before?
I didn't have the error before though
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)
Hm, it does import it explicitly https://github.com/pandas-dev/pandas/blob/main/pandas/__init__.py#L149
pandas/__init__.py line 149
to_pickle,```
and lists it in __all__
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
that could be for backwards compatibility
but at the same time, it's not deprecated 🤷♂️
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
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"
Yeah, I can certainly appreciate that
But there was really no need to rush removal from the stubs
if it's planned to be deprecated, maybe just change your code to import it from the correct module?
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
nope
So it doesn't help me at all does it
🥲
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
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
vendor the stubs

That's awful
I was looking into whether I can have my own stubs file that can add more stubs
external stubs will suffer from this kind of problem, when they're a second class sort of thing
Have you considered fixing the function's types?
?
If you don't like vendoring stubs, fix it upstream
... Have you read the whole convo or jumped in part way?
Pandas stubs are maintained by pandas though afaik
It's a very long conversation. I'll stay out of it.
So I don't believe it is second class, but I could be wrong
tl dr the function was already annotated correctly and they removed it because pandas is saying they should one day deprecate it. Even though they haven't
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
So the stubs removed it instead of adding @typing.deprecated? Is it even deprecated yet?
yeah, so it's extra strange
I guess the advantage is that you can release them at a different cadence?
I read the whole convo and had the same thought as him 
How can I fix the functions types if it's not there anymore 🤔
PR 'em back in!
how broken are you?
yes
https://images.soheab.com/qNNhPfMrYU8.mp4
Move your types out of the TYPE_CHECKING block. They're used at runtime.
not if I stringify them?
I believe discord.ui.Button's generic args are important at runtime.
Is that something you're doing for dpy?
nah, it's just for the self.view attribute's type
it's a paginator extension for dpy yes
https://images.soheab.com/AUtmuR7YkfM.mp4 even funnier
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
circular imports and I don't need them at runtime
hmm alright then
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())
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"
Hm, but you can make a callable protocol with a __name__ attribute
you could yeah
yeah this is a known unsoundness, Callable and function types are sort of entangled
or one that doesn't have a __name__ attribute
I'm glad to see that **P accepts [] and ...
Basedmypy also gets this right. You can select this in the mypy playground.
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
class SetItemCallbackProto(Protocol):
def __call__(
self,
interaction: Interaction = ...,
view: BaseView | None = ...,
item: ViewItem | None = ...,
/
) -> Coroutine[Any, Any, Any]: ...
Do you know what "contravariance" is?
Not really
Wait what
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?
positional parameters can be optional
What do you mean?
I assumed it would still require them to be passed in for some reason
Like I don't need any methods from the passed class, it just needs to be a subclass of Animal
Since Animal already implements all the methods I need
are you ever going to call this function?
Can you show a more realistic example of what you have?
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
Yes
The passed function will be called with the three optional arguments
Will try to create one in a hour or so
__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:
seems right
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'
indeed, otherwise, you are saying that the return type does not depend on the input type in the subclass
that seems like an unfortunate interface though
Why though? Why not return a bytes object of size 1 when the size is 1?
Because I'm emulating what b"a"[0] is doing
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.
Is __getitem__(self, index: int|slice) -> bytes|int any better?
your case is a bit different, because you are changing the return type based on type or subtype of the input
The overload actually helps in that case. When I do a_bytes[0:5], I always get a bytes object back
even if the slice is empty or of size 1
what if the user passes in an int, which happens to have a value of 1?
it will not get caught by Literal[1]
Yes, that can't be determined statically, sure
That's why the return type is int | bytes in that case
so in practice, you mostly have a non-overloaded function
which returns int | bytes variably
yeah I was getting to it ^
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
insane
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
there's an issue open https://github.com/redis/redis-py/issues/2399 that's over 3 years old
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
To clarify: are you making a new interface or are you just adding types to existing code?
The latter
refactor
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()
jep
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?
Make a decorator that returns the input function type
I dont want to bc in my context _target_method is an abstract method on an abc
what is P bound to?
yeah im starting to see that
nothing so I cant do it. There's no way to type hint that is there?
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
I decorate _target_method?
Yes, pass in the function you're calling
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?
Use it like @forwards(_target_method)
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
You won't get type hinting inside the method, but you'll get proper types when you call it.
ok so the P has no meaning in this context will stay
Alright I guess I can live with that, thanks
Just remove it
You dont need it?
now if _target_method is an abstract method that subclasses will implement, this wont work right?
As long as it's defined
The type checker is smart enough for that? Interesting
This was very educational and that no op decorator is slick
I just thought the type checker would some reason only lookat the initially passed abstractmethod on the ABC
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
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
I believe unalivejoy was referring to a situation without inheritance
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
show that
but if i Know the method signature then __call__ bit is useless
maybe don't do this compute --> _call_ thing
fairnuff
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
@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)
pyright still complains about __call__ in Foo in Melen's code above
Now how do I do that without the class [] things i'm stuck on 3.10
I was thinking of this earlier, with manually binding the type parameters
P = ParamSpec("P")
R_co = TypeVar("R_co", covariant=True)
class MyClass(Generic[P, R_co]):
@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)
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
typescript really makes you feel sadness when you go back to python's typesystem
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
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
typehints make it easier to write good python code
it's not a "I will use python because it has typehints"
If something is possible in TypeScript or Rust or Haskell, but not in (typed) Python, does it mean it's a bad design? Obviously not
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
oh, it's a real work project?
yeah im spit balling an interface right now
Did you read everything in https://typing.python.org/en/latest/ and also the non-mypy-specific parts of mypy docs (https://mypy.readthedocs.io/en/stable/)?
nope
are your teammates familiar with at least one statically typed language?
or just Python?
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
that will make it tricky
I would probably read through both of these sites
Are your teammates familiar with typing in general? Like, do you have a type checker in CI?
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
You can throw the mypy docs at the teammates, specifically "First steps" and "The type system reference"
I might just be a freak of nature but I feel like generic stuff is more easy to understand.
I suppose... the rest of my life has taught me that this is an unusual trait 
Link me up Scotty
if we then overloaded the implemented abstractmethod on subclass with say multidispatch or singledispatch does this then work?
asumming we did the typing.overload decorator correctly
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
Is the union discriminated? Meaning a key whose value determines the type.
No
Check for the presense of one of the keys
No, parameterizing ParamSpec only works for simple positional argument signatures
It's in the works xD
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
Hot
- Is
Anythe appropiate type annotation forotherin__eq__? Saw some other code useobject. - 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.
Depends on what objects you want to define/support equality for.
I mean technically the user may pass anything
But it should be Color only
Then I would type it as Color and maybe not even bother to runtime check the type of other.
Using object will stop you from accessing any properties without an explicit type check.
You're already using isinstance on it
You want the user to see a type error/warning as soon as possible (in their editor).
__eq__ is supposed to support all objects.
if x == y raises a TypeError, that's a bug.
!e
class A:
def __hash__(self): return 0
def __eq__(self, other):
if not isinstance(other, A): raise TypeError("A.__eq__ other is not an A")
class B:
def __hash__(self): return 0
d = {A(): 42}
d[B()]
:x: Your 3.14 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File [35m"/home/main.py"[0m, line [35m8[0m, in [35m<module>[0m
003 | [31md[0m[1;31m[B()][0m
004 | [31m~[0m[1;31m^^^^^[0m
005 | File [35m"/home/main.py"[0m, line [35m4[0m, in [35m__eq__[0m
006 | if not isinstance(other, A): [1;31mraise TypeError("A.__eq__ other is not an A")[0m
007 | [1;31m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[0m
008 | [1;35mTypeError[0m: [35mA.__eq__ other is not an A[0m
(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)
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
yeah, if type(x).__eq__(x, y) returns NotImplemented, then type(y).__eq__(y, x) is tried, and if that returns NotImplemented too, then its False
Unless the object somehow returned NotImplemented on itself

For simple classes, you don't need to worry about NotImplemented
do i just type to Color and thats it
!e
class A:
def __eq__(self, other):
print("A.__eq__")
return NotImplemented
class B:
def __eq__(self, other):
print("B.__eq__")
return NotImplemented
a = A()
b = B()
print(a == a) # lol
print(A() == A())
print(a == b)
:white_check_mark: Your 3.14 eval job has completed with return code 0.
001 | A.__eq__
002 | A.__eq__
003 | True
004 | A.__eq__
005 | A.__eq__
006 | False
007 | A.__eq__
008 | B.__eq__
009 | False
This is #type-hinting and your code has a frightening lack of types
and then there is me doing #esoteric-python message with types
What does notimplemented even mean as a value
its specialcased both by typecheckers and python itself in the evaluation of operations with dunders, usually meaning that "this operation is not valid but you should try the respective dunder on the other thing"
Yes
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..
the first one could be PublicUserFlags(data.get("public_flags", 0)), i think
the other ones i guess stay as-is
None of these classes should take None
I want the whole attr to be nullable, not the class to be modified based on that
Ah the first one
Try a walrus operator ```py
... = Obj(d) if (d := data.get("key")) else None

Lowkey never did this xD
Is your public_flags key nullable?
Alright
you could also say that there is a 0% chance someone would want PublicUserFlags(a) == b to be True unless b is a PublicUserFlags and do return isinstance(other, PublicUserFlags) and self.value == other.value, that would be fine too 🤷
Yea but I don't trust the discord dev docs so to be careful I set as 0
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
just to be safe then
tbh users shouldnt init that class at all xD
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.
For public flags, it might be better to do PublicUserFlags(data.get("public_flags") or 0)
Thanks didn't think of that

Another correction
bool keys being optional is just weird to me. Who knows.
default value is already None, which makes False
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
Any reason you're not using discord.py?
I'm making a wrapper for Discord's webhook events. Neither of those have that or will ever have it
I'm not going to make a new wrapper for the whole api/gateway xD
!pip discord-webhook
Nah that's just webhooks
Or are you trying to receive webhooks?
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
This might help for some types. https://github.com/discord/discord-api-spec/blob/main/specs/openapi.json
In case you want to generate code
Nawr my lib does not send anything
A discord.py extension that allows to easily set up, receive, and handle Webhook Events. - DA-344/discord-ext-webhook-events
That's cool honestly but I'm not going to just delete my code 
It's very different than what you're doing
It's meant to be paired with discord.py
That's literally an extension for dpy where it subclasses dpy's classes and calls its stuff
This would've been useful if I found out about it earlier
Lol
Yep, won't be useful for anyone who doesnt use dpy
-# i mean, could be a fork
Way too bloated to fork and modify it just to receive those events
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 
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
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
Idk; what determines the type of Container?
Is this one of those things you fix by specifying discord.ui.Containerint or whatever?
idek
This but with Any works, obviously, so yeah I assume there's a correct way to do it
Well, maybe throw a LayoutView in there and go from there.
Play in the space; don't be afraid 
Except of Any. Be afraid of Any.
That actually worked lol ty
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
Is there a way to make typechecker acknowledge that guild is not None if is_guild_install?
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]
ill be honest i have no idea how to integrate this into what i have
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
Yea that's right
Although it looks weird if you use that and there's the check
i hope u are going well
is there anyone hiring dev?
!rules 6 9
6. Do not post unapproved advertising.
9. Do not offer or ask for paid work of any kind.
also please keep discussions to the topic of the channel.
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?
If you're okay with using Any in your codebase, just disable the lint
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")```
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
I hope this will be fixed once closed TypedDicts from PEP 728 are relased
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
I wonder why pyright allows **d1
well, BetterKwargs is-a Kwargs
Thanks everyone. I'll just disable the linter on kwargs lines for now.
I usually disable ANN401
doesn't seem to work.. it wants all parameters and with a default value
So you want a function that is either callable with one argument, two arguments, or three arguments?
How would you know how to call it?
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
then this #type-hinting message seems like what you want, how is that not working for you?
(What's V and I though?)
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]):
...
So you want the first argument to be Interaction, the second argument (if it's present) to be V, and the third argument to be the class the method is on (like ViewItem[V])?
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]"], /):
...
what do you mean?
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)
Ah, you want to subclass ViewItemFurther
What lowest Python version are you targeting?
Are you already depending on typing-extensions somewhere?
fortunately yes, it's a required dep
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]):
...
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)
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) 
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
agreed
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?
No match/case or mappings in the type system yet
What you have with the overloads is the best we can do right now
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.)
I'm not familiar with that; let me read the docs
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()
Okay, yeah, that makes sense on paper. Trying to figure out if my use-case will work here.
(Sorry, I'm scrambling to get up to speed on this because I haven't done a Protocol before)
(I think recommending runtime_checkable is a bad idea)
We could help with the ideal solution if you shared how the typeis is implemented
My actual use-case is differentiating between:
Foo:Sequence[int | float]Bar:Sequence[Sequence[int | float]]- i.e.,
Bar:Sequence[Foo]
- i.e.,
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))
That is kind of... um..
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
yes, you changed it from O(n) to O(1)
Everywhere you go, you'll find someone who recommends against type narrowing of collections
Let me rephrase this:
Shortcutting it like that is not sound, but all of the callers of this function are all my code, so I'm making the assumption that it's being fed valid data, and I'm just trying to differentiate between the two types of values it should ever be fed
the only soundness hole in here is if the collection is empty
otherwise it's fine
(because float cannot be a Sequence)
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.
why can't it be
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?
Oh, I thought it would have incompatible bases or something
I don't think so. It would be a pretty bad idea in practice to have a float subclass be a Sequence though
!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))
:white_check_mark: Your 3.14 eval job has completed with return code 0.
001 | 2
002 | 1 0
IIRC the Compound Statments docs say that [] and () are both sequences, not just lists
Just tested it myself. ```py
match 0,:
... case []: print("was a list")
... case (,): print("was a tuple")
... case _: print("what?")
...
was a list
yeah, found it:
There is no difference if parentheses or square brackets are used for sequence patterns (i.e.
(...)vs[...]).
yes, it just checks if it's a sequence (but not a str or bytes)
This is really nice; I'll give this a try
does ruff/black normalize ()/[] in match/case?
Yeah I remember now, you only get incompatibility if two bases are C types with incompatible layouts
!pep 800
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...
I'm getting UnionType is not a class in Pyright from the Numeric()
I can do this, though:
case [int() | float(), *_a], [int() | float(), *_b]:
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
Microsoft doesn't invest much in pyright, Eric Traut does but he's no longer at Microsfot
ah right
inb4 stalebot
Since they're short staffed, maybe I can leverage Microsoft Copilot to help them out 
Force feed them their own dogfood
Trolling Microsoft is cool, though trolling Eric who's the presumably the only person maintaining a 120k line TypeScript codebase is not very cool
Is he still at microsoft?
Good. I'm a native headphones
I'm also a native microphone.
the desire for Sequence[str] & ~str strikes yet again
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
meanwhile rust has 12 different string types
yes, though a lot of that is a consequence of not being a GC language
so there's differing types based on ownership concerns
and the many different ways to represent a string
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
it raises an OSError
does that basically stop you from using e.g. pathlib almost at all then?
Why would it?
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
open() supports bytes
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
And can you represent that file as a string?
When I print it I just get a blank
but repr()?
not an exception
unprintable characters doesn't mean unrepresentable
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
It's technically still unicode
then maybe this is a bad example? I did a quick google search for creating a filename with an invalid unicode filename
\udc80 is just a surrogate character
Oh yes, this is stinky
hmm, then yeah, probably a bad example
Python strings are allowed to have surrogate characters. And apparently they are used to encode invalid UTF-8 sequences when working with OS paths
I do think a lot of people are going to have entire careers and never once come across dealing with invalid unicode filenames
interesting
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
Could just be bytes
Path doesn't support bytes
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
Yeah, I meant before that (chronologically). Maybe Python 3 changed too many things into unicode
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
It really depends on how you define a char
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
It's too late now 😔
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
and before collections.abc.Sequence was widely used
I've definitely hit this before, too. I can't remember if I decided ot live with it, or just use list[str] instead
I hate this. ```py
set(dir(())) - set(dir(""))
{'class_getitem'}
Here's an interesting quirk... I created a банан.txt file in Windows using Windows1252 as the preferred encoding. When I do os.listdir("C:/") in Python 3.4, I do get "банан.txt" as the file name. But when I do os.listdir(b"C:/"), I got b"?????.txt". Really weird that it doesn't give you the actual raw bytes 
I do wonder how Windows managed to even create a file with these characters if the system encoding is Win1252, it doesn't have б, а, н
it probably falls back to utf8
it's done this since windows 1.0 I think, at least it did for edit charset detection
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
I'm down for blaming microsoft.
again?
never gets old
the real question is why do you have easy access to python 3.4
python 3.4 is the last version to support Windows XP
and I don't have a more recent windows VM
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
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
good taste
although it does run on newer windows too
I'm pretty I've played that game on my desktop
on W11
yeah, it does work
Hello typing people. Looking for feedback and tomatoes on the latest tutorial article.
https://decorator-factory.github.io/typing-tips/tutorial/4-assignability/
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
seems that only 1 out of 9 examples in the first section are negative. Perhaps that ratio should be adjusted. Maybe include a negative example with a generic type too.
You're in the wrong channel. Check out #python-discussion or #data-science-and-ml. Also make sure to ask your actual question, not if anyone is around to answer some questions.
Yes. I am Bender Rodriguez
???
Hi
Oh yeah, I should do something like "int is not assignable to str"
but for generic types, that's sort of why the rest of the chapter exists
not sure what you could do with this, but this line gets clipped, so you have to scroll to see the last 6 chars. It is in the Union with None section.
Yeah I noticed, I agree that it's annoying. I should probably add custom CSS to make the article column wider on all pages
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
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
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"
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
icic
But maybe it's too much work and it's already understantable
ok I'll leave further notes to someone else
What are the use cases of these type hints?
var: Any
var: Unknown
var: object
Should someone ever use object?
Any disables type checking for that var. Unknown doesn't exist, it's a type checker internal. Object is for any type with type checking.
A type is unknown if it can't be resolved, such as an invalid type or a missing import.
Got it, thanks
Not sure I understand the use cases for Any vs object
I understand that one turns off type checking
But what does type checking for any type mean?
Most Any uses can be replaced with Object
var: Any; var.foo() passes
var: object; var.foo() fails
Ooooooh I see
Makes sense
Annotating as object requires narrowing
Thank you!
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:
...
- Can this function be written? How?
- How to restrict
return_typeto onlyintorstr?
def get_parameter[T: int | str = int](size: int, return_type: T) -> T: ...
I think?
Default requires 3.13+
https://docs.python.org/3/library/typing.html#typing.TypeVar
Not sure if it should be T: (int, str) = int
typing_extensions my old friend
If that's used at runtime, yes absolutely
Can it be done in python 3.10?
Wait no it doesn't have to be used at runtime
For my usecase static analysis (not runtime) is enough.
You can import TypeVar from typing_extensions instead
-# I'll let qtit handle the rest
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? 🫢🙈
then Union[type[int], type[str]] would work iirc
What would be the total result of the function definition and typevar declarations?
-# come back here i need to sleep

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
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 
