-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Descriptors #244
Comments
Since properties are now largely functional (see #220), this can be worked around by writing the descriptor as a property in a stub file. This does, however, require a fair amount of coding since you need to rewrite the same property declaration everywhere the descriptor is instantiated (not to mention that a property declaration is considerably longer than a typical descriptor instantiation). The whole point of making your own descriptors is to avoid having to do this, so this is actually a pretty serious loss in terms of duplicate code. As a stopgap, it would be nice if there was a way to make type aliases of properties, or if we had some kind of |
Jukka may understand what this is about -- but for my and others' benefit, can you post some examples of what the work-around looks like and what you think should be supported instead? |
@gvanrossum Suppose you have a class like this (a descriptor class):
In most cases, GetType and SetType are identical. You might also have a Then you probably (read: in my use case) have a bunch of declarations like this:
In the stub file, you can write this:
But that's a lot of code, and it defeats the purpose of making Foo (the class) in the first place: You now have to write out a property declaration everywhere you use Foo (or else live with uncheckable code). I don't want to write that every time; it's why I wrote As a stop-gap measure, it would be nice if we could just write this:
This is still a lie, but at least it's not a lie that takes six lines to express. *: |
OK, I think I see it now. Is there any way you could write this Property thing without changing mypy? (Then we could talk about adding it to typing.py or something like that.) BTW I think it should not be called Property -- maybe Descriptor? |
@gvanrossum: I was thinking of this as a stopgap (precisely equivalent to the Having said that, if we're doing this, we need to get the semantics right. I would interpret it something like this:
The ??? indicates that I'm not sure what to write for that case. The convention is to return The difficulty here is that all three methods are independent of each other, meaning we arguably have three separate abstract classes. OTOH, it is uncommon to implement This is a rather opinionated idea, so feel free to tell me I'm wrong about everything. * The If we just want to write a
The return type is precisely the type we are trying to write in the first place, so this is a non-starter. We could subclass @JukkaL Any other ideas on how to effectively convince mypy that something is a property without writing out a full property? |
The If you just have a read-write descriptor that is always to be accessed via an instance, you can kind of fake it my declaring it as a regular attribute:
Also, it's not obvious how mypy should support statically typed descriptors outside stubs. The |
This may be a good argument in favor of the |
@NYKevin, do you need help coming up with the actual definition of the generic class that we can add to typing.py and to typing.pyi (in typeshed, for mypy)? You should be able to prototype this without any changes to typeshed or mypy, once we've seen the work we can discuss how to add it. (I'm trying not to work on solving this problem directly myself, I need to focus on fewer things.) |
@gvanrossum I'd appreciate it if someone could give my code a quick once-over, but I'm pretty sure it's mostly correct. See below. But first, I want to be very clear about one thing: this code is not a workaround for this bug (mypy not understanding descriptors). So far as I can tell, this bug cannot be worked around any better than the clumsy
Some notes about the code:
And some things that Jukka might actually care about (or may have already figured out by himself a long time ago):
|
Sorry, my brain is not set up any more for understanding this kind of stuff, and I got a headache trying to understand this. :-( (This worries me, since I invented the descriptor concept -- many years ago.) I would have to whiteboard it out with someone who also knows how descriptors work and knows enough about mypy. All I can tell right now is that (1) |
Here's a tutorial example that may help my future self understand this. This is much more concrete -- I'm assuming that we're implementing a database and descriptors are used to declare fields in records. I'm also combining your GetType and SetType into a single FieldType variable (I understand why you're doing that, but it makes the example more complex without adding extra light -- just like you already omitted
Anyway, here goes:
This currently doesn't pass mypy because python/typing#175 hasn't been implemented yet (heck, I cowardly haven't posted the PEP update to the peps repo yet :-). And here's the part that shows that mypy doesn't support descriptors yet:
The error we get here shows that it doesn't make the leap from |
PS. I think the overloading of
or perhaps
As you remarked, the convention is that if obj is None So the key thing here is that if a class attribute a is The distinction between the get-type and the set-type, like so many other things, doesn't really change much fundamentally. (Do you have an actual use case where G and S differ? I'd like to hear about it.) |
No objection there. At least 90% of typical use cases will have the same types there, so it makes sense to combine them at the implementation level. Nevertheless, if we really are putting this in
Personally, I used inheritance + template method pattern to create different types of Fields, which is particularly handy when your different types have heterogeneous implementations, but this is a minor quibble. Factory functions work just as well for something like this.
You don't actually have to do that; a metaclass can do it for you. It's very hairy code and I wouldn't recommend it for a simple descriptor example. In particular, I had to override the metaclass
This looks wrong to me. I don't think
Again, no objection, but in the long run it might be nice to consider this more carefully. Proxy objects may want to return the thing they are proxying (You have two descriptors, one of which is a proxy for the other, and the first has its
No, I don't. I mostly put it in for completeness. |
I think mypy itself should just look for
To mypy it should be all the same -- it just needs to know the ultimate type of the class variable so it can check whether it has a
You don't have to tell me that. :-) I just left it out because it would be a distraction in this example (a bigger distraction than passing the name redundantly).
True, it's another detail I tried to sweep under the rug as irrelevant to understanding the example.
You still haven't said a thing about your use case. You have no idea how frustrating that is. I can't participate in a discussion about an API design if it's all about hypotheticals and abstractions. The abstractions have to follow from use cases we care about.
Point in case. |
My apologies. The wrapped descriptor is a read-write data descriptor. The system of which it is a part generally assumes that it is OK to call |
Why does the proxy need to to use descriptors? Couldn't it just overload
`__getattr[ibute]__`? (I suppose the system you're talking isn't open
source? Because if it is, you could just point me to it. :-))
|
It is open source. I've been linking to it in my comments above, but I suppose I wasn't very obvious about it. Here are the main components:
The read-write fields are significantly older than the read-only wrapper. The latter was essentially bolted on when I needed
I would like to note that composition is often preferred to inheritance, and I perhaps could have taken that advice when I was initially designing this system. If I had gone down that road, I believe there would be a lot more of this kind of method call forwarding between descriptor classes, but I have no evidence of this and could be entirely mistaken. If anyone knows of a good example of composition-over-inheritance descriptor design, it would be very interesting to look at. |
OK, now I understand what your code is doing -- though I wouldn't
recommend this architecture to anyone doing a similar thing from
scratch, and it sounds like you agree. :-)
Now let's roll back the discussion stack. We're back to the design of
the Descriptor and DataDescriptor classes that you posted way earlier.
But those aren't useful until mypy has support for descriptors built
in, and I don't like to put the cart before the horse. Once that's in
mypy, it'll be easier to experiment with different approaches before
we decide to add something to typing.py. Things to vary at that point
might be whether T is necessary, whether the distinction between G and
S is really useful, and how exactly to overload `__get__`.
|
Meh... Perhaps starting over from scratch would yield a better design, But this is irrelevant bikeshedding (though I do have to wonder whether mypy has any chance whatsoever of understanding protobuf message classes without stubs that would get outdated w.r.t. the .proto file... but that's a different topic).
SGTM. EDIT: At the risk of reopening a rather lengthy and (IMHO) not entirely fruitful discussion, I'd like to provide another example of an object that overrides |
Looks like this got fixed by #2266 but we forgot to close this issue. |
Now that mypy has support for these, would it makes sense to add these as (the classes being mentioned are from #244 (comment)) |
Support descriptors in the type checker.
The text was updated successfully, but these errors were encountered: