For example, if I have a lot of type Decimal elements:
from decimal import Decimal
D = Decimal
# Generating a very log list of Decimal numbers
very_long_generated_list = [D('1.0'), D('2.9'), D('3.8')]
print(very_long_generated_list)
Will produce:
[Decimal('1.0'), Decimal('2.9'), Decimal('3.8')]
Visually parsing output with all those “Decimal” strings is quite tedious, especially if there are mathematical operations, nested structures etc. involved.
Wanted outcome is visually less taxing format for the Decimal strings:
[D('1.0'), D('2.9'), D('3.8')]
There is not always a possibility to do postprocessing of output. That is the reason I’m looking for a way to adjust the output earlier.
I noticed that decimal.Decimal is an immutable type, so it may not be possible.
def changeClassTypeName(theclass, thename):
theclass.__class__ = type(thename, (type,), {})
changeClassTypeName(Decimal, 'D')
will produce
TypeError: cannot set '__class__' attribute of immutable type 'decimal.Decimal'
It’s possible, but it envolves some Python dark magic. I don’t know if there is a better way.
Since the Decimal
class is a immutable type – as you discovered by yourself – we can use a metaclass to achieve this:
(If you don’t understand what a metaclass is, just copy and paste this code into your program):
class Wrapper(type):
def __init__(cls, name, bases, dct):
# Helper decorator to set `f` a method of `D`
def asmethod(f):
setattr(cls, f.__name__, f)
return f
# Function to be called whenever a method is called on `D`
def _wrapped(method_name):
def _callable(self, *args, **kwargs):
# If any argument is a `D`, unwrap its `Decimal`
args = [arg.__obj if isinstance(arg, cls) else arg for arg in args]
kwargs = {k: arg.__obj if isinstance(arg, cls) else arg for k, arg in kwargs.items()}
# Execute the real `Decimal` operation
real_attribute = getattr(self.__obj, method_name)
result = real_attribute(*args, **kwargs)
# If the result of the operation is a `Decimal`, wrap it into a `D`
return cls(result) if isinstance(result, cls.__wraps__) else result
return _callable
# Whenever the user creates a `D`, set also a
# `__obj` attribute containing the wrapped `Decimal`
@asmethod
def __init__(self, obj):
self.__obj = cls.__wraps__(obj)
# Whever the user accesses an attribute/method of `D`,
# fetch its equivalent attribute/method of `__obj`
@asmethod
def __getattribute__(self, name):
if name == '_Wrapper__obj':
return object.__getattribute__(self, name)
real_attribute = getattr(self.__obj, name)
if callable(real_attribute):
function = _wrapped(name)
return lambda *args, **kwargs: function(self, *args, **kwargs)
return real_attribute
# The logic you want: whevener the user accesses `D` as a
# `repr`/`str`, fetch the original `repr`/`str` from
# `__obj__`, but replacing occurences of 'Decimal' with 'D'
@asmethod
def __repr__(self):
return repr(self.__obj).replace(cls.__wraps__.__name__, cls.__name__)
@asmethod
def __str__(self):
return str(self.__obj).replace(cls.__wraps__.__name__, cls.__name__)
# Whenever the user accesses an dunder method of `D`,
# fetch its equivalent dunder method of `__obj`
# (such as `__add__` and `__div__`)
ignore = set("__%s__" % n for n in "class mro new init setattr getattr getattribute repr str".split())
for name in dir(cls.__wraps__):
# Filter only dunder methods
if not name.startswith("__"):
continue
if name in ignore:
continue
if name in dct:
continue
setattr(cls, name, _wrapped(name))
Usage:
from decimal import Decimal
class D(metaclass=Wrapper):
__wraps__ = Decimal
print([D('1.0'), D('2.9'), D('3.8')])
# Outputs [D('1.0'), D('2.9'), D('3.8')]
# No functionality is affected!
print(D('2.3').copy_sign(D('-1.5'))) # -2.3
print(D('3.14').sqrt()) # 1.772004514666935040199112510
print([D('1.0') + D('2.9')]) # [D('3.9')]
from decimal import Decimal
class D(Decimal):
def __init__(self, value):
self.value = value
def __repr__(self):
return f"D({self.value})"
I agree with the previous poster: use this code with caution, as you can easily confuse the hell out of another developer if you pass this along.
This is silly. The default outputs of built-in classes are only there for convenience and debugging. If you want to present usable output, then YOU are responsible for creating that output. Don’t just print a list. Go to the trouble of writing a short function to product the output YOU need.