-
Notifications
You must be signed in to change notification settings - Fork 782
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
#[pyclass]
trait ergonomics
#4207
Comments
@davidhewitt I would like to start exploring the str task. I think if we land eq first, I can rebase the work I did in ord to integrate it with what @Icxolu did for eq. I can migrate in the tests and doc updates and add the extra match arms pretty easily at that point. I don't think this should have any impact on the str work, but please correct me if I missed something. You did make a comment reagarding naming the methods. I take it we should adhere to this for all of these features? If there is anything else I should look out for let me know! Thanks! |
@davidhewitt You mentioned passing formatting arguments to str and repr. Is there a benefit to that vs having the user directly implement Display and Debug traits where they would have full formatting control? |
@Zyell sounds great, please do feel welcome to take on
Yes, I think we can handle that to make the duplication check for all of these functions. I think #4210 now has working logic to do that.
I think by default we should use the traits for convenience, but if the format string is passed use that instead. I think it's common to want the text formatting to be a bit different between Python and Rust. I think this is particularly true for #[pyclass(str)] // uses Display
struct Foo {}
#[pyclass(str = "hello {name}")] // uses custom formatting string
struct Person {
name: &str
} As for the variables permitted in these custom formatting strings, I think prior art would be in how something like thiserror handles them. |
@davidhewitt That makes sense. Thank you for the pointer to the thiserror library, that approach looks really nice. |
@davidhewitt I have finished the |
Thanks @Zyell, the Given what we're discussing in that review, I'm having a bit of self-doubt whether it's really useful at all for I'm now wondering whether if we had e.g. something like the below #[pyclass(repr)]
struct Point {
x: i32,
y: i32,
#[pyo3(repr = false)] // skip this field in the repr
metadata: Metadata
} might generate a repr While it would be a more complex implementation, I'd suspect this is what users probably want in most cases? I guess the problem is that we would still have to use Rust's ... all in all, it feels like there are design possibilities with
... or is this too complex and we should back out and leave users to write What I'm now wondering is, does this have implications for |
@davidhewitt I like the idea of being able to disable fields from the output! I use that all the time in dataclasses. I still like the idea of tying this to the Debug trait for reasons I mentioned in my last comment on the str implementation. However, the PyObject case or the vector of PyObjects might be problematic. I think it is reasonable for the user to expect it to use its I think the ideal design would be something like this:
I will explore the PyObject case you pointed and see what we might do about it before starting an implementation or repr so we have a better idea about what makes sense to do. Thanks for the insight! |
@davidhewitt I took a closer look at repr and it is looking rather complex to implement. I can navigate the AST just fine in the pyclass macro for the fields to implement a custom format. However, for nested objects, this quickly gets complex. There is no type information about the fields other than its named type (as far as I understand it this is a limitation from within this kind of macro). I could parse that looking for patterns like #[derive(PartialEq, Debug)]
struct Label {
a: u32
}
#[pyclass(repr)]
#[derive(PartialEq, Debug)]
struct Point4 {
name: String,
msg: String,
idn: u32,
label: Label,
} The Formatter on the Debug trait implementation gets around this by delegating the formatting to all fields, requiring their types to implement Debug all the way down the chain. I had hoped that the Formatter might take arguments such that we could override some of the separators like changing Assuming we could get the necessary type information to traverse these fields, we could delegate most of the formatting to the Debug implementation for collections and primitives. For Without that information though, I'm not sure we could generically and automatically implement a repr that matches the Python dataclass implementation. I'm happy to explore this further if you have some additional insights and ideas! |
Looks like this is the issue for coming across this now, handling this with a code generator, and want to +1 for |
Following up to #4202 and #4206 I wanted to write down something I've been thinking about for a while. It looks like we're moving ahead with accepting those options so I'll assume that at least the design choice to have these options is accepted.
In summary, I think there are at least five convenience options to add to
#[pyclass]
to automatically expose Python magic methods based on Rust trait implementations:#[pyclass(eq)]
for equality based offPartialEq
- add pyclasseq
option #4210#[pyclass(ord)]
for ordering operators based offPartialOrd
- feat: Add 'ord' option for PyClass and corresponding tests #4202#[pyclass(hash)]
for hash based offHash
- add pyclasshash
option #4206#[pyclass(str)]
for__str__
based offDisplay
. I think we could also have#[pyclass(str = "<format>")]
to specify a format-args-compatible string concisely.#[pyclass(repr)]
for__repr__
based offDebug
. Similarly to the above I think we could have#[pyclass(repr = "<format>")]
.Based off the recent PRs I think the basic implementation of these is more or less agreed upon, possibly excluding the extra argument on the string formatting options.
As we proceed with / make progress on the above, I think there are two further steps which make sense to explore:
eq
andrepr
automatically. I think we should be deprecating the automatic generation and start requiring opt-in to the new behaviour, to be consistent with everything else. I think it's relatively easy to do this as we just need to emit the deprecation warning if theeq
orrepr
arguments weren't passed.#[pyclass(init)]
to automatically generate a constructor (like we already do for complex enums) and then#[pyclass(dataclass)]
which does some or all of the above conveniences (maybe depending on whether the class isfrozen
).The text was updated successfully, but these errors were encountered: