#type-hinting
1 messages Β· Page 65 of 1
Whats the PEP?
Yeah I am just looking at it again, and I cant possible imagine who would find this Callable[[Callable[Concatenate[F, P], None]], Callable[P, tuple[Response, int]]]
easier to understand then
((F, **P) -> None) -> ((P) -> tuple[Response, int])
677
I mean I think both of these are a bit rough
and I'd use an alias either way
but in any case I think the main point was that it makes parsing harder, not that it's harder to read for people (at least that's what I understood)
where should this error?
It's for positional only arguments
It denotes the end of positional only args
only thing i find confusing is the binding order for ->
Ooh, so its the partner in crime of "*"?
Yeah
nice, another thing learned
Why would maintaining that be hard? Sure the first implementation would be hard but why would it need to be revisited? I though you had to give in a draft of the implementation when you publish a pep
err that's not how software works? π
I am speaking from ignorance though
You got me first
Writing software is generally only a small part of the cost of creating software
maintaining it is much more expensive
But it is a parser
Yes, and we're going to make changes to that parser in the future, right?
I honestly don't know how it is implemented, I am not doubting anything, I guess if I havent had to work with it, it is harder to undersatnd
I don't know how it's implemented either, i'm just going by Jelle's comment
but also a statement like "why would it need to be revisited" as a blanket statement about anything in active software is just kind of incorrect
there's going to be another feature that needs to be there in a week, a month, etc, where the person implementing that feature is going to be impacted (negatively) by the additional complexity in the parser introduced by supporting this feature.
That's more or less inarguable; it's only a matter of degrees: a) how much extra complexity is this really? b) How much impact does this complexity have on future work? c) how much work is happening on this in the future?
PEP 677
Thanks, looks cool actually
I see, from that perspective I can see what you mean
I might be way off on this, but isnt the actual parser codegen-ed from a grammar file?
yes
you still need to write the grammar file, and create the runtime objects that the syntax maps to
Hmm
btw this is the main post with concerns about PEP 677: https://mail.python.org/archives/list/python-dev@python.org/thread/FI4AFU3I25PECARIH2EVKAD5C5RJRE2N/
inchresting
(even though we have an advanced parser in
CPython, that does not mean community maintained projects such as
jedi, parso, black will have one).
that completely went over my head
Didn't even consider lol
Same
everyone just use libcst smh
are you volunteering to rewrite black? π
Kinda annoying to have to import
what does black use
a patched version of lib2to3
Batuhan somehow made it able to parse match statements
yes
Concatenate is such a mistake imo
i wish it was just + or &
or at least let me use those as synonyms
Good idea. That looking better now? Is it right to indicate that from __future__ import annotations is the proper way to do it for older versions without using deprecated stuff? 
not sure, it depends
but generally, for application code, yeah
although I'm not sure
@oblique urchin is this correct?
https://github.com/microsoft/pyright/issues/3010#issuecomment-1033021766
I don't think I understand how F[A2] is allowed
covariant constraints are a thing cause typeshed uses them
I don't think he really addresses your point
yeah
he does that sometimes I think
maybe he just hates me and wants to close the issue π
he did that to me when i broke ci for a while asking for bidirectional type inference on lambdas
(that's my working hypothesis)
he reopened it, maybe I'm just bad at english
success π
new working hypothesis: I'm bad at english π
Don't be so hard on yourself, you are far above average from what I've seen over the years.
hopefully we can resolve this once and for all https://mail.python.org/archives/list/typing-sig@python.org/thread/WZ4BCFO4VZ7U4CZ4FSDQNFAKPG2KOGCL/
@trim tangle a way to get more data on variance + constraints could be to write a mypy PR that gives an error in this case, then look at the mypy-primer results
that would be around here: https://github.com/python/mypy/blob/872c03da11b7e8a11c724e337577b2233573b3b3/mypy/semanal.py#L3193
mypy/semanal.py line 3193
def process_typevar_parameters(self, args: List[Expression],```
Welcome to Discord's home for real-time and historical data on system performance.
typing.Self happened https://twitter.com/raymondh/status/1491187805636407298 
#Python news: It was always awkward to write a type annotation for methods that returned self (an instance of the current class). As of yesterday, typing.Self was added to make this much easier and more readable.
It is a big win.
1/
228
if you have no information about the keys and values beforehand then yeah, dict[Any, Any] is the only option
Great news! But i assume this doesn't work for class methods that return a new instance?
do you have any additional information about the structure? eg if it's one of 3 different options, but you know all 3 options
you could also use object which assumes that you know "nothing" about the contents
object is maximally restrictive, while Any is maximally permissive
That is covered by the use of Self
Self isn't the self parameter it's the type of the self parameter
And hence implementing copy as (self) -> Self is ok to type checkers
Pyright doesn't like the @task line: ```py
from typing import Callable, TypeVar
Task = Callable[['User'], None]
F = TypeVar('F', bound=Task)
def task(func: F) -> F:
...
class User:
pass
class MyUser(User):
@task
def foo(self) -> None:
pass
Is this a bug or am I missing something?
I think bluenix had a similar problem but idk how that ended
Is it not because of contravariance?
I think that might be the issue yeah (Callable's arguments are contravariant)
Callable[['User'], None] <: Callable[['MyUser'], None]
Although you could do
T = TypeVar('T', bound='User')
def task(func: Callable[[T], None]) -> Callable[[T], None]:
...
Ah I didn't realise that
Yeah Callables have some odd variance by default (it may not have been invariant, I just remember that it didn't work). I had a super similar case to yours here, with me wanting to accept a callable with a base class that I then pass some subclass of (in reality that is fetched from the typehints so I know for a fact that the user can handle this subclass) but I had loads of issues. Lookup discussions from me on the Pyright repository.
yeah callables are contravariant wrt args and covariant wrt return type
The newest Pyright version has this behaviour: ```py
from typing import Protocol, Literal
class IFalsey(Protocol):
def bool(self) -> Literal[False]: ...
class Falsey:
def bool(self) -> Literal[False]: ...
def f(x: IFalsey) -> None:
if x:
reveal_type(x) # IFalsey
else:
reveal_type(x) # IFalsey
def g(x: Falsey) -> None:
if x:
reveal_type(x) # Never
else:
reveal_type(x) # Falsey
mypy doesn't do any narrowing with Never in either case
What was wrong with narrowing IFalsey?
This bug
https://github.com/microsoft/pyright/issues/3006
led to this change https://github.com/microsoft/pyright/releases/tag/1.1.220:
Behavior Change: Changed the type narrowing logic for truthy and falsy conditions to exempt protocol classes.
I'm not sure if this is a problem. I can't really imagine the use of a protocol that's always falsey or truthy
Ahh, I see although the list of classes that by defualt implement a bool inst large, why not add exceptions?
Particularly to any Container, Number, and NoneType, isnt that all of them?
jokes on you int isnt a subclass of Number (to the typechecker)
I feel like that's the mypy issue with the best name
hah
Yeah that took me down a rabbit hole that I had no idea about, and two very big walls of text x)
https://stackoverflow.com/a/69383462/13413858
https://github.com/python/mypy/issues/3186#issuecomment-885718629
Brought to you by Python Typing
Well, than pyright or mypy should probably not assume the truthines of any Container, that I think covers almost every case except numbers
Or actually just Iterable, either way it seems weird to assume every object is object in the std is truthy when many of them define len and bool
I actually think if iterable and if container should be avoided, since the protocols don't define any meanings for those operations
And there are iterables (e.g., generators) for which truthiness doesn't imply that it's nonempty
if iterable definitely should be avoided
It's fine for implemented classes though right?
<#type-hinting message>
What do you think about this, Jelle?
I don't see why protocols should behave differently from normal classes
if your variable is typed as a list it's fine
if it's typed as an Iterable it's not
Do you think there's any use case for a protocol that's always truthy/falsy?
can't think of anything, but I wouldn't rule it out
Might anybody know why I can't typehint a linked list node to itself?
class Node:
def __init__(self, value, next=None) -> None:
self.value = value
self.next: Node = next
I can do it inside of __init__ but not actually within. It'll say something weird like:
You need wrap Node around quotes
At the point the function is being defined, the node class itself isn't, and so Node is an undefined variable.
or from __future__ import annotations
Wrapping it in quotes works, is that good practice though?
didn't they add typing.Self in 3.11?
I mean, to the type checker it is all the same, maybe someone knows what to recommend.
Yeh I'd like to know the best practice way to do it
it usually affects very little, I would say to go with the import
How does the import work?
Oh right, you seem to be using vscode
I haven't heard of the __future__ module before
You could import from typing_extension
every annotation is a string of the expression instead of the expression being evaluated
So wrapping it in quotes?
future imports are special in that they can be read by the compiler to change stuff before runtime, and annotations does the above
basically, but implicitly
So, it implicitly wraps it in quotes without me actually doing it?
doesn't seem to be complaining now
That should be an optional though
It makes the annotations strings, but they still need to be valid syntax (unlike what an actual string annotation would do)
What's the difference between typing.Optional and just doing =None?
=None is just the default value, but the default value is not compatible with the type you are providing
They are different things, one is you declaring the parameter to have a certain type, and the other is jsut the default value you give
Optional is for the typing, None is for python
Oh i see
I am actually, I'll give that a try as well
or 3.7+ with the annotations import
Seems like the annotations import is working! Thank you all for the help, i've actually seen that import a few times before but wasn't sure what it actually did :p
here are the relevant mypy docs π https://mypy.readthedocs.io/en/stable/runtime_troubles.html
Optional just means "None is a valid value for this argument"; it has basically nothing to do with the presence of absence of a default confusion. this is a very common confusion, and most people acknowledge that Optional was poorly named
i guess it was meant to evoke the "option type"? with | syntax fortunately we can mostly ignore and stop using it
PEP 677 (callable syntax) was rejected
- While the current Callable[x, y] syntax is not loved, it does work. This
PEP isnβt enabling authors to express anything they cannot already. The PEP
explicitly chose be conservative and not allow more syntax to express
features going beyond what Callable supports. We applaud that decision,
starting simple is good. But we can imagine a future where the syntax would
desire to be expanded upon.
I'm having trouble understanding what they're saying
Are they presenting the ability of the syntax to accommodate additional items as a negative, or a negative that the syntax was too simple to start with
I'm trying to understand what the takehome is. Is it that we we shouldn't leave possibilities open for future syntax expansion?
any pydantic experts in the house?
I have two questions:
- I have a model that needs to be extended to four different models, each with different Literals for the same parameter. I can create new classes for each of the four models and subclass the main model (see example below), but it adds a lot of boiler plate when all that's changing is the value of the Literal. Is there a shorthand way to do this?
class MyModel(BaseModel):
...
class Model1(MyModel):
foo = Literal["bar"]
class Model2(MyModel):
foo = Literal["foo"]
...
- I am creating a model from a TypedDict using
create_model_from_typeddictbut also want to modify an existing attribute in the model which starts out with astrbut again I want to override it to become aLiteral. How do I accomplish this? I have to imagine that the answer to 1 is also the answer to 2, or if not, similar. Perhaps there is something I can pass in to the function to handle this?
ok solved the second one by passing the overridden attribute and type into the function
I guess the question then is is there a helper function to create a model from an existing model
oh, I think I got it
i also dont get this point
the syntax proposed was shown to be backwards compatible with any new hybrid syntax if it was added
i found this confusing too, so i replied asking for clarification
Where about did you ask?
It's not on typing sig or the dev mailing list is it?
idk why, but I find contributing to a mailing list discussion really scary
seems kinda unnatural as opposed to, say, github discussions
maybe I just never used mailing lists
Use the online UI
If you log into HyperKitty you can reply directly to posts with a reply button
@hasty hull π https://github.com/microsoft/pyright/issues/2984
π
It's also in the README: https://github.com/microsoft/pyright#command-line
π³
quite a few people are using it as well, it gets like 10k downloads a week
What do you guys think of type defaults for TypeVars?
T = TypeVar("T", default=int)
@dataclass
class Box(Generic[T]):
value: T | None = None
# This means that if no type is specified T = int
reveal_type(Box()) # type is Box[int]
reveal_type(Box(value="Hello World!")) # type is Box[str]
It would also solve a common problem for things like Coroutine where people want to only give it one argument (The return type)
SendT = YieldT = TypeVar(..., default=Any)
ReturnT = TypeVar("ReturnT")
class Coroutine(Generic[SendT, YieldT, ReturnT]): ...
Coroutine[str] # fine
Coroutine[Any, Any, None] # works fine as normal
Coroutine[None] # would be equivilent to the above
Yes, that would be nice. I was thinking about writing this up as PEP actually, using the same syntax you suggest.
typing.Generator is the obvious use case I feel like
I hate having to google the order every time
One thing I was worried about is whether there would be enough convincing use cases if we can't have named TypeVar arguments
so I just use Iterator
like I'd want to be able to write Generator[int, ReturnT=bool] or something
but that's PEP 637 which was rejected
It might get accepted the second time around with a more convincing use case, but it will be an uphill struggle
Why was it rejected?
Typing always takeing the backseat
but even if it
were, at this point weβre reluctant to add general Python syntax that only
(or mostly) benefits the static typing language.
Then again, they did accept new syntax in PEP 646
It really doesn't help that typeshed uses _T_co or whatever for the names
Yeah, only because there was evidence that it would be useful outside of typing
I think we should plan a coup to put Guido back in charge for a bit so we can get more typing syntax ;)
From the comments that he makes on typing issues, I think he is too pragmatic, we need someone less pragmatic to add new syntax for typing only xD
I mean, Generator[{"return": int, "yield": str}] technically works
I know it's a bit noisy, but still
I don't think so. args: *Ts definitely isn't useful outside of typing
The grammar changes weren't that disruptive were they?
Yes, I think that's the major reason they were accepted
Oh right
I was just thinking of [*T]
Type defaults can come up in the context of HKTs
re use cases
Very useful for CI builds
I want the special syntax for covariance/contravariance, and Optional
I'm bearish on the chances rn though
Particularly the optional one
special syntax for co/contra shouldnt be that bad
Which pep is that?
Unless none aware syntax pep gets accepted, then the optional syntax is probably a shoo-in
I feel the absence of the none-aware stuff pretty much daily
Didn't the author stop because they felt None would be starting to be used too much?
Someone else posted to ML wanting to pick it up and I haven't heard more since then
I have no idea, but that seems like a pretty silly reason to me. All the cases in question, I still end up using None.
It's just more painful.
Because I would agree with that in all honesty. Raising errors in properties are more pythonic than a bunch of Nones.
i thought it was simply deferred by the steerco for the usual reason; not clear if the benefit is worth the syntactic changes
you're assuming that it's for error handling
The SC hasn't taken a position on it
in fact it's not about error handling at all
What are you using daily that's causing you to grab attributes of Optional[X]?
Guido said in the thread that revived it that he wanted to see it accepted and then there was a bit more debate
they deferred it didn't they, so obviously they're not excited about it?
This was a few months ago
discord.py :(
Daily, you try to get data or calculate things, and some data isn't present, and you keep going. You either omit that data from final output, replace it with a default, etc.
maybe a directory is an optional argument, and if it's passed, you want to perform some concatenation operations on it, and if it's not passed, you leave it as None.
you end up with these eyesores everywhere for very simple operations
just pretend falsey values don't exist and use or short circuiting π
sry didn't mean to reply
It's been like over 30 years of Python and we haven't had a ! or ?, they feel un-pythonic π
by that reasoning we can just stop accepting proposals for new syntax π
Did you mean that you didn't mean to ping? Because either way it does not matter to me
Yes
Pretty much all languages these days have some kind of support for either Optional, or None/Null, semantics
I don't mind new syntax such as many of those brought up in this channel related to typing, but those two characters irks me 
Here's an example of a gem like this:
def get_as_date(row: Mapping[str, str], key: str) -> Optional[datetime.date]:
try:
return company_date_to_date(int(row[key]))
except (KeyError, ValueError):
return None
I end up writing functions like this all over the place
I wouldn't mind not having none aware syntax if it wasn't actually the case -in real life- that stdlib and pypi libraries expect me to handle None all the time
it's just so much boilerplate. In Kotlin I'd probably have something like row[key]?.intOrNull()?.companyDateToDate()
when the language gives you tools to nicely compose small things together, you can compose them on the fly and understand what's happening
with python, the composition tools suck, so you end up with these pointless, verbose, brittle, 4-5 line functions everywhere
I actually find it a bit annoying in CI builds sometimes because it auto updates pyright which can cause builds to fail due to pyright changes
not even a large project https://i.imgur.com/NblA9HL.png
well, just handling defaults
because of python's spectacularly weird edge case with mutable defaults
How would yall have felt about a superset language for typing syntax, keeping python itself unchanged? Like js/ts
The compilation step would have been really awkward
Some people have tried this sort of thing with # encoding hacks (not for typing though), and apparently it usually doesn't work well
Why so? 
I like the idea. Not sure how it would it would work though
When TypeScript came around, minifying and babelifying JS was already a battle-tested thing
Imagine minified python, with semicolons for all statements that allow it
it doesn't have to be minified though, it could be like running black or pyupgrade
I am joking x)
yeah I'm just speaking about the overall idea
problem would be that all other tools would have to do different things for typed-python (tython?) and not-typed python
it wouldn't be much like black because you want to keep the original code around too
more like pyc files
oh right, you'd not want to debug the compiled code
So, Python for no types, Cython for C 'types', Tython for uncensored types everywhere
I like the name xD
thanks for that
Wondering what peoples thought are for typechecking tools? I've only used mypy.
Any recommendations? I will be wanting to use it both locally and in CI workflows
I highly recommend https://github.com/microsoft/pyright
Eric is unbelievably fast at fixing bugs and adding features
yeah
pyright also provides autocompletion in a variety of editors (VSCode, Atom, Emacs, Vim, ...)
Are pytype and pyre that bad, no one ever recommends them x)
I think they just haven't had much uptake outside their respective companies
mypy gets a lot of usage because it was the first widely used type checker for Python
and pyright because it's really easy to use from vscode
I used mypy initially, but then switched to pyright
Yeah that is true, pyright is just default for a lot of users, and those who dont mypy does seem to be the first option
Same, I originally switched for recursive types and performance
pytype is kind of weird in my experience. In typeshed CI we occasionally see it crash in very opaque ways
yeah mypy is slow
I once tried it on our codebase at work and it ran for hours, then spit out massive amounts of incomprehensible stuff
so not a great experience
My biggest gripe with mypy is that it doesn't provide autocompletion/types on hover. I get that that is not its purpose, but it really kills half the benefits of static typing
ok maybe not half, but a reasonable part
Yeah, I've found that a lot of people using my ORM actually don't care about the type checking, they just wanted autocomplete
I have a walnut sized brain so I need a smart editor to get anything done
same lol
i'll check it out, tyvm!
also I like Python's approach to keeping the types in the source as metadata instead of transpiling them out since frameworks can use it for generating openapi docs / schema validation / other runtime things. Whereas in typescript you have to have generation steps part of the build process
lol it uses NPM underneath?
It's written in typescript
That way it integrates better with vscode
ah
@hasty hull maintains a package that lets you pip install it, but it just calls out to npm
And it's jit compiled so it's faster
yeah, as absurd as it sounds, it is faster because of JS
and it still has cursed performance hacks
like putting the core logic into a single 20000-line function
I needed to compile them when I wanted to try them if I remember correctly - or some similar install / setup /config issues that I just stopped bothering
Oof, yeah I would not download OCamal just to compile Pyre
Yeah @fiery goblet you can use pyright as if it was native python as my package handles downloading node for you if you don't already have it
And it also automatically keeps pyright up to date
https://pypi.org/search/?q=pywrong time to make a package 
very nice, I just added it to my proj.
I'm using a docker interpreter so I had to quickly lint something so it would download the modules (during the build).
Is there going to be a way to do this via a flag or command? i.e. pyright --install
Not currently but that is a good suggestion
You can achieve the same effect by running pyright --help
ah, i'll do that. ty
saves me from this
&& $HOME/.poetry/bin/poetry run pyright /tmp || true # Installs NPM requirements
I actually haven't tested how my wrapper works within a docker container so please let me know if you run into any issues
does the wrapper allow you to reveal_type on things without actually having reveal_type over the variable?
It doesn't have any type checking features on top of pyright, so if the pyright CLI can do that then the wrapper can do it
how would that work? by a command line flag?
like pyright foo.py --reveal-type bar?
i thought it supported the language server
I forgot about that lol
but that just exposes the language server entrypoint
it finds the JS file to start the language server and runs that
have you ever tried to use for anything, id like to switch my very bad type finder script for docs from mypy to pyright so it doesnt crash when i use features mypy doesnt know about
how does vsc et al do it then?
Oh it just clicked what you mean, yes you could do that but it would be very hacky
I don't know anything about the LSP protocol
You would need to start the language server and send it commands somehow
That's how vsc and co do it
I know that one method you can use is JSONRPC but I don't know what the structure of the commands is
Language Server Protocol documentation and specification page.
lmao wtf
fair point
What am I doing wrong?
def sign(self, signers: Optional[list(PublicKey | Keypair)] = None) -> None```
```TypeError: 'types.UnionType' object is not iterable```
You are calling list on the Union, you need to use subscription for generics
list[PublicKey | Keypair]
Np
btw: you probably want Sequence or Iterable and not list
from collections.abc import Iterable
...
def sign(self, signers: Optional[Iterable[PublicKey | Keypair]] = None) -> None
An maybe even a tuple default
from collections.abc import Iterable
...
def sign(self, signers: Iterable[PublicKey | Keypair]= ()) -> Noneβ
Why iterable? The iterable needs to be mutable so tuples won't work, order matters here so sets won't work either, so we are only left with lists right?
Isn't setting a default value like that not right? I remember seeing a warning somewhere for empty default list and dict, not sure if it applies on tuples as well
Ohk
What about the iterables type hinting? ^
Tuples are Iterable
Wait you need it to be mutable? What for?
yeah, it will run some validations which will add items to it if it's empty or the required length is more
One of the class attributes needs to be appended to it as well, and if I am not wrong; I can't access any attribute in the function header itself
What does the caller do with the mutated signers list?
Iterates over them, signs a message (signers are a keypair object) and stores all those signatures and their respective signers in a dataclass assigns all those objects to an existing class attributes (self.signatures)
No the caller
Why not just return a new list?
What does ((str) -> _T@add_argument) mean in this type hint? It's the -> I don't get - I've only seen it used for return types.
I think that is how Pyright shows its callables
So type would either take a FileType or a callable with str as a param and return some T
Not sure though
Whenever I put Iterable as a typehint.. Doesn't that mean that my function should also handle strings? Since theyre Iterables
yes
Usually I just want to ducktype away on dicts/tuples/sets/lists..
Would the correct way be to typehint Union[dict, list, tuple, set])?
That's a meaningful type hint but not a very good one, because it doesn't say anything about what should be in the dict/list/tuple/set
I know, I'd add int or whatever to it, the question is more about: How do I exclude strings from my "Containers"-type hint
even though I guess technically str is also a container
I would just stick with Iterable. There is some talk about adding a Not type that would let you say "any iterable except str"
why do you want to exclude it?
i.e. why does the difference between a string and an iterable of strings matter?
I'm not sure I understand the question
!e
def myfunc(my_iterable):
for i in my_iterable:
print(i)
myfunc("string")
myfunc(["str1", "str2"])
@dapper yacht :white_check_mark: Your eval job has completed with return code 0.
001 | s
002 | t
003 | r
004 | i
005 | n
006 | g
007 | str1
008 | str2
I think they were just asking what is the situation in which your function could accept any iterable except a string
now if I wanted to print every username in a list, id iterate over a list (like above).
Do you mean I should do something like this?
!e
def my_func(my_iterable):
if isinstance(my_iterable, str):
my_iterable = my_iterable.split(",")
for elem in my_iterable:
print(elem)
my_func("asd,123,bcd")
my_func(["asd", "123", "bcd"])
@dapper yacht :white_check_mark: Your eval job has completed with return code 0.
001 | asd
002 | 123
003 | bcd
004 | asd
005 | 123
006 | bcd
no, just that there's no functional difference between the two apart from the contents
Yeah, but isnt the point of type hinting to know the contents of the supplied object?
its job is to take care of the types, not the actual runtime contents (most of the time)
if you were to exclude strings for that reason, should list("string") also be invalid?
what if I don't want to have strings in my Iterable
any object that is not a string
What if I wanted to convey, that theres an Iterable with instances of MyClass?
def my_func(iterable: Iterable[MyClass]):
pass
Does that automatically exclude strings?
Yes
I might just be very confuzzled
it'll only allow iterables of MyClass
if you know what you want to exclude then you most likely also know of a generic parameter to give it
You could make an overload that returns NoReturn, but that's kinda weird
What if I wanted to accept a list of dict-keys, a Iterable[str] wouldnt be very fitting because it includes str right?
it's still an iterable of strings that can be used as keys
Yeah but would it be a sensible type hint for that function?
If that's what it needs, sure
I think I was trying to hard to make the function scream "Enter sensible dict keys here". But that's not entirely the point of type-hinting/duck typing?
yeah that's more of a job for the docstring
Ah ok
Damn
Its just to check (dynamically or statically) if types match and the program flow wont stop unexpectedly
if it's crucial that it can't be 1 char strings for some reason, then you can validate it at runtime
be it mypy or some IDE highlighting stuff
I hate validation
I'd rather have the user (mostly me) validate what he calls the methods with beforehand
Cheers though
thank you
is if pkt.mode != -1: your way of narrowing?
from Union[list[Packet], list[str]] to list[Packet]
that's not a design that's very compatible with type checking
I mean, you can use a typeguard if you change some stuff around
One sec
Actually, not sure how to do this one outright x)
either way it is supposed to narrow your types
day16/part1.py lines 15 to 23
class _Packet(Protocol):
@property
def version(self) -> int: ...
@property
def type_id(self) -> int: ...
@property
def n(self) -> int: ...
@property
def packets(self) -> tuple[_Packet, ...]: ...```
from typing import TypeGuard, Union, Protocol
from dataclasses import dataclass
class _Packet(Protocol):
sub_packets: list[Packet]
@dataclass
class Packet:
version: int
type_id: int
mode: int
sub_packets: Union[list[Packet], list[str]]
def is_type_packet(pkt: Packet) -> TypeGuard[_Packet]:
return pkt.mode != -1
packet_heap: list[Packet] = []
if is_type_packet(pkt): # pkt is an object of type Packet
packet_heap.extend(pkt.sub_packets)
Or maybe it would be better to use Generics? Either way this seems to work
is_type_packet now is the guard, if the object you passed, pkt returns true, that means it adheres to the type of the TypeGuard, which in this case it the _Packet Protocol, which always has a sub_packets of type list[Packet]
Although if you try to do anything else with it that you could with a normal Packet the type checkers will complain since now your object only follows the protocol and not you original Packet
In these instances I would honestly just cast and forget about it x)
Or type ignore as you said
@hidden spoke
protocol is typing where you define what something can do, so you don't have to type by inheritance. imo Protocol works best for typing function signatures, this seems like a weird example
i.e
class Edible(Protocol):
def eat(self) -> None: ...
class Candy:
def __init__(self, colour: str) -> None:
self.colour = colour
def eat(self) -> None:
print(f"Chewy {self.colour} Candy! Nom nom.")
class Fruit:
def __init__(self, name: str) -> None:
self.name= name
def eat(self) -> None:
print(f"Yummy {self.name} fruit")
def digest(food: Edible) -> None:
food.eat()
del food # or something,
yellow_candy = Candy("Yellow")
apple = Fruit("Apple")
digest(yellow_candy) # Implements `eat` so it is correct typing
digest(apple)
That example is pretty weird because the protocol references the concrete class
usually, one of the main points of using protocols, is that the Protocol doesn't have to know about the concrete class, and the concrete class doesn't have to know about the protocol
Looking back at it, it also doesnt have to be a protocol, just subclassing Packet was enough and it also made sure it had all methods
that was a terrible use a Protocol x)
yeah I don't think that the issue here was one of using language features like Protocols, or type checking
it's more a matter of design
having a static type depend on a dynamic value, basically.
you really want to express that within the class, because it's a class invariant
well, the idea with type checking is that its static, right
but a value like pk.mode is dynamic
outside of very very trivial cases, it can't really know if pkt.mode is -1, without running code
The type checker can't understand that whenever mode has a certain value, it's going to influence the type
So, this is the kind of place where python starts to suffer because the obvious solution here is a lambda
but python lambdas suck
but, it's probably still what I would do
@dataclass
class Packet:
version: int
type_id: int
mode: int
sub_packets: Union[list[Packet], list[str]]
def apply_to_non_str_sub_packets(self, action: Callable[[list[Packet]], None]):
if pkt.mode == -1:
raise RuntimeError("Error, stored subpackets are strings!")
action(cast(self.sub_packets, list[Packet]))
maybe something like that?
Alternate approaches: a) Just don't allow a list[str] to begin with, process it into a packet. b) maybe some kind of inheritance hierarchy.
or a tagged union
Yeah, i haven't been following pattern matching, so I don't know where that's at
this is also kind of a painful situation in python because you start to see breakdowns between the static type system and the dynamic type system
Union[list[Packet], list[str]], translates to a dynamic type of just... list
i've never seen this Annotated very cool, ty. if this is what you was in reference to
https://docs.python.org/3/library/typing.html#typing.Annotated
no, this is not related
let me dig up something
It's an algebraic data type. a.k.a. union type, sum type, discriminated union, tagged union, variant record, also mentioned at times as Rust enums, sealed traits, disjoint union, variant, choice type, coproduct, disjoint coproduct, tagged variant, product dual, tagged product dual, discriminated coproduct, intuitionistic logical disjunction under the CurryβHoward correspondence, and the embodied thought process of Henry VIII
Basically, you have a type that can take a few fixed structures, like ```hs
data PaymentMethod = Cash | CreditCard String | Voucher VoucherId
If the core language is standardizing pattern matching, they should really standardize a proper sum type
because depending on the dynamic type, as per the current situation, runs into issues like the one above
time to google sum type
its the same thing π
You can already implement them with some hackery. But, of course, type checkers don't like that @terse sky
<#type-hinting message> π
Right. That's why it seems silly to me.
The current approach has these weird edge cases
yeah it's just weird though
and unintuitive
and with boilerplate you can't eliminate
@dataclass
class HoldsListPacket:
data: list[Packet]
@dataclass
class HoldsListString:
data: list[str]
@dataclass
class Packet:
version: int
type_id: int
mode: int
sub_packets: Union[HoldsListPacket, HoldsListString]
truly, a thing of beauty π
i'm not even necessarily pro patma for a language like python, but if you're already doing patma then to me it just makes sense to also be able to handle this properly
What does this fix? I don't really get the difference π
If I have a packet, and then I get the sub_packers, I still only have a Union, meaning weather I can or can't do something like Packet.sub_packets.data[0].version or Packet.sub_packets.data[0].upper() is still dependant on the mode, which the static checker is unaware about
I kinda wish callable syntax had just been deferred for a few years instead of rejected
maybe it can be revisited if it's ever decided type annotations can have their own syntax a long time from now
what it fixes is that the union of dataclasses, you can use isinstance directly
if mode only has two values, then at that point, you don't really need to store mode at all; you can derive it from the isinstance results
isinstance is basically one of the places where python's static and dynamic type systems meet directly. And in this case, you can see it's not a happy meeting.
When you have Union[list[Packet], list[str]], to the static type system this is all distinct and logical and fine.
The problem is that the way you use Union right now in python, it typically depends on isinstance checks, and isinstance checks only use the dynamic type, not the static type
and the dynamic type of that thing is always just list, you can't distinguish between the two cases, unless you start iterating the elements, which is just silly
Ohhh It set in now, you are eliminating the need to depend on a dynamic variable by using isinstance checks instead.
That is why you are wrapping it around an object right
sum/discriminated union types in othe rlanguages don't work this way, they don't just depend on one of the values being there.
Yes.
I see, I dont really know anything else so it kind goes over my head
At first sight I dont see what is wrong with the dynamic variable x) But yeah it would be cool if python could redesign the isinstance check or add some runtime behaviour that conforms to a static Union
the problem with the dynamic variable is basically exactly what's being shown here
if you have a separate dynamic variable, then the static type checker doesn't understand this relationship
how can it, after all? They're two completely unrelated fields from the perspective of the type checker, it can't even begin to verify an invariant as complex as that, globally
I mean to say at first I didn't see, I see now, because those two are completely unrelated
But I guess the only thing I wonder is what would be some valid uses of TypeGuard in Python
In a language like say, Rust, a Rust enum stores an integer like this, as part of the block
the discriminant
and then you can do if statements or pattern matches on the enum, and operate on it.
well, now I'm curious π
well actually, as a detail, ||it can often optimize this. For example, if you have an Option<&Foo>, it can use a null pointer internally to represent None||
The thing is that the typeguard, afaics, is just in the end a fancy wrapper around isinstance, so you still have the same nonsense I was pointing out before:
def is_str_list(val: List[object], allow_empty: bool) -> TypeGuard[List[str]]:
if len(val) == 0:
return allow_empty
return all(isinstance(x, str) for x in val)
yeah, I'm aware, was just saying for exposition
which is why it leaves the layout unspecified, β¨ optimizations β¨
that doesn't actually matter because presumably the discriminant function is still available
yeah it doesn't impact usage at all
but yeah, I feel like generics are really where you start to see a lot of the wonkiness in python's "dual" type system
it has all of the wonkiness of Java type erasure but much less protection
To add to your point: the is_str_list function has a subtle issue. It doesn't guarantee that the list is actually a list[str].
I meant that someone else could hold a reference to it as a list[object] and push some nonsense to it later
well, that's a separate issue
that would be mutating it later on
at the time the function is run, it's still what it says it is
the problem is that it's still not correct
a list[Animal] cannot have [Cat(), Dog(), Elephant()]
it can
this is ok ```py
things: list[object] = [1, "foo", (1, 2, 3)]
This is surely correct: py animal: Animal = Cat() and you can add an Animal to a list of animals: ```py
animal: Animal = ...
animals.append(animal)
hold on, I'm trying to sort out if I've just confused myself π
what you can't do is you can't assign a list[Dog] to a list[Animal] variable. But you can assign a Dog to an Animal variable.
(because T is covariant in T, to speak in scary words)
yeah, I think you're right. This is one of those cases I always get a bit confused on.
there is a difference between the type of a variable and the concrete class of a value, I think that's a bit confusing
as in, here py animal: Animal = Dog() the (conceptual) type of the animal variable is Animal, but the value it holds is an instance of Dog
at any rate though. Iterating all the elements in the list for a type guard is pretty bonkers.
yeah that part is pretty simple
that's just dynamic vs static type
beartype does some black magic, I've heard
it's when I start thinking about covariance/contravariance that things get a bit wonkier
but I haven't looked into how it does it tbh
I haven't tried any runtime type checkers
Speaking of,
I just pushed a draft version of Subtyping and Generic types articles to Typing Tips, I'd be grateful for feedback from anyone
https://decorator-factory.github.io/typing-tips/
do you plan to cover variance at some point?
yep, that's the next article
from my (limited) experience it's really hard to explain
the rustnomicon has a good explanation you could try to get inspiration from
The Dark Arts of Advanced and Unsafe Rust Programming
The only one I tried, Beartype, is nice. I have absolutely not measured its claim of being unπ»ably fast, I just like how stupidly convenient it is
trivially covariance, contravariance and invariance correspond to these propositions ```
((A => B) => (A => C)) => (B => C)
((B => A) => (C => A)) => (C => B)
(((A => B) => (A => C)) and ((B => A) => (C => A))) => (B <=> C)
"it is well known that"
I should feel bad for seeing arrow functions
we shall leave the proof as an exercise for the reader
The hardest to understand seems to be contravariance.
It literally seems "backwards"
i mean the simplest way to understand it IMHO is in terms of a function
with one argument, and one output
suppose you have a function foo. foo would like to accept a function that takes an Animal, and returns a Robot
i didnt think contravariance worked in functions?
it should π€·ββοΈ
oh methods?
there's no methods here
Functions are contravariant in the parameter types
Animal -> Foo is a subtype of Dog -> Foo
they're contravariant in the parameters when you want to pass a function to something
no
no the other way around
i toldya it's hard!!
yeah, you're right, i just think of it in terms of when is the function usable in the way you want, is the idea
but I mixed that up
but at any rate it's still the simplest pedagogical tool, because the function does both sides of it
it's probably a pretty easy transition. "but what is the type of a function :O?"
Other useful examples of what?
contravariance, presumably
but yeah, it's more rare
...well, an output stream of Ts is contravariant because it has a method that's contravariant in T π
well, that's also true about covariance π Why is why functions are the natural starting point I suppose
yep
all of variance can be explained with function variance
a type is invariant if it contains a covariant method and a contravariant method
Although for covariance it's kind of unintuitive. I think tuple[T, ...] is good for that
I think I'll do the variance article tomorrow, it's about 4am here
Maybe I'm trying to do the wrong thing here, and a markdown file alone is a bad medium for explaining it.
It's like trying to explain music in text
I also know some basic set theory, so some explanations make sense to me but not to normal people
well, not "normal"
idk what's the right word
I think I'll need to think of some good illustrations
like shapes with holes, or something
although I'm afraid that this will turn into "monad tutorials"
i.e. using weird analogies when just explaining what's going on would suffice
maybe I could pay someone to record a video
but that would be like unearthly weird
Is there any way to pass along a signature like with a Callable while requiring something to be a FunctionType?
you mean, an intersection between FunctionType and, say, Callable[[str], int]?
yeah something like that
I don't think there is. The closest you can get is a Protocol with a __call__ method and other attributes you're interested in
However, not sure how well that would work in practice. I don't think type checkers know that user-defined functions are instances of FunctionType
It's kinda sad that returned functions are typed as Callable. This makes working with methods really hard and inconsistent across type checkers
because any old Callable behaves differently in a class context than a function, because a function is a descriptor
so e.g. mypy has a weird thing where a callable instance variable is treated as a method
and I wasn't able to decorate a method in pyright, somehow
ah that's a shame
I think the callable pep rejection does a lot to convince me keeping typing syntax and language syntax in sync is not the way forward
I think we may be failing to communicate with the steering council for some reason: for example, there was consensus in typing that the syntax change and possible expansions would have made the situation better and easier to understand/read, but the PEP was rejected because it would have made things harder to read - so there's clearly a disconnect
huh? Isn't the callable syntax used in tools already anyway?
It as implemented as a proof of concept of the (now rejected) PEP, yeah
but I'm not sure where the disconnect happened
That could just be a difference in opinion. Several people on python-dev also brought up that it's hard to read if there are multiple arrows
no doubt
It is really good explanation, i love it
But i cant remember what is "covariant" and what is "contravriant", i confuse them
The way I see it, covariant is the one I expect most of the time and the one I understand
I know the difference but cant understand what is being conveyed by those arrows lol
If you don't understand it, then it's most likely Haskell
Given A :> B then,
Gen[A] :> Gen[B] If Gen covariant on first param.
Gen[B] :> Gen[A] if Gen contravariant.
Where SuperType :> SubType
I would still like to understand what the arrows mean though xD
those are logic symbols I think, https://en.wikipedia.org/wiki/List_of_logic_symbols
In logic, a set of symbols is commonly used to express logical representation. The following table lists many common symbols, together with their name, how they should be read out loud, and the related field of mathematics. Additionally, the subsequent columns contains an informal explanation, a short example, the Unicode location, the name for ...
=> is Implies and <=> is equivalent
Yeah to me it means implies
It is implication
But I dont think it is what it means there
A=>B means "if A, than B"
The meaning of notation changes depending on what you are doing
The meaning of notation changes depending on whether or not you are doing pattern matching syntax in which case match is a keyword okay
I think the tree sitter pattern matching syntax parser works now
It doesn't support the _ keyword though
But whatever
Python actually specifically doesn't use the normalization form recommended by Unicode for programming
I have no idea why
Maybe it was a mistake
hi
like I used to do with FastApi routes, I want to make a function that is expecting a dict. I want to type hint like in FastAPI with a pydantic model.
Note that I am just using FastAPI as a reference here and this app serves a total different purpose.
What I did:
models.py
from pydantic import BaseModel
class Mymodel(BaseModel):
name:str
age:int
main.py
def myfunc(m:Mymodel):
print(m)
print(m.name)
myfunc({"name":"abcd","age":3})
It prints m as a normal dict and not Mymodel and m.name just throws an AttributeError.
I don't understand why it is behaving like this because the same code would work in FastAPI. Am I missing something here? What should I do to make this work.
I am expecting a dict arg in the func, I want to type hint with a class inherited from pydantic BaseModel. Then I want to acccess the attributes of that class.
I don't want to do:
def myfunc(m):
m = Mymodel(**m)
Thank You.
@mortal fractal maybe allowing non-ascii letters for identifiers was a mistake π
I mean, even if you wanted to do that they still did it in a way that seems mistaken
they used one of the equivalences that isn't just similar letters, objectively different letters can be equivalent under the system they chose
On the plus side, fewer programmers are driven to madness because an identical-looking code point accidentally got copy & pasted π
Annotations don't change how a function works at all. They are just documentation that can be used by automated tools (like mypy or pyright) or inspected at runtime by a library.
I recommend taking a look at the articles in the top pin
FastAPI inspects the annotations of a function and does something similar to Mymodel(**m) under the hood
oh thank you, I'll look into their source as well as the pinned articles.
You can make your own decorator that does this, if that's what you want
sure thanks again
hey guys! do you use boto3 or aiobotocore?
No. Hope I answered your question! π
Question: why would anyone use type annotations? Isn't it just a waste of time?
supposedly it is not
Because
a) It gives your better self-documentation to the code
b) you can use mypy instruments to run automatic tests for correctness of your type hints, to imitate weakly strong typed language, so you are adding additional layer of unit testing layer to your code basically
Which can be an advantage in big python project code bases to fight the python weaknesses of being too freedom language
a) You can just write documentation (or e.g. make better names)
b) You can just add more testing coverage
well yeah. But documentation as a code is always better
documentation gets old, code usually not
For that you can use contracts (e.g. using deal). It can express more than types can.
playing devils advocate x)
To add to b): types only cover some rather basic contracts. For example, you can't ensure that a function always receives a positive integer.
I think the most noticeable benefit right off the bat would be providing information to tooling like auto completion
Do people pass the wrong times all that often, and wouldn't these blatant errors be caught by tests anyway?
looking at this library, I think type hinting and deal covers still different aspects of python code documentation.
anyway, typehinting is in built, and supported by IDEs, to give you better code colorization / documentation to write the code at IDE interactive level
once you write type hints, your IDE knows what your stuff does, and makes correctly shown object types and code colors, and hovering tips in its interface
Sure, it speeds up development. But it's not free:
- you need to invest in tooling: run
mypyin your CI pipeline - you need to spend extra times on writing types - the time you could be adding more features, or improving the design, or writing more tests
- you need to code "around" the type checker, sometimes change absolutely valid Python code because
mypydoesn't like it
this thing can be especially important, if your code is used as a package
less pain to people who use your library
we wish to be not seeing the code of the library ;b it should be good to use at docs/interactive IDE tips
I have found that often when Mypy complained it was because there was an underlying issue with my design, thus I think that second point is null, sure it makes so you have to code around it, but also prevents you from shooting yourself in the foot
Also, potentially type hints could be used as a part of building auto documentation to the code in some auto building tools π€
And that's we we come to the so called RISK MANAGEMENT π
if our software works stabely and matching the desired 99.99% uptime or whatever we wished, we can risk and to make new features faster
Right, so the question is: do the benefits outweigh the costs? And how do you make that conclusion?
Is there any research?
if it breaks too often, perhaps it is time to take better care of unit testing / refactoring / type hinting and etc
yeah, like here π€ this is kinda misleading
Also, although it doesnt ensure specific contracts, If I just stumbled on a function that I didnt code, it is harder to parse all the contracts that the function adheres to. Knowing the type gives me a general understanding of what the function expects, and what I can expect from it, without having to read into specific contracts. I mean ideally you would have contracts and posconditions on top of the type hints
I usually don't type enough to satisfy a proper checker in the complex cases, just to get proper support from pycharm
If used like that I'd definitely say it saved me some time, but there are places where I just said fuck it because I had no idea how to achieve something and didn't think it was worth the time when not exposed as a library
I write my architecture in microservices, and they still did not grow to the size I would care to use type hints tbh
every part of my architecture contains so small amount of code, so it remains simple for me to be not caring about big code growth issues
we can expect encounter the need for type hints in big enough amount of codes I guess
they should be important for scaling the code of the project
Lots of Python libraries use dynamic things that are not really subject to static analysis. They need hand-crafted mypy plugins that, well, only work in mypy.
Or rather, Python's typing system failed to account for these already existing libraries.
So what problems do type hints solve in a big project?
anyway, for big enough code bases I received advice... to be just not using python
perhaps it would be a time to just switch to Golang
So type hints are a waste of time?
just remembered https://sethmlarson.dev/blog/tests-arent-enough-case-study-after-adding-types-to-urllib3
in my opinion? .... perhaps yes.
There are better ways to improve the code
writing better unit tests
integration tests
improving CI CD pipelines
for the spent time more result in code quality
but if I would be having a goal to make documentation, if i would be leaving the project and needing to transfer to other people, I would probably think to use every advantage for documentation, including type hints
For me the biggest benefit happens when I look at a library and I want to see how the function needs to be called, or exactly what it returns. Without type hints you have to rely on (often vague or nonexistent) documentation.
I have seen this. Is there some summary of the bugs that have been found?
oh, I see now
So it seems like they found 3 actual bugs?
- comparison of strings and bytes
PoolManager.connection_from_X()APIs which, as noted, very few people useSSLTransportinterface
Yeah, I agree that libraries benefit from more additional documentation, especially more rigorous documentation in the form of type hints. I also agree that, from personal experience, developers are less reluctant to use type hints than to write documentation (especially if you enforce them)
I like type hints and I watch the whole typing world very closely. But it's purely a subjective feeling, so I'm wondering if there's some actual data on whether type hints are a waste of time, or whether they solve some problems at a reasonable cost.
I know that there are stories by companies and library writers who got some benefit by adding type annotations. But I think it's subject to the "survivorship bias": nobody will make a story about typing 100k lines of code and losing a million dollars as a result.
I think if used in the manner I described (not going for completely passing some type checker, but to provide information to things like IDEs and users) it definitely helps as the typing itself shouldn't take long, but I'd say it's arguable if you do use it with strict checks and everything as some constructs are hard to type
maybe more worth it if you do need the safety it provides, but then you can also use a static language and be sure that everything is correct while usually being simpler to use from the typing perspective
Yeah, that's also a good point. If you need the benefits of static typing, why Python?
tests = [("ABAB", 2, 4)]
for *input, ans in tests:
a, b = input
# print(Solution().characterReplacement(*input), ans)
print(Solution().characterReplacement(a, b), ans)
# junk.py:24: error: Argument 1 to "characterReplacement" of "Solution" has
# incompatible type "object"; expected "str"
# print(Solution().characterReplacement(a, b), ans)
# ^
# junk.py:24: error: Argument 2 to "characterReplacement" of "Solution" has
# incompatible type "object"; expected "int"
# print(Solution().characterReplacement(a, b), ans)
# ^
mypy doesn't understand the unloading operator?
@rustic gull In this case, input will be a list. A list doesn't have information about elements at different positions, only an element type. So input gets inferred as list[Unknown], or list[str | int] depending on what type checker and settings you're using.
Why not do ```py
for a, b, ans in tests:
...
I see thank you
I love using a library with type hints I don't need to spend nearly as much time backtracking where a function is used, and even if I want to it's suddenly much clear where I need to go
I haven't yet added types to a codebase where it didn't uncover at least one π
I agree that it is useful for libraries, especially those that don't use magic but are just boring Python code
My question is more about whether it's profitable for a commercial/enterprise application
Of course finding bugs is good. But there's more than one way to find bugs (like auditing, code review, adding documentation (surprisingly), testing (including more advanced things like mutation testing property testing))
enterprise applications are mostly libraries
if you have a million lines of code, you don't know what all the functions do
I guess
I guess I'm just not finding these theoretical/subjective concerns good enough
typing-extensions 4.1.0 was released. Enjoy using TypeVarTuple, Unpack, LiteralString, never, assert_never, reveal_type, dataclass_transform, and is_typeddict
@oblique urchin do you know when dataclass_transform is gonna be submitted to the SC?
Not sure. Doesn't seem too urgent since it will be simple to implement in CPython
Is reveal_type just a runtime version of mypys reveal_type?
no it's much less complex than mypy's version
also wasnt is_typeddict already part of python?
I need to read the relevant peps x)
it's not got a pep for it, I'm pretty sure it's just got a BPO
it's only in python3.11? iirc
yeah it's a backport of that
Ah, got it
Is there anywhere I can read about it? The reveal_type, assert_never and never
cpython dcs
Oh right, just put the version to 3.11 dev
https://mail.python.org/archives/list/typing-sig@python.org/thread/YIZ3BKBQZVBTZH6GUA2EBOSWN7LAHE6M/
https://mail.python.org/archives/list/typing-sig@python.org/thread/CGIYKGMF6XLDX3F2JNRCCE7HC6K2XLZ2/
https://mail.python.org/archives/list/typing-sig@python.org/message/OTOZ4TZLZ64NHCODCHPUIBJ5FL5JZUD6/
the thing is that it's very hard to prove anything properly in software
if you're interested, a pretty good literature survey: https://danluu.com/empirical-pl/
As for the "why python" of it, I think in most cases languages aren't selected for those kind of reasons, practically. It's more the usual boring but important things; which libraries are available, supported languages for APIs you care about, ease of hiring, existing code, etc.
I do agree if you like static typing, and none of those things are a factor, python isn't likely to be your first stop.
For me though and probably many others, using python for various things made sense because python was already a lock, for its quantitative stack.
On the flip side I really like having types for the non-quant code in our codebase; a lot of the code is stuff that is very hard to meaningfully test because a lot of the work can be ssh'ing into boxes, running random commands, pulling git repos, etc. Types catch a lot of silly mistakes cheaply and it's not so easy to write unit tests that will catch those same mistakes cheaply without more time spent mocking than is really worth it.
So at the moment, I have some code which takes advantage of a function's __annotations__ to cast the provided arguments to what they are hinted as, but this will break once 3.11 arrives and all type hints are now strings - is there some other solution I can use? I'm wondering if something like pydantic might have a tool that solves this, or if I'll have to parse it myself
The feature is not yet set to arrive in 3.11 afaik
It was deferred again to 3.12.
there's also https://www.python.org/dev/peps/pep-0649/ as an alternative
What you can use is either typing.get_type_hints(), or inspect.get_annotations().
get_type_hints() detects the stringified versions, and execs them to get the real object. It also by default unwraps Annotated, so you can just get the types and not annotations.
- as of 3.10
-gs typing.get_type_hints
brb
didn't always work before 3.10 https://github.com/python/cpython/blob/2cd268a3a9340346dd86b66db2e9b428b3f878fc/Lib/typing.py#L1796-L1801
Lib/typing.py lines 1796 to 1801
# This is surprising, but required. Before Python 3.10,
# get_type_hints only evaluated the globalns of
# a class. To maintain backwards compatibility, we reverse
# the globalns and localns order so that eval() looks into
# *base_globals* first rather than *base_locals*.
# This only affects ForwardRefs.```
Ah, I see
inspect.get_annotations() is more generic, but handles some specific differences between how annotations work in classes, modules and functions.
And if I import annotations from __future__?
That still works.
if you do that make sure to not stringify your annotations
Since I'm planning on doing that
eg, don't both import and use ' around your annotations
Alright
Have a look at this guide here, which explains how to deal with everything in more detail:
https://docs.python.org/3/howto/annotations.html
Ah ok, thanks :D
isn't importing annotations already making my annotations strings?
Basically explains why to use these functions and what to do in 3.9 or less.
Yes it does.
Right
yes
So if you manually wrap in quotes, it'll be quoted twice which is a bit confusing.
yeah
Hm, so inspect.get_annotations wont return stringified annotations?
ah, but inspect_get_annotations in only in 3.10+
I'd like my code to work on 3.7+
ah, this might have a solution for that
oh
this is nice
I don't need to fix this, I'm using pydantic and that already does this for me
Has it already been officially deferred again?
I thought it was still just in talks
It has not
Ah alright
Does anyone know how to type hint pandas column so that they suggest you columns in a DataFrame?
E.g.
import pandas as pd
df = pd.DataFrame({"fruit":["apple","banana"]})
def print_fruit_df(df: pd.DataFrame):
print(df.fruit)
When I am just looking at the functions that accept a DataFrame, I never know what columns names the accepting DataFrame will have and it is really annoying
Instead, I want to do something like this:
class FruitDataFrame():
def __init__(self, df) -> None:
self.df: pd.DataFrame
self.fruits: pd.Series[str]
def print_fruit_df(df: FruitDataFrame):
print(df.fruit)
So that the text editor can figure out what coulumns the data frame will have
but the above is inconvenient because I cant set a column data to the DataFrame through the attribute without writing a custom setter for each columns: I.E.
df.fruits = ['new fruit']
Would not update the coulmns in the dataframe and I would have to write a custom setter function for each columns and that is really annoying
Pls help
I found out about typedDict, and I would want to do something like
from typing import typedDict
import pandas as pd
class FruitDataFrame(pd.DataFrame, typedDict):
fruit: pd.Series[str]
df: FruitDataFrame = pd.DataFrame({'fruit': ['apple', 'banana'] })
Something like that
@spiral blaze I think that's not possible
(I'm not familiar with pandas for the record)
I think it's possible to make a mypy plugins for this, maybe there already is one. But it will not help with IDE autocompletion
can you tell me a bit about making your own type hint though so that I can craft something that is atleast useful?
Like in theory, I could just create a custom class, or use a typedDict as the type hint for pandas correct? @trim tangle
But it will not help with IDE autocompletion
somehow this can be done
probably
okay so ours works with overloads and __getitem__
I don't really know about pandas, maybe someone with more knowledge can help. But it will probably be some kind of hack
like I dont understand how vs code doesnt even have a proper implementation of typedDict
It does
it doesnt suggest you anything though
it does
it just points out when you are making a mistake
it's TypedDict, not typedDict
Are you using Pylance?
ye but not when:
foo.bar
Right, because a TypedDict is a type for a dict
but cant you access value of dict like that?
@trim tangle :x: Your eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 2, in <module>
003 | AttributeError: 'dict' object has no attribute 'foo'
this is not JavaScript π
i mean you can if you implement a subclass of dict and use that
where do i ask about help with time.time() ?
bs4 comes to mind
ye so I wish I could just use TypedDict but not give me warning when I assign a pandas Dataframe to it
You should see #βο½how-to-get-help and claim a help channel
ok
sure
!pypi attrdict
I.e.```
from typing import TypedDict
import pandas as pd
class MyType(pd.DataFrame, TypedDict):
x: int
y: int
a: MyType = pd.DataFrame({'x':[], 'y':[]})
a['x']
but linter complains when I assign a pandas Dataframe
right, because it's not an instance of MyType
You can put a # type: ignore at the end if you want to ignore the error
ye but like, arent you supposed to be able to create new types?
What do you mean?
If you have this:
class A:
pass
class B(A):
pass
``` you can't just do ```py
b: B = A()
``` because it's not true
? The only reason why I want to inherit is so that it will still hint me the Pandas Method as well as their columns
how do i make time.time() show only hour and minute and second in the local time
in that case, you can put # type: ignore
this might help: #type-hinting message
my english isnt very good, i didnt understand what was in the channel
ye, but its awqward to do that every time, I want to introduce a Data Oriented Programming Paradigm with Pandas Dataframe
so basically, I will be passing Pandas Dataframe to lots of functions
I wish I could code up some custom Typehing so that I can do what I want
@vague tulip You need to click at one of the channels in the " β AVAILABLE HELP CHANNELS " category and just type your question in it
but I guess its not possible
ohhhh ok
sad, I guess I will stick with: https://pypi.org/project/data-science-types/
#help-ramen i made it
but I dont like these external packages, coz you have to install them for runtime as well
we had a small discussion here yesterday on whether type hints are a waste of time π
type hints are good
but I need way more freedom to express my own types
its too limited for somereason, and clunky
Turns out that it's really hard to type a dynamic language like Python. Type checkers are already unbelievably complex and have lots of bugs
type hint in python focus too much on primitive types
and in practice you dont really need type hints for primitive types any way coz they are obvious
you need to start hinting when its more complex
And like the creator of data-science-types say, the aim should not be to be correct but be useful in the development process
If you have something to suggest, you can post a message on the typing-sig mailing list π
and then maybe suggest a PEP
no, coz they would assault me and call me stupid
I never like being too close to development process of language
still have my scar from Golang discussions
hm?
You can read already existing discussions on https://mail.python.org/archives/list/typing-sig@python.org/
python mailing lists do get heated sometimes, but it's very rare
especially in typing-sig
Ye, maybe I will start reading, so that I can actually suggest when I'm ready
do you think it would be ok to ask similar question but just rephrased there? And ask for feature/ways I could achieve that with native python?
sure
If you want an example of a successful gradual typing system, take a look at TypeScript btw π
but maybe they will get angry at me for not reading the manual enough
what do you mean by gradual typing system though?
'gradual typing' refers to partially adding static types to an otherwise dynamic language
If you read all the reasonable documentation you found and it's still not clear, it's a bug in the documentation π
meh sure maybe I will have a go at asking
when I have the time
I will probably ask you to proof read before posting
I don't know about any data science libraries for TypeScript, but it is certainly possible. You can have something like
const a = DataFrame({x: [1, 2], y: ["foo"]})
``` and have `a` inferred as `DataFrame<{x: number[], y: string[]}>`
I think I gave you a false sense of expertise π
youve ever posted on reading list?
no
!pep 681
coz seem to know a lot more about typing and TypeScript
maybe this can also provide some background to you
although it's still a draft
I will look into it
oh, so its still in development
ye this looks relevant thanks i learnt a lot
the kind of completion you wanted for DataFrames is possible in vscode in the jupyter console
but that's just runtime autocompletion, right?
but there it doesn't use typing information, it just directly reads the data
yeah the dataframe has to exist in memory in the jupyter kernel or whatever it is called
after that when you type in the jupyter interactive thing, it gives auto-completions for column names
from typing import Generic, TypeVar, List
import pandas as pd
T = TypeVar('T')
class MyColumn(Generic[T]):
def __init__(self, column_name: str) -> None:
super().__init__()
self.column_name = column_name
def __get__(self, instance, owner):
return instance.df[self.column_name]
def __set__(self, instance, value: T):
instance.df[self.column_name]=value
class MyDF():
def __init__(self) -> None:
self.df = pd.DataFrame()
class CustomDF(MyDF):
a:MyColumn[List[int]] = MyColumn('a')
b:MyColumn[List[float]] = MyColumn('b')
df = CustomDF()
df.a = [1,2,3]
df.b = [2.2, 2.3, 3.3]
print(df.a)
print(df.b)
Ok I was able to get this far with trying to type columns, I'm pretty impressed with my own creativity
but I still have one problem
a:MyColumn[List[int]] = MyColumn('a')
nice solution, I haven't thought of this
I have to specify a twice
is there like a way to get MyColumn to figure out that it is called a?
use __set_name__ https://docs.python.org/3/howto/descriptor.html#customized-names
ok I will look into it thanks
from typing import Generic, TypeVar, List
import pandas as pd
T = TypeVar('T')
class MyColumn(Generic[T]):
def __set_name__(self, owner, name):
self.column_name = name
def __get__(self, instance, owner):
return instance.df[self.column_name]
def __set__(self, instance, value: T):
instance.df[self.column_name]=value
class MyDF():
def __init__(self) -> None:
self.df = pd.DataFrame()
class CustomDF(MyDF):
a:MyColumn[List[int]] = MyColumn()
b:MyColumn[List[float]] = MyColumn()
df = CustomDF()
df.a = [1,2,3]
df.b = [2.2, 2.3, 3.3]
print(df.a)
print(df.b)
Oh my GOD its beautiful
class CustomDF(MyDF):
a:MyColumn[List[int]] = MyColumn()
b:MyColumn[List[float]] = MyColumn()
look at that so beautiful
if I wanted to use dataframe methods I could do:
df.df.drop('a')
but is there a cleaner way of doing it, so kind of like relaying other method calls to self.df?
not yet, I think there was some discussion on proxy types somewhere
I think adding an attribute access layer is fine
ok thanks
I was looking for ways of maybe inheriting directly from pandas dataframe?
class CustomDF(MyDF, pd.DataFrame):
but I dont think Im good enough for that but I think where we were able to take this is good enough to be very useful thanks
Not sure about that. Haven't worked with pandas
Does Typescript have lots of bugs? Or is this buggines a product of having typing not be a superset of Python like typescript is?
I haven't looked deeply, but
https://github.com/microsoft/TypeScript/issues?q=label%3ABug+
π
mypy has 2038 open, 5194 closed
I think it is about the same then if you account for the fact that typescript has a bigger team behind it
I bet a lot of the mypy bugs can be closed as duplicates, I might do a round of triage at some point
that's all the issues though, not just bugs π
from __future__ import annotations
from typing import Any, TypeVar, Generic
T = TypeVar('T')
def flatten(original_cls: type[int] | type[str]) -> '(type[Response[Any]]) -> type[Response[Any]]':
...
@flatten(int)
class Response(Generic[T]):
...
class FooResponse(Response[T]):
...
Pylance has periodically been giving me errors (x is already specialized) for my code, which was similar to what's above. I realized the errors are because the flatten decorator is returning a specialized version of the type it's decorating. Pyright doesn't flag this as an error and pylance doesn't always (for some reason) either, should they be?
the errors were either "type x is already specialized" or "expected no arguments for type x" (this is an example of one error)
I fixed the function signature and errors, I was just wondering whether this should be reported by pyright
although like i mentioned pylance doesn't report it consistently
Yeah, I don't know what the error even is x) My only guess as to what that error means doesnt fit the context of the code you provided so I dont think this is it
Hello typing people, I made the generics tutorial more complete.
https://decorator-factory.github.io/typing-tips/tutorials/generics/
I would like to hear corrections, additions and the most brutal feedback
assigned to a variable of type Sup
thank you
I should probably change to Child and Parent because I got confused as well π
yeah, agreed
not really incorrect, but why name the instance of Box pair instead of box?
that is indeed a bug
i feel like the function name and parameter names makes this harder to read
since you only need to look at the types
make_animal :: str -> Animal π₯΄
So our Box class should stay invariant.
yeah I agree, it's not pretty. Not sure how to express it better though.
how would you feel about using the (rejected π) callable syntax in your tutorial
I would like the idea to be as unambiguous as possible
the snowboarders are people who use types in their python code https://www.youtube.com/watch?v=XPZDEWBzneY
Feel the angst , feel the vibe. News segment on the Rivalry
try:
from some_dependency import BaseClass # type: ignore
except ImportError:
class BaseClass:
def __init__(self, *args, **kwargs) -> None:
raise ImportError("Missing dependency to use this O_o")
class Somesubclass(BaseClass):
...
Pyright keeps complaining about this (hold on let me upload it to a playground)
I get "Argument to class must be a base class"
Mypy gives another error though π€ https://mypy-play.net/?mypy=latest&python=3.10&gist=9315f1312926762453fc2f449c4623d1
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
Any tips for this?
does anyone know why apple:str = 2 is not shown as a error on vs code?
the mypy error you can just type ignore away
probably you don't have type checking on or don't have pylance/pyright installed
Right yeah, I am a bit more wary about the one I got from Pyright. Any ideas why it would cause that? Perhaps I should start a discussion.
I am using pylance
and Ive checked the setting as well
yeah that one sounds weird
type ignore works, i also sometimes use if not TYPE_CHECKING for this kind of thing
huh, pyright doesn't like my attempt to play with its recursion
GraphItem = Union[tuple[str, list["GraphItem"]], int]
def listint() -> list[int]:
...
a: GraphItem = ("tests", listint()) # very unhappy
Meanwhile mypy doesnt even support recursion x)
I guess I can just make it list[graphitem] instead of int
is that a variance thing?
maybe π€ I didn't put too much thought into it
b = [3, 4, 6]
a: GraphItem = ("test", b)
this ^^^ is not okay
a: GraphItem = ("test", [3, 4, 6])
but pyright says this is fine
that looks like bidirectional inference at work
in the first example it infers b as being of type list[int]
which isn't compatible with GraphItem
and the second GraphItem presumably, yeah
this interaction with mypy is annoying though
like,
master_list: GraphItem = (form_filt, [])
assert isinstance(master_list, tuple)
I would have to assert like this every time in mypy (because it takes the annotation as religion), but pyright wouldn't need the assert
oh well
I wish argparse wasn't so dynamic so it could be statically type checked
Hello, I'm working on a project to predict congestion at the airport.
We are trying to build a pipeline that connects data and machine learning.
But I don't know how or what tools to use to make it.
This is not related to the generics article, but I have feedback for https://decorator-factory.github.io/typing-tips/faq/sometimes-raises/ I think it would be worth putting more emphasis on using alternatives to None. Maybe linking to some other resources that explain the problems with using None. I'm not sure if the docs of returns already have an explanation for that.
eh, I'm not sure. I think the conceptual overhead of adding some monad stuff is too high to consider adding it to a normal project for normal developers
Perhaps. But I think it's easy enough to understand for use. Maybe not to understand the theory, but that isn't necessary.
In any case, it's worth mentioning None has problems and leave it up to the reader to do more research and make a choice or just ignore it
How to recognize element type in list?
i.e data = [42, "42", 42.0]
for element in data:
if element is type (int):
