from dataclasses import dataclass
@dataclass(frozen=True, order=True)
class AppVersion:
major: int = 0
minor: int = 0
patch: int = 0
@classmethod
def from_string(cls, version_string: str):
return cls(*[int(x) for x in version_string.split(".")])
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"
Before dataclasses you could've used namedtuples, at a loss of attribute typing and default initializer:
from collections import namedtuple
class AppVersion(namedtuple("AppVersion", "major minor patch")):
@classmethod
def from_string(cls, version_string: str):
parts = [int(x) for x in version_string.split(".")] + [0, 0]
return cls(*parts[:3])
def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"
Nice solution with dataclass! And for a complete comparison with the blog you can also use a library to do it for you. It's not quite in the official python distribution but it's maintained by pypa as a dependency of pip so you probably have it installed already.
>>> from packaging.version import Version
>>> Version("1.2.3") > Version("1.2.2")
True
>>> Version("2.0") > Version("1.2.2")
True
packaging.version has a somewhat weird (or at least Python-specific) set of rules that don't match the semantics of Ruby's Gem:Version, which will accept basically anything as input.
I'd use `semver` from PyPI and whatever the equivalent Gem is on the Ruby side in most cases.
I write mostly Python these days, but agree with op. The comparables implementation in Ruby seems much nicer to me (maybe because I'm less familiar with it).
Then use the `total_ordering` decorator to provide the remaining rich comparison methods.
That said, it's a little annoying Python didn't keep __cmp__ around since there's no direct replacement that's just as succinct and what I did above is a slight fib: you still may need to add __eq__() as well.
> Then use the `total_ordering` decorator to provide the remaining rich comparison methods.
While we're here, worth highlighting `cmp_to_key` as well for `sorted` etc. calls.
> it's a little annoying Python didn't keep __cmp__ around since there's no direct replacement that's just as succinct
The rationale offered at the time (https://docs.python.org/3/whatsnew/3.0.html) was admittedly weak, but at least this way there isn't confusion over what happens if you try to use both ways (because one of them just isn't a way any more).
However, I think comparing the Ruby example implementation with the "data classes example" is a category error.
The Ruby example should be compared to the implementation of data classes. The Ruby code shows how cleanly the code for parsing, comparing and printing a version string can be. We would need to see the code underlying the data classes implementation to make a meaningful comparison.
It's a little magicky. I guess the "Order=True" is what ensures the order of the parameters in the auto-generated constructor matches the order in which the instance variables are defined?
order: If true (the default is False), __lt__(), __le__(), __gt__(), and __ge__() methods will be generated. These compare the class as if it were a tuple of its fields, in order.
eq: If true (the default), an __eq__() method will be generated. This method compares the class as if it were a tuple of its fields, in order. Both instances in the comparison must be of the identical type.