Creating new conversion specifier in Python - python

In python we have conversion specifier like
'{0!s}'.format(10)
which prints
'10'
How can I make my own conversion specifiers like
'{0!d}'.format(4561321)
which print integers in following format
4,561,321
Or converts it into binary like
'{0!b}'.format(2)
which prints
10
What are the classes I need to inherit and which functions I need to modify? If possible please provide a small example.
Thanks!!

What you want to do is impossible, because built-in types cannot be modified and literals always refer to built-in types.
There is a special method to handle the formatting of values, that is __format__, however it only handles the format string, not the conversion specifier, i.e. you can customize how {0:d} is handled but not how {0!d} is. The only things that work with ! are s and r.
Note that d and b already exist as format specifiers:
>>> '{0:b}'.format(2)
'10'
In any case you could implement your own class that handles formatting:
class MyInt:
def __init__(self, value):
self.value = value
def __format__(self, fmt):
if fmt == 'd':
text = list(str(self.value))
elif fmt == 'b':
text = list(bin(self.value)[2:])
for i in range(len(text)-3, 0, -3):
text.insert(i, ',')
return ''.join(text)
Used as:
>>> '{0:d}'.format(MyInt(5000000))
5,000,000
>>> '{0:b}'.format(MyInt(8))
1,000

Try not to make your own and try to use default functions already present in python. You can use,
'{0:b}'.format(2) # for binary
'{0:d}'.format(2) # for integer
'{0:x}'.format(2) # for hexadecimal
'{0:f}'.format(2) # for float
'{0:e}'.format(2) # for exponential
Please refer https://docs.python.org/2/library/string.html#formatspec for more.

Related

Print all numbers as hex format in Python command line [duplicate]

I'm doing a bunch of work in the Python console, and most of it is referring to addresses, which I'd prefer to see in hex.
So if a = 0xBADF00D, when I simply enter Python> a into the console to view its value, I'd prefer python to reply with 0xBADF00D instead of 195948557.
I know I can enter '0x%X' % a to see it in hex, but I'm looking for some sort of python console option to have it do this automatically. Does something liket this exist? Thanks!
The regular Python interpreter will call sys.displayhook to do the actual displaying of expressions you enter. You can replace it with something that displays exactly what you want, but you have to keep in mind that it is called for all expressions the interactive interpreter wants to display:
>>> import sys
>>> 1
1
>>> "1"
'1'
>>> def display_as_hex(item):
... if isinstance(item, (int, long)):
... print hex(item)
... else:
... print repr(item)
...
>>> sys.displayhook = display_as_hex
>>> 1
0x1
>>> "1"
'1'
I suspect you'll quickly get tired of seeing all integers as hex, though, and switch to explicitly converting the ones you want to see as hex accordingly.
Building on previous answers, here's a version that works for Python 2/3, doesn't display bools as hex, and also properly sets the _ variable:
import sys
def _displayhook(o):
if type(o).__name__ in ('int', 'long'):
print(hex(o))
__builtins__._ = o
else:
sys.__displayhook__(o)
def hexon():
sys.displayhook = _displayhook
def hexoff():
sys.displayhook=sys.__displayhook__
Something like this, perhaps?
class HexInt(int):
"Same as int, but __repr__() uses hex"
def __repr__(self):
return hex(self)
So you'd use that when creating all your integers that you want to be shown as hex values.
Example:
>>> a = HexInt(12345)
>>> b = HexInt(54321)
>>> a
0x3039
>>> b
0xd431
>>> c = HexInt(a + b)
>>> c
0x1046a
Note that if you wanted to skip the explicit creation of a new HexInt when doing arithmetic operations, you'd have to override the existing int versions of methods such as __add__(), __sub__(), etc., such that they'd return HexInts.
Modifying the top python2 answer for python3...
def display_as_hex(item):
if isinstance(item, int):
print(hex(item))
else:
print(repr(item))
import sys
sys.displayhook = display_as_hex
You could so something like this:
while True:
print hex(input('> '))
To get a basic prompt that prints the hex value of all of the results. You could even make it conditional -- check to see if the return type of input is a string or number, and if it is, print the hex value, else print the value normally.

Roundtrip leading 0s of hexadecimal numbers

I'm willing to load a yaml file containing 32-bit hexadecimal numbers, and keep the leading 0s so that the number is always in the form 0xXXXXXXXX.
I have created a custom class and representer so that dumping hexadecimal numbers in this form is possible:
class HexWInt(int):
pass
def represent_HexWInt(self, data):
# type: (Any) -> Any
return self.represent_scalar(u'tag:yaml.org,2002:int', '0x' + format(data, 'x').upper().zfill(8))
yaml.RoundTripRepresenter.add_representer(HexWInt, represent_HexWInt)
However, I cannot find a proper way to apply this format to roundtripped hexadecimal numbers.
Indeed, the following:
yamltext = "hexa: 0x0123ABCD"
code = yaml.round_trip_load(yamltext)
yaml.dump(code, sys.stdout, Dumper=yaml.RoundTripDumper)
Displays
hexa: 0x123ABCD
Where I would like this to be displayed
hexa: 0x0123ABCD
How can I proceed to force hexadecimal numbers to fit the 0xXXXXXXXX format?
There are multiple ways to do what you want. If you don't want to influence the normal behaviour for the parser, you should subclass the RoundTripLoader and RoundTripConstructor with alternative RoundTripConstructor and RoundTripRepresenter. But that requires registering all constructors and representers, and is quite verbose.
If you don't care about being to be able to load other YAML documents with hex scalar integers that have leading zeros with the original functionality later on in your program, you can just add a new constructor and representer to the RoundTripConstructor and RoundTripRepresenter.
The easiest part is to get your format, based on a value and a width. You don't need zfill() nor upper() for that if you are using format anyway:
'0x{:0{}X}'.format(value, width)
does the job.
The main reason that your code doesn't work is because your code never constructs a HexWInt, as the RoundTripLoader doesn't know that it should do so. I would also not hard code the width to eight, but derive it from the input (using len()), and preserve that.
import sys
import ruamel.yaml
class HexWInt(ruamel.yaml.scalarint.ScalarInt):
def __new__(cls, value, width):
x = ruamel.yaml.scalarint.ScalarInt.__new__(cls, value)
x._width = width # keep the original width
return x
def __isub__(self, a):
return HexWInt(self - a, self._width)
def alt_construct_yaml_int(constructor, node):
# check for 0x0 starting hex integers
value_s = ruamel.yaml.compat.to_str(constructor.construct_scalar(node))
if not value_s.startswith('0x0'):
return constructor.construct_yaml_int(node)
return HexWInt(int(value_s[2:], 16), len(value_s[2:]))
ruamel.yaml.constructor.RoundTripConstructor.add_constructor(
u'tag:yaml.org,2002:int', alt_construct_yaml_int)
def represent_hexw_int(representer, data):
return representer.represent_scalar(u'tag:yaml.org,2002:int',
'0x{:0{}X}'.format(data, data._width))
ruamel.yaml.representer.RoundTripRepresenter.add_representer(HexWInt, represent_hexw_int)
yaml_text = """\
hexa: 0x0123ABCD
hexb: 0x02AD
"""
yaml = ruamel.yaml.YAML()
data = yaml.load(yaml_text)
data['hexc'] = HexWInt(0xa1, 8)
data['hexb'] -= 3
yaml.dump(data, sys.stdout)
HexWInt stores both value and width. alt_construct_yaml_int passes everything to the original construct_yaml_int except for the case where the scalar starts with 0x0. It is registered with add_constructor() based on the normal regex based matching done by the Resolver. The representer combines the value and width back into a string. The output of the above is:
hexa: 0x0123ABCD
hexb: 0x02AD
hexc: 0x000000A1
Please note that you cannot do something like:
data['hexb'] -= 3
as ScalarInt (which does have the method __isub__) doesn't know about the width attribute. For the above to work, you'll have to implement the appropriate methods, like ScalarInt does, as methods on HexWInt. E.g.:
def __isub__(self, a):
return HexWInt(self - a, self._width)
An enhanced version of the above (which also preserves _ in integers and supports octal and binary integers) is incorporated in ruamel.yaml>=0.14.7

Python - convert to string with formatter as parameter

I am trying to write a function which takes two arguments:
an object
a formatter string (as specified in docs)
which then returns a formatted string:
What I tried is sort of:
def my_formatter(x, form):
return str(x).format(form)
What I am expecting is:
s = my_formatter(5, "%2f")
# s = 05
t = my_formatter(5, "%.2")
# t = 5.00
etc...
The format function unfortunately does not work like that. Any ideas?
For that style of formatting you'd have to use the string % values string formatting operation:
def my_formatter(x, form):
return form % x
You'd also have to alter your format; to get 05 you'd have to use "%02d", not "%2f".
You were getting confused by the str.format() method, which uses a different formatting syntax, and you got the arguments swapped; you'd use form.format(x) instead.
You probably want to look into the built-in format() function here; the syntax is slightly different, but offers more features:
>>> format(5, '02d')
'05'
>>> format(5, '.2f')
'5.00'
That's pretty close to what you were already using, minus the %.

Python change repr floating digits

Okay, I want to use repr() to print out a text version of a bunch of lists and nested arrays.
But I want the numbers to have only 4 decimal places not: 42.7635745114 but 32.7635.
I'd like to use repr() because of its nice ability to handle nested arrays. Writing my own print loop is an unattractive option.
Surely there is some way to overload repr to do this? I see there is a repr and reprlib modules but examples are really scarce, like nonexistent.
No, there is no way to overload repr(). The format for floats is hardcoded in the C source code.
The float_repr() function calls a helper function with the 'r' formatter, which eventually calls a utility function that hardcodes the format to what comes down to format(float, '.16g').
You could subclass float, but to only do that for representing values (especially in a larger structure) is overkill. This is where repr (reprlib in Python 3) comes in; that library is designed to print useful representations of arbitrary data structures, and letting you hook into printing specific types in that structure.
You could use the repr module by subclassing repr.Repr(), providing a repr_float() method to handle floats:
try: # Python 3
import reprlib
except ImportError: # Python 2
import repr as reprlib
class FloatRepr(reprlib.Repr):
def repr_float(self, value, level):
return format(value, '.4f')
print(FloatRepr().repr(object_to_represent))
Demo:
>>> import random
>>> import reprlib
>>> class FloatRepr(reprlib.Repr):
... def repr_float(self, value, level):
... return format(value, '.4f')
...
>>> print(FloatRepr().repr([random.random() for _ in range(5)]))
[0.5613, 0.9042, 0.3891, 0.7396, 0.0140]
You may want to set the max* attributes on your subclass to influence how many values are printed per container type.
Maybe you could try string formatting using return "%.4f" %(self.float):
>>> class obj:
... def __init__(self, value):
... self.float = value
... def __repr__(self):
... return "%.4f" %(self.float)
...
>>> x = obj(8.1231231253252)
>>> x.float
8.1231231253252
>>> x
8.1231
>>>

Python: Capitalize a word using string.format()

Is it possible to capitalize a word using string formatting? For example,
"{user} did such and such.".format(user="foobar")
should return "Foobar did such and such."
Note that I'm well aware of .capitalize(); however, here's a (very simplified version of) code I'm using:
printme = random.choice(["On {date}, {user} did la-dee-dah. ",
"{user} did la-dee-dah on {date}. "
])
output = printme.format(user=x,date=y)
As you can see, just defining user as x.capitalize() in the .format() doesn't work, since then it would also be applied (incorrectly) to the first scenario. And since I can't predict fate, there's no way of knowing which random.choice would be selected in advance. What can I do?
Addt'l note: Just doing output = random.choice(['xyz'.format(),'lmn'.format()]) (in other words, formatting each string individually, and then using .capitalize() for the ones that need it) isn't a viable option, since printme is actually choosing from ~40+ strings.
As said #IgnacioVazquez-Abrams, create a subclass of string.Formatter allow you to extend/change the format string processing.
In your case, you have to overload the method convert_field
from string import Formatter
class ExtendedFormatter(Formatter):
"""An extended format string formatter
Formatter with extended conversion symbol
"""
def convert_field(self, value, conversion):
""" Extend conversion symbol
Following additional symbol has been added
* l: convert to string and low case
* u: convert to string and up case
default are:
* s: convert with str()
* r: convert with repr()
* a: convert with ascii()
"""
if conversion == "u":
return str(value).upper()
elif conversion == "l":
return str(value).lower()
# Do the default conversion or raise error if no matching conversion found
return super(ExtendedFormatter, self).convert_field(value, conversion)
# Test this code
myformatter = ExtendedFormatter()
template_str = "normal:{test}, upcase:{test!u}, lowcase:{test!l}"
output = myformatter.format(template_str, test="DiDaDoDu")
print(output)
You can pass extra values and just not use them, like this lightweight option
printme = random.choice(["On {date}, {user} did la-dee-dah. ",
"{User} did la-dee-dah on {date}. "
])
output = printme.format(user=x, date=y, User=x.capitalize())
The best choice probably depends whether you are doing this enough to need your own fullblown Formatter.
You can create your own subclass of string.Formatter which will allow you to recognize a custom conversion that you can use to recase your strings.
myformatter.format('{user!u} did la-dee-dah on {date}, and {pronoun!l} liked it. ',
user=x, date=y, pronoun=z)
In python 3.6+ you can use fstrings now. https://realpython.com/python-f-strings/
>>> txt = 'aBcD'
>>> f'{txt.upper()}'
'ABCD'

Categories

Resources