Say I have this class:
class MyString(str):
def someExtraMethod(self):
pass
I'd like to be able to do
a = MyString("Hello ")
b = MyString("World")
(a + b).someExtraMethod()
("a" + b).someExtraMethod()
(a + "b").someExtraMethod()
Running as is:
AttributeError: 'str' object has no attribute 'someExtraMethod'
Obviously that doesn't work. So I add this:
def __add__(self, other):
return MyString(super(MyString, self) + other)
def __radd__(self, other):
return MyString(other + super(MyString, self))
TypeError: cannot concatenate 'str' and 'super' objects
Hmmm, ok. super doesn't appear to respect operator overloading. Perhaps:
def __add__(self, other):
return MyString(super(MyString, self).__add__(other))
def __radd__(self, other):
return MyString(super(MyString, self).__radd__(other))
AttributeError: 'super' object has no attribute '__radd__'
Still no luck. What should I be doing here?
I usually use the super(MyClass, self).__method__(other) syntax, in your case this does not work because str does not provide __radd__. But you can convert the instance of your class into a string using str.
To who said that its version 3 works: it does not:
>>> class MyString(str):
... def __add__(self, other):
... print 'called'
... return MyString(super(MyString, self).__add__(other))
...
>>> 'test' + MyString('test')
'testtest'
>>> ('test' + MyString('test')).__class__
<type 'str'>
And if you implement __radd__ you get an AttributeError(see the note below).
Anyway, I'd avoid using built-ins as base type. As you can see some details may be tricky, and also you must redefine all the operations that they support, otherwise the object will become an instance of the built-in and not an instance of your class.
I think in most cases it's easier to use delegation instead of inheritance.
Also, if you simply want to add a single method, then you may try to use a function on plain strings instead.
I add here a bit of explanation about why str does not provide __radd__ and what's going on when python executes a BINARY_ADD opcode(the one that does the +).
The abscence of str.__radd__ is due to the fact that string objects implements the concatenation operator of sequences and not the numeric addition operation. The same is true for the other sequences such as list or tuple. These are the same at "Python level" but actually have two different "slots" in the C structures.
The numeric + operator, which in python is defined by two different methods(__add__ and __radd__) is actually a single C function that is called with swapped arguments to simulate a call to __radd__.
Now, you could think that implementing only MyString.__add__ would fix your problem, since str does not implement __radd__, but that's not true:
>>> class MyString(str):
... def __add__(self, s):
... print '__add__'
... return MyString(str(self) + s)
...
>>> 'test' + MyString('test')
'testtest'
As you can see MyString.__add__ is not called, but if we swap arguments:
>>> MyString('test') + 'test'
__add__
'testtest'
It is called, so what's happening?
The answer is in the documentation which states that:
For objects x and y, first x.__op__(y) is tried. If this is not
implemented or returns NotImplemented, y.__rop__(x) is tried. If this
is also not implemented or returns NotImplemented, a TypeError
exception is raised. But see the following exception:
Exception to the previous item: if the left operand is an instance of
a built-in type or a new-style class, and the right operand is an
instance of a proper subclass of that type or class and overrides the
base’s __rop__() method, the right operand’s __rop__() method is tried
before the left operand’s __op__() method.
This is done so that a subclass can completely override binary
operators. Otherwise, the left operand’s __op__() method would always
accept the right operand: when an instance of a given class is
expected, an instance of a subclass of that class is always
acceptable.
Which means you must implement all the methods of str and the __r*__ methods, otherwise you'll still have problems with argument order.
May be:
class MyString(str):
def m(self):
print(self)
def __add__(self, other):
return MyString(str(self) + other)
def __radd__(self, other):
return MyString(other + str(self))
a = MyString("Hello ")
b = MyString("World")
(a + b).m()
Update: you last version with super works for me
Related
I know how to use magic methods to define mathematical operators in a custom class.
class CustomDataClass:
def __init__(self, data):
self.data = data
def __str__(self):
return str(self.data)
def __add__(self, obj):
self.data+= obj
return self
a = CustomDataClass(1)
Then in the console you could do
print(a+1)
>>> 2
What if I want to do it in the opposite order?
print(1+a)
This would throw an error. I am curious how do I go about it. How did they do this in pandas for example?
import pandas as pd
a = pd.Series(1)
print(1+a)
>>> 0 2
dtype: int64
You can implement __radd__ which will be called if the l.h.s. __add__ method returns NotImplemented (which is the case for 1 + a).
Quoting the docs on __r*__ methods:
These functions are only called if the left operand does not support the corresponding operation and the operands are of different types. For instance, to evaluate the expression x - y, where y is an instance of a class that has an __rsub__() method, y.__rsub__(x) is called if x.__sub__(y) returns NotImplemented.
I've a question regarding overriding operator magic methods. I'm trying to provide the methods/attributes of value to the object a which holds that attribute. I would like to do so, because I have an application where value might be of changing type and it's not only limited to the add function. I could re-implement each magic method returning the return value of the corresponding magic method of value. Nevertheless, I was wondering whether it might work by re-implementing __getattr__.
Why does the example below raise an exception?
How does python call the __add__ or __radd__ method when + is used?
Is it possible to redirect that call without re-implementing __add__?
Thanks a lot for your help!
class Attr:
def __init__(self, value) -> None:
self.value = value
def __getattr__(self, name):
try:
return super().__getattribute__(name)
except Exception as e:
value = super().__getattribute__("value")
return getattr(value, name)
if __name__ == "__main__":
a = Attr(1)
print("A:", a.value + 1) # returns 2
print("B:", getattr(a, "__add__")(1)) # returns 2
print("C:", getattr(a, "__radd__")(1)) # returns 2
print("D:", a.__add__(1)) # returns 2
print("E:", a.__radd__(1)) # returns 2
print("F:", a + 1) # raises: TypeError: unsupported operand type(s) for +: 'Attr' and 'int'
When dealing with python objects, if you use the + operator, you're basically implying an addition, but those two are not numbers, that's why the error..
In your case here you have a type Attr and a type int, in this case you cannot add them directly. You cannot also convert the Attr to an int implicitly like so: int(a) because it is not a supported type for converting to integers
The __add__() function when used with objects and attributes, implicitly defines what you want to happen, which is addition
Say I have a Python class as follows:
class TestClass():
value = 20
def __str__(self):
return str(self.value)
The __str__ method will automatically be called any time I try to use an instance of TestClass as a string, like in print. Is there any equivalent for treating it as a number? For example, in
an_object = TestClass()
if an_object > 30:
...
where some hypothetical __num__ function would be automatically called to interpret the object as a number. How could this be easily done?
Ideally I'd like to avoid overloading every normal mathematical operator.
You can provide __float__(), __int__(), and/or __complex__() methods to convert objects to numbers. There is also a __round__() method you can provide for custom rounding. Documentation here. The __bool__() method technically fits here too, since Booleans are a subclass of integers in Python.
While Python does implicitly convert objects to strings for e.g. print(), it never converts objects to numbers without you saying to. Thus, Foo() + 42 isn't valid just because Foo has an __int__ method. You have to explicitly use int() or float() or complex() on them. At least that way, you know what you're getting just by reading the code.
To get classes to actually behave like numbers, you have to implement all the special methods for the operations that numbers participate in, including arithmetic and comparisons. As you note, this gets annoying. You can, however, write a mixin class so that at least you only have to write it once. Such as:
class NumberMixin(object):
def __eq__(self, other): return self.__num__() == self.__getval__(other)
# other comparison methods
def __add__(self, other): return self.__num__() + self.__getval__(other)
def __radd__(self, other): return self.__getval__(other) + self.__num__()
# etc., I'm not going to write them all out, are you crazy?
This class expects two special methods on the class it's mixed in with.
__num__() - converts self to a number. Usually this will be an alias for the conversion method for the most precise type supported by the object. For example, your class might have __int__() and __float__() methods, but __int__() will truncate the number, so you assign __num__ = __float__ in your class definition. On the other hand, if your class has a natural integral value, you might want to provide __float__ so it can also be converted to a float, but you'd use __num__ = __int__ since it should behave like an integer.
__getval__() - a static method that obtains the numeric value from another object. This is useful when you want to be able to support operations with objects other than numeric types. For example, when comparing, you might want to be able to compare to objects of your own type, as well as to traditional numeric types. You can write __getval__() to fish out the right attribute or call the right method of those other objects. Of course with your own instances you can just rely on float() to do the right thing, but __getval__() lets you be as flexible as you like in what you accept.
A simple example class using this mixin:
class FauxFloat(NumberMixin):
def __init__(self, value): self.value = float(value)
def __int__(self): return int(self.value)
def __float__(self): return float(self.value)
def __round__(self, digits=0): return round(self.value, digits)
def __str__(self): return str(self.value)
__repr__ = __str__
__num__ = __float__
#staticmethod
def __getval__(obj):
if isinstance(obj, FauxFloat):
return float(obj)
if hasattr(type(obj), "__num__") and callable(type(obj).__num__):
return type(obj).__num__(obj) # don't call dunder method on instance
try:
return float(obj)
except TypeError:
return int(obj)
ff = FauxFloat(42)
print(ff + 13) # 55.0
For extra credit, you could register your class so it'll be seen as a subclass of an appropriate abstract base class:
import numbers
numbers.Real.register(FauxFloat)
issubclass(FauxFloat, numbers.Real) # True
For extra extra credit, you might also create a global num() function that calls __num__() on objects that have it, otherwise falling back to the older methods.
In case of numbers it a bit more complicated. But its possible! You have to override your class operators to fit your needs.
operator.__lt__(a, b) # lower than
operator.__le__(a, b) # lower equal
operator.__eq__(a, b) # equal
operator.__ne__(a, b) # not equal
operator.__ge__(a, b) # greater equial
operator.__gt__(a, b) # greater than
Python Operators
Looks like you need __gt__ method.
class A:
val = 0
def __gt__(self, other):
return self.val > other
a = A()
a.val = 12
a > 10
If you just wanna cast object to int - you should define __int__ method (or __float__).
I have one class A which extends B, and B has one method count(). Now I want to allow user call both A.count and A.count(). A.count means count is one field of A while A.count() means it is method derived from B.
This is impossible in Python, and here's why:
You can always assign a method (or really any function) to a variable and call it later.
hello = some_function
hello()
is semantically identical to
some_function()
So what would happen if you had an object of your class A called x:
x = A()
foo = x.count
foo()
The only way you could do this is by storing a special object in x.count that is callable and also turns into e.g. an integer when used in that way, but that is horrible and doesn't actually work according to specification.
As i said, it's not exactly impossible, as told by other answers. Lets see a didactic example:
class A(object):
class COUNT(object):
__val = 12345
def __call__(self, *args, **kwargs):
return self.__val
def __getattr__(self, item):
return self.__val
def __str__(self):
return str(self.__val)
count = COUNT()
if __name__ == '__main__':
your_inst = A()
print(your_inst.count)
# outputs: 12345
print(your_inst.count())
# outputs: 12345
As you may notice, you need to implement a series of things to accomplish that kind of behaviour. First, your class will need to implement the attribute count not as the value type that you intent, but as an instance of another class, which will have to implement, among other things (to make that class behave, by duck typing, as the type you intent) the __call__ method, that should return the same as you A class __getattr__, that way, the public attribute count will answer as a callable (your_inst.count()) or, as you call, a field (your_inst.count), the same way.
By the way, i don't know if the following is clear to you or not, but it may help you understand why it isn't as trivial as one may think it is to make count and count() behave the same way:
class A(object):
def count(self):
return 123
if __name__ == '__main__':
a = A()
print(type(a.count))
# outputs: <class 'method'>
print(type(a.count()))
# outputs: <class 'int'>
. invokes the a class __getattr__ to get the item count. a.count will return the referente to that function (python's function are first class objects), the second one, will do the same, but the parentheses will invoke the __call__ method from a.count.
Can magic methods be overridden outside of a class?
When I do something like this
def __int__(x):
return x + 5
a = 5
print(int(a))
it prints '5' instead of '10'. Do I do something wrong or magic methods just can't be overridden outside of a class?
Short answer; not really.
You cannot arbitrarily change the behaviour of int() a builtin function (*which internally calls __int__()) on arbitrary builtin types such as int(s).
You can however change the behaviour of custom objects like this:
Example:
class Foo(object):
def __init__(self, value):
self.value = value
def __add__(self, other):
self.value += other
def __repr__(self):
return "<Foo(value={0:d})>".format(self.value)
Demo:
>>> x = Foo(5)
>>> x + 5
>>> x
<Foo(value=10)>
This overrides two things here and implements two special methods:
__repr__() which get called by repr()
__add__() which get called by the + operator.
Update: As per the comments above; techincally you can redefine the builtin function int; Example:
def int(x):
return x + 5
int(5) # returns 10
However this is not recommended and does not change the overall behaviour of the object x.
Update #2: The reason you cannot change the behaviour of bultin types (without modifying the underlying source or using Cuthon or ctypes) is because builtin types in Python are not exposed or mutable to the user unlike Homoiconic Languages (See: Homoiconicity). -- Even then I'm not really sure you can with Cython/ctypes; but the reason question is "Why do you want to do this?"
Update #3: See Python's documentation on Data Model (object.__complex__ for example).
You can redefine a top-level __int__ function, but nobody ever calls that.
As implied in the Data Model documentation, when you write int(x), that calls x.__int__(), not __int__(x).
And even that isn't really true. First, __int__ is a special method, meaning it's allowed to call type(x).__int__(x) rather than x.__int__(), but that doesn't matter here. Second, it's not required to call __int__ unless you give it something that isn't already an int (and call it with the one-argument form). So, it could be as if it's was written like this:
def int(x, base=None):
if base is not None:
return do_basey_stuff(x, base)
if isinstance(x, int):
return x
return type(x).__int__(x)
So, there is no way to change what int(5) will do… short of just shadowing the builtin int function with a different builtin/global/local function of the same name, of course.
But what if you wanted to, say, change int(5.5)? That's not an int, so it's going to call float.__int__(5.5). So, all we have to do is monkeypatch that, right?
Well, yes, except that Python allows builtin types to be immutable, and most of the builtin types in CPython are. So, if you try it:
>>> _real_float_int = float.__int__
>>> def _float_int(self):
... return _real_float_int(self) + 5
>>> _float_int(5.5)
10
>>> float.__int__ = _float_int
TypeError: can't set attributes of built-in/extension type 'float'
However, if you're defining your own types, that's a different story:
>>> class MyFloat(float):
... def __int__(self):
... return super().__int__() + 5
>>> f = MyFloat(5.5)
>>> int(f)
10