Is there a __repr__() like method for a python class?
Question:
I’m solving a funny problem that requires to define a class that can be called like this:
class Chain(2)(3)(4)
And it should print out the multiplication of all arguments.
I ended up a solution like this:
class Chain():
calc = 1
def __new__(cls, a=None):
if a:
cls.calc = cls.calc*a
return cls
else:
return cls.calc
This works fine and self.calc
is equal to 24 but i have a wrong representation <class '__main__.Chain'>
.
Is there anyway to have representation of multiplication instead of class name like what we have in __repr__
for objects?
note: The call arguments count has no limits and may be different on each call.
Answers:
First of all to answer your direct question from the title:
As everything in Python, classes are too – objects. And just like classes define how instances are created (what attributes and methods they will have), metaclasses define how classes are created. So let’s create a metaclass:
class Meta(type):
def __repr__(self):
return str(self.calc)
class Chain(metaclass=Meta):
calc = 1
def __new__(cls, a=None):
if a:
cls.calc = cls.calc*a
return cls
else:
return cls.calc
print(Chain(2)(3)(4))
This will print, as expected, 24
.
A few notes:
-
Currently Meta
simply accesses a calc
attribute blindly. A check that it actually exists could be done but the code above was just to make the point.
-
The way your class is implemented, you can just do Chain(2)(3)(4)()
and you will get the same result (that’s based on the else
part of your __new__
).
-
That’s a weird way to implement such behavior – you are returning the class itself (or an int…) from the __new__
method which should return a new object of this class. This is problematic design. A classic way to do what you want is by making the objects callable:
class Chain():
def __init__(self, a=1):
self.calc = a
def __call__(self, a=None):
if a:
return self.__class__(self.calc * a)
else:
return self.calc
def __repr__(self):
return str(self.calc)
print(Chain(2)(3)(4))
This solves your problem of even needing to do what you want, because now you just implement the class’ __repr__
(because now each call in the chain returns a new object, and not the class itself).
I’m solving a funny problem that requires to define a class that can be called like this:
class Chain(2)(3)(4)
And it should print out the multiplication of all arguments.
I ended up a solution like this:
class Chain():
calc = 1
def __new__(cls, a=None):
if a:
cls.calc = cls.calc*a
return cls
else:
return cls.calc
This works fine and self.calc
is equal to 24 but i have a wrong representation <class '__main__.Chain'>
.
Is there anyway to have representation of multiplication instead of class name like what we have in __repr__
for objects?
note: The call arguments count has no limits and may be different on each call.
First of all to answer your direct question from the title:
As everything in Python, classes are too – objects. And just like classes define how instances are created (what attributes and methods they will have), metaclasses define how classes are created. So let’s create a metaclass:
class Meta(type):
def __repr__(self):
return str(self.calc)
class Chain(metaclass=Meta):
calc = 1
def __new__(cls, a=None):
if a:
cls.calc = cls.calc*a
return cls
else:
return cls.calc
print(Chain(2)(3)(4))
This will print, as expected, 24
.
A few notes:
-
Currently
Meta
simply accesses acalc
attribute blindly. A check that it actually exists could be done but the code above was just to make the point. -
The way your class is implemented, you can just do
Chain(2)(3)(4)()
and you will get the same result (that’s based on theelse
part of your__new__
). -
That’s a weird way to implement such behavior – you are returning the class itself (or an int…) from the
__new__
method which should return a new object of this class. This is problematic design. A classic way to do what you want is by making the objects callable:
class Chain():
def __init__(self, a=1):
self.calc = a
def __call__(self, a=None):
if a:
return self.__class__(self.calc * a)
else:
return self.calc
def __repr__(self):
return str(self.calc)
print(Chain(2)(3)(4))
This solves your problem of even needing to do what you want, because now you just implement the class’ __repr__
(because now each call in the chain returns a new object, and not the class itself).