Classes: Alternative way of adding len() - python

I am building a string Class that behaves like a regular string class except that the addition operator returns the sum of the lengths of the two strings instead of concatenating them. And then a multiplication operator returns the products of the length of the two strings. So I was planning on doing
class myStr(string):
def __add__(self):
return len(string) + len (input)
at least that is what I have for the first part but that is apparently not correct. Can someone help me correct it.

You need to derive from str, and you can use len(self) to get the length of the current instance. You also need to give __add__ a parameter for the other operand of the + operator.
class myStr(str):
def __add__(self, other):
return len(self) + len(other)
Demo:
>>> class myStr(str):
... def __add__(self, other):
... return len(self) + len(other)
...
>>> foo = myStr('foo')
>>> foo
'foo'
>>> foo + 'bar'
6

string is not a class. It's not anything*. There is no context where len(string) will work unless you define string.
Secondly, __add__ does not have an input parameter.
You need to fix both of these issues.
* You could import a module called string, but it's not something that just exists in global scope.

Related

Implicit conversions in Python

For example, if I wanted to apply mathematical operations on objects in the following way:
class A(object):
def __init__(self, value):
self.value = value
def __repr__(self):
return value
assert(A(1) + A(2) == 3)
I am getting the following error: TypeError: unsupported operand type(s) for +: 'A' and 'A'
Is it possible to evaluate objects to primitives so that I can apply simple operations on them? Similarly how you could use implicit conversions in Scala.
You can implement __add__ to define addition on your class.
class A(object):
def __init__(self, value):
self.value = value
def __repr__(self):
return 'A(%r)'%self.value
def __add__(self, other):
return A(self.value+other.value)
>>> A(1)+A(2)
A(3)
This implementation assumes that you are only trying to add instances of A to other instances of A to get a third instance of A. You can write an __add__ adaptable to what type of operand you need it to work for.
See also __radd__ and __iadd__.
That depends on what you're trying to do. You can define the + operator by defining the __add__ method:
class A(object):
def __init__(self, value):
self.value = value
def __repr__(self):
return value
def __add__(self, other):
return A(self.value + other.value)
then of course in your example code you're trying to compare it to an integer which also need to be defined - which is done by implementing the __eq__ method:
def __eq__(self, other):
try:
self.value == other.value
except AttributeError: # other wasn't of class A, try to compare directly instead
return self.value == other
(implicit typecasts on the other hand is not available as far as I know)
The problem is that there isn't enough context in the expression to decide what the objects should be converted to. Python has various methods that can be defined on an object that implement various operators, including the __add__() and __radd__() methods.
There isn't enough context to know that foo should be equivalent to foo.value, so with Python's philosophy explicit is better than implicit. You can certainly subclass int, but then the operators won't produce your new class, and the object itself would remain immutable (as numbers in Python generally are). Notably, ctypes such as c_int32 have a value attribute like your example but do not implement numeric operators.

Can someone help me understand special methods vs normal methods?

What is the difference between using a special method and just defining a normal class method? I was reading this site which lists a lot of them.
For example it gives a class like this.
class Word(str):
'''Class for words, defining comparison based on word length.'''
def __new__(cls, word):
# Note that we have to use __new__. This is because str is an immutable
# type, so we have to initialize it early (at creation)
if ' ' in word:
print "Value contains spaces. Truncating to first space."
word = word[:word.index(' ')] # Word is now all chars before first space
return str.__new__(cls, word)
def __gt__(self, other):
return len(self) > len(other)
def __lt__(self, other):
return len(self) < len(other)
def __ge__(self, other):
return len(self) >= len(other)
def __le__(self, other):
return len(self) <= len(other)
For each of those special methods why can't I just make a normal method instead, what are they doing different? I think I just need a fundamental explanation that I can't find, thanks.
It is a pythonic way to do this:
word1 = Word('first')
word2 = Word('second')
if word1 > word2:
pass
instead of direct usage of comparator method
NotMagicWord(str):
def is_greater(self, other)
return len(self) > len(other)
word1 = NotMagicWord('first')
word2 = NotMagicWord('second')
if word1.is_greater(word2):
pass
And the same with all other magic method. You define __len__ method to tell python its length using built-in len function, for example. All magic method will be called implicitly while standard operations like binary operators, object calling, comparision and a lot of other. A Guide to Python's Magic Methods is really good, read it and see what behavior you can give to your objects. It similar to operator overloading in C++, if you are familiar with it.
A method like __gt__ is called when you use comparison operators in your code. Writing something like
value1 > value2
Is the equivalent of writing
value1.__gt__(value2)
"Magic methods" are used by Python to implement a lot of its underlying structure.
For example, let's say I have a simple class to represent an (x, y) coordinate pair:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
So, __init__ would be an example of one of these "magic methods" -- it allows me to automatically initialize the class by simply doing Point(3, 2). I could write this without using magic methods by creating my own "init" function, but then I would need to make an explicit method call to initialize my class:
class Point(object):
def init(self, x, y):
self.x = x
self.y = y
return self
p = Point().init(x, y)
Let's take another example -- if I wanted to compare two point variables, I could do:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
This lets me compare two points by doing p1 == p2. In contrast, if I made this a normal eq method, I would have to be more explicit by doing p1.eq(p2).
Basically, magic methods are Python's way of implementing a lot of its syntactic sugar in a way that allows it to be easily customizable by programmers.
For example, I could construct a class that pretends to be a function by implementing __call__:
class Foobar(object):
def __init__(self, a):
self.a = a
def __call__(self, b):
return a + b
f = Foobar(3)
print f(4) # returns 7
Without the magic method, I would have to manually do f.call(4), which means I can no longer pretend the object is a function.
Special methods are handled specially by the rest of the Python language. For example, if you try to compare two Word instances with <, the __lt__ method of Word will be called to determine the result.
The magic methods are called when you use <, ==, > to compare the objects. functools has a helper called total_ordering that will fill in the missing comparison methods if you just define __eq__ and __gt__.
Because str already has all the comparison operations defined, it's necessary to add them as a mixin if you want to take advantage of total_ordering
from functools import total_ordering
#total_ordering
class OrderByLen(object):
def __eq__(self, other):
return len(self) == len(other)
def __gt__(self, other):
return len(self) > len(other)
class Word(OrderByLen, str):
'''Class for words, defining comparison based on word length.'''
def __new__(cls, word):
# Note that we have to use __new__. This is because str is an immutable
# type, so we have to initialize it early (at creation)
if ' ' in word:
print "Value contains spaces. Truncating to first space."
word = word[:word.index(' ')] # Word is now all chars before first space
return str.__new__(cls, word)
print Word('cat') < Word('dog') # False
print Word('cat') > Word('dog') # False
print Word('cat') == Word('dog') # True
print Word('cat') <= Word('elephant') # True
print Word('cat') >= Word('elephant') # False

"subtracting" strings, classes in python

Learning about classes in python. I want the difference between two strings, a sort of subtraction. eg:
a = "abcdef"
b ="abcde"
c = a - b
This would give the output f.
I was looking at this class and I am new to this so would like some clarification on how it works.
class MyStr(str):
def __init__(self, val):
return str.__init__(self, val)
def __sub__(self, other):
if self.count(other) > 0:
return self.replace(other, '', 1)
else:
return self
and this will work in the following way:
>>> a = MyStr('thethethethethe')
>>> b = a - 'the'
>>> a
'thethethethethe'
>>> b
'thethethethe'
>>> b = a - 2 * 'the'
>>> b
'thethethe'
So a string is passed to the class and the constructor is called __init__. This runs the constructor and an object is returned, which contains the value of the string? Then a new subtraction function is created, so that when you use - with the MyStr object it is just defining how subtract works with that class? When sub is called with a string, count is used to check if that string is a substring of the object created. If that is the case, the first occurrence of the passed string is removed. Is this understanding correct?
Edit: basically this class could be reduced to:
class MyStr(str):
def __sub__(self, other):
return self.replace(other, '', 1)
Yes, your understanding is entirely correct.
Python will call a .__sub__() method if present on the left-hand operand; if not, a corresponding .__rsub__() method on the right-hand operand can also hook into the operation.
See emulating numeric types for a list of hooks Python supports for providing more arithmetic operators.
Note that the .count() call is redundant; .replace() will not fail if the other string is not present; the whole function could be simplified to:
def __sub__(self, other):
return self.replace(other, '', 1)
The reverse version would be:
def __rsub__(self, other):
return other.replace(self, '', 1)

__add__ all elements of a list

I'd like to combine a list of class instances of a class for which the __add__ method is defined.
i.e., I have a list of class instances L=[A,B,C,D] and I want their sum E = A+B+C+D, but generalized so that instead of the + syntax I could do something like E = sum(L).
What function should I use to do that? Is the __add__ method adequate, or do I need to define a different class method (e.g. __iadd__) in order to accomplish this?
(if this turns out to be a duplicate, how should I be asking the question?)
import operator
reduce(operator.add, L)
sum may want to add numerical values to instances of your class. Define __radd__ so for example int + Foo(1) will be defined:
class Foo(object):
def __init__(self, val):
self.val = val
def __add__(self, other):
return self.val + other.val
def __radd__(self, other):
return other + self.val
A = Foo(1)
B = Foo(2)
L = [A,B]
print(A+B)
# 3
print(sum(L))
# 3
Ignore my previous answer, it was wrong.
The reduce function allows you to apply any binary function or method to all the elements of a sequence. So, you could write:
reduce(YourClass.__add__, sequence)
If not all objects in the sequence are instances of the same class, then instead use this:
import operator
reduce(operator.add, sequence)
Or this:
reduce(lambda x, y: x + y, sequence)

TypeError after overriding the __add__ method

I am trying to understand how __add__ works:
class MyNum:
def __init__(self,num):
self.num=num
def __add__(self,other):
return MyNum(self.num+other.num)
def __str__(self):
return str(self.num)
If I put them in a list
d=[MyNum(i) for i in range(10)]
this works
t=MyNum(0)
for n in d:
t=t+n
print t
But this does not:
print sum(d)
TypeError: unsupported operand type(s) for +: 'int' and 'instance'
What am I doing wrong? How can I get the sum() to work?
My problem is how to use the sum on a list of objects that support the __add__, need to keep it as generic as possible.
You need to define __radd__ as well to get this to work.
__radd__ is reverse add. When Python tries to evaluate x + y it first attempts to call x.__add__(y). If this fails then it falls back to y.__radd__(x).
This allows you to override addition by only touching one class. Consider for example how Python would have to evaluate 0 + x. A call to 0.__add__(x) is attempted but int knows nothing about your class. You can't very well change the __add__ method in int, hence the need for __radd__. I suppose it is a form of dependency inversion.
As Steven pointed out, sum operates in place, but starts from 0. So the very first addition is the only one that would need to use __radd__. As a nice exercise you could check that this was the case!
>>> help(sum)
Help on built-in function sum in module __builtin__:
sum(...)
sum(sequence[, start]) -> value
Returns the sum of a sequence of numbers (NOT strings) plus the value
of parameter 'start' (which defaults to 0). When the sequence is
empty, returns start.
In other words, provide a start value:
sum(d, MyNum(0))
Edit pasted from my below comment:
sum works with a default start value of the integer zero. Your MyNum class as written does not know how to add itself to integers. To solve this you have two options. Either you can provide a start value to sum that has the same type as you class, or you can implement __radd__, which Python calls when adding values of differing types (such as when the first value in d is added to the default start value of zero).
I oppose relaying on sum() with a start point, the loop hole exposed below,
In [51]: x = sum(d, MyNum(2))
In [52]: x.num
Out[52]: 47
Wondering why you got 47 while you are expecting like
…start from 2nd of MyNum() while leaving first and add them till end, so the expected result = 44 (sum(range(2,10))
The truth here is that 2 is not kept as start object/position but instead treated as an addition to the result
sum(range(10)) + 2
oops, link broken !!!!!!
Use radd
Here below the correct code. Also note the below
Python calls __radd__ only when the object on the right side of the + is your class instance
eg: 2 + obj1
#!/usr/bin/env python
class MyNum:
def __init__(self,num):
self.num=num
def __add__(self,other):
return MyNum(self.num+other.num)
def __radd__(self,other):
return MyNum(self.num+other)
def __str__(self):
return str(self.num)
d=[MyNum(i) for i in range(10)]
print sum(d) ## Prints 45
d=[MyNum(i) for i in range(2, 10)]
print sum(d) ## Prints 44
print sum(d,MyNum(2)) ## Prints 46 - adding 2 to the last value (44+2)
class MyNum:
def __init__(self,num):
self.num=num
def __add__(self,other):
return self.num += other.num
def __str__(self):
return str(self.num)
one = MyNum(1)
two = MyNum(2)
one + two
print(two.num)
Another option is reduce (functools.reduce in Python 3.x).
from functools import reduce
from operators import add
d=[MyNum(i) for i in range(10)]
my_sum = reduce(add,d)

Categories

Resources