Thanks Chris. I had misunderstood Steve's request, and I was thinking of something much more complicated.
Your code is very helpful. -Ken On Wed, Aug 31, 2016 at 05:07:11PM +1000, Chris Angelico wrote: > On Wed, Aug 31, 2016 at 2:08 PM, Ken Kundert > <python-id...@shalmirane.com> wrote: > > > What's the mnemonic here? Why "r" for scale factor? > > > > My thinking was that r stands for real like f stands for float. > > With the base 2 scale factors, b stands for binary. > > "Real" has historically often been a synonym for "float", and it > doesn't really say that it'll be shown in engineering notation. But > then, we currently have format codes 'e', 'f', and 'g', and I don't > think there's much logic there beyond "exponential", "floating-point", > and... "general format"? I think that's a back-formation, frankly, and > 'g' was used simply because it comes nicely after 'e' and 'f'. (C's > decision, not Python's, fwiw.) I'll stick with 'r' for now, but it > could just as easily become 'h' to avoid confusion with %r for repr. > > >> (2) Support for full prefix names, so we can format (say) "kilograms" as > >> well > >> as "kg"? > > > > This assumes that somehow this code can access the units so that it can > > switch > > between long form 'grams' and short form 'g'. That is a huge expansion in > > the > > complexity for what seems like a small benefit. > > > > AIUI, it's just giving the full word. > > class ScaledNumber(float): > invert = {"μ": 1e6, "m": 1e3, "": 1, "k": 1e-3, "M": 1e-6} > words = {"μ": "micro", "m": "milli", "": "", "k": "kilo", "M": "mega"} > aliases = {"u": "μ"} > def autoscale(self): > if self < 1e-6: return None > if self < 1e-3: return "μ" > if self < 1: return "m" > if self < 1e3: return "" > if self < 1e6: return "k" > if self < 1e9: return "M" > return None > def __format__(self, fmt): > if fmt == "r" or fmt == "R": > scale = self.autoscale() > fmt = fmt + scale if scale else "f" > if fmt.startswith("r"): > scale = self.aliases.get(fmt[1], fmt[1]) > return "%g%s" % (self * self.invert[scale], scale) > if fmt.startswith("R"): > scale = self.aliases.get(fmt[1], fmt[1]) > return "%g %s" % (self * self.invert[scale], self.words[scale]) > return super().__format__(self, fmt) > > >>> range = ScaledNumber(50e3) > >>> print('Attenuation = {:.1f} dB at {:r}m.'.format(-13.7, range)) > Attenuation = -13.7 dB at 50km. > >>> print('Attenuation = {:.1f} dB at {:R}meters.'.format(-13.7, range)) > Attenuation = -13.7 dB at 50 kilometers. > >>> print('Attenuation = {:.1f} dB at {:rM}m.'.format(-13.7, range)) > Attenuation = -13.7 dB at 0.05Mm. > >>> print('Attenuation = {:.1f} dB at {:RM}meters.'.format(-13.7, range)) > Attenuation = -13.7 dB at 0.05 megameters. > > It's a minor flexibility, but could be very useful. As you see, it's > still not at all unit-aware; but grammatically, these formats only > make sense if followed by an actual unit name. (And not an SI base > unit, necessarily - you have to use "gram", not "kilogram", lest you > get silly constructs like "microkilogram" for milligram.) > > Note that this *already works*. You do have to use an explicit class > for your scaled numbers, since Python doesn't want you monkey-patching > the built-in float type, but if you were to request that > float.__format__ grow support for this, it'd be a relatively > non-intrusive change. This class could live on PyPI until one day > becoming subsumed into core, or just be a permanent third-party float > formatting feature. > > ChrisA > _______________________________________________ > Python-ideas mailing list > Python-ideas@python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/