-
-
Notifications
You must be signed in to change notification settings - Fork 44
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
bug: Descriptors are not handled properly #328
Comments
(I'll move this to Griffe) Thanks for the report!
Hmmm. I don't think I want to enter this territory. Griffe has some capabilities to follow annotations, but inferring them is currently a non-goal. Even looking at the code, it took me a few seconds to know what the type would be ( If
Griffe indeed focuses on the usage perspective, though it's true that sometimes it can provide info that is not relevant to usage (like, a To support your |
I don't think it would be unrealistic to get the correct type from the You can access descriptors both via the class and via the instance, but you get different results. In the former case you get the descriptor itself, but when you access it from the instance you get the value returned but >>> class Example:
... d = PositiveInteger()
... def __init__(self):
... self.d = 1
... @property
... def p(self):
... return 2
... def m(self):
... return 3
...
>>> e = Example()
>>> e.d
1
>>> e.p
2
>>> e.m
<bound method Example.m of <__main__.Example object at 0x7f2780d8a3c0>>
>>> Example.d
<PositiveInteger object at 0x7f2780e29bd0>
>>> Example.p
<property object at 0x7f2780c81a80>
>>> Example.m
<function Example.m at 0x7f2780c7d8a0> If you want to learn more about descriptors, I recommend reading the excellent Descriptor Guide. Descriptors aren't used that widely in normal code, but Python itself uses them extensively. I'm sure their usage gets more widespread in the future, though. For example, I read somewhere that SQLAlchemy has started to used from in its models. I would expect that our |
Few more examples using the >>> Example.d.__get__(e, type(e)) # Standard descriptor call requires `instance` and `type`.
1
>>> Example.d.__get__(None, type(e)) # `None` as `instance` is same as accessing from class.
<descriptor.PositiveInteger object at 0x7f2780e29bd0>
>>> Example.p.__get__(e) # With property `type` is optional.
2
>>> Example.m.__get__(e)() # Somewhat strange way to call a method...
3
>>> from typing import get_type_hints
>>> get_type_hints(Example.d.__get__)
{'return': typing.Union[int, typing.Self]} As the end of the example demonstrates, getting the type from
|
You seem to assume dynamic analysis here, with access to runtime objects. But by default, Griffe uses static analysis. I'm sure you understand it's another story 😄 Griffe also supports dynamic analysis though, but I'm not sure we explicitly support data descriptors (we do support method descriptors). |
It actually occurred to be, after writing the previous comments, that this isn't as straightforward with static analysis alone. I assume methods (that are non-data descriptors) work also in static analysis, so perhaps the same approach could also be extended to data descriptors. If that's too complicated, proper data descriptor support requiring dynamic analysis ought to be fine. |
I believe we can (and should) improve support for data-descriptors through both static and dynamic analysis. Data descriptors are a feature of the language, so even if it's hard, Griffe must support them. Dynamic analysis should be able to handle them directly while visiting object trees, while static analysis will probably require a built-in extension. Anyway, these are just internal details 🙂 What I note is:
After this, to support your |
Thanks for all the explanations, and for your patience 🙂 I've read the guide on data descriptors you shared, and I think I understand their main benefit: contrary to properties, they are easily reusable, and require much less boilerplate once implemented. Properties would have to shared via inheritance (mixins) instead of composition, and even then would be less flexible. Makes sense since properties are a specific use of descriptors. |
Yeah, descriptors are really handy. They look complicated first, but users typically don't need to even know them. I think they are underused but believe that will change. Your plans sound really good. The If you implement all that, I'd expect our |
If I use a descriptor as at the end of this description, there are two problems with how the generated documentation for
Example.count
looks like:__get__
method.class-atribute
andinstance-atribute
which are both somewhat wrong. Technically it is a class attribute, but so are methods andproperty
s (that are also implemented as descriptors) and they don't get such a label either. Technically it's not an instance attribute, but from the usage perspective it acts like one. I believe a proper label would bedata-descriptor
, butproperty
would probably be more familiar for normal users and the functionality is the same.You can see how the example is rendered here in a demo site that I've used for MkDocs experiments.
A variation of this is using a descriptor as a method decorator like the setter we use extensively with the Robot Framework project to avoid using normal propertys with dummy getters. Such attributes are currently shown as methods without any type information. You can see an example here in a site where we are creating our new Manual that will also incorporate API docs.
I'm not sure should this have been submitted to griffe instead or should I have submitted separate issues about different problems. Feel free to move or split as needed.
Boost priority
The text was updated successfully, but these errors were encountered: