Monkey patching operator overloads behaves differently in Python2 vs Python3 [duplicate] - python

This question already has answers here:
Overriding special methods on an instance
(5 answers)
Closed 6 years ago.
Consider the following code:
class Foo:
def __mul__(self,other):
return other/0
x = Foo()
x.__mul__ = lambda other:other*0.5
print(x.__mul__(5))
print(x*5)
In Python2 (with from future import print), this outputs
2.5
2.5
In Python3, this outputs
2.5
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<ipython-input-1-36322c94fe3a> in <module>()
5 x.__mul__ = lambda other:other*0.5
6 print(x.__mul__(5))
----> 7 print(x*5)
<ipython-input-1-36322c94fe3a> in __mul__(self, other)
1 class Foo:
2 def __mul__(self,other):
----> 3 return other/0
4 x = Foo()
5 x.__mul__ = lambda other:other*0.5
ZeroDivisionError: division by zero
I ran into this situation when I was trying to implement a type that supported a subset of algebraic operations. For one instance, I needed to modify the multiplication function for laziness: some computation must be deferred until the instance is multiplied with another variable. The monkey patch worked in Python 2, but I noticed it failed in 3.
Why does this happen?
Is there any way to get more flexible operator overloading in Python3?

That is not a monkeypatch.
This would have been a monkeypatch:
class Foo:
def __mul__(self, other):
return other / 0
Foo.__mul__ = lambda self,other: other * 0.5
x = Foo()
x*9 # prints 4.5
What was done with x.__mul__ = lambda other:other*0.5 was creating a __mul__ attribute on the x instance.
Then, it was expected that x*5 would call x.__mul__(5). And it did, in Python 2.
In Python 3, it called Foo.__mul__(x, 5), so the attribute was not used.
Python 2 would have done the same as Python 3, but it did not because Foo was created as an old-style class.
This code would be equivalent for Python 2 and Python 3:
class Foo(object):
def __mul__(self,other):
return other/0
x = Foo()
x.__mul__ = lambda other:other*0.5
print(x.__mul__(5))
print(x*5)
That will raise an exception. Note the (object).

You can't override the special methods on instance level. Based on python's documentation:
For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.
The rationale behind this behaviour lies with a number of special methods such as __hash__() and __repr__() that are implemented by all objects, including type objects. If the implicit lookup of these methods used the conventional lookup process, they would fail when invoked on the type object itself:
>>> 1 .__hash__() == hash(1)
True
>>> int.__hash__() == hash(int)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' of 'int' object needs an argument
So one simple way is defining a regular function for your your monkey-patching aims, and assign your new method to it:
In [45]: class Foo:
def __init__(self, arg):
self.arg = arg
def __mul__(self,other):
return other * self.arg
def _mul(self, other):
return other/0
Demo:
In [47]: x = Foo(10)
In [48]: x * 3
Out[48]: 30
In [49]: my_func = lambda x: x * 0.5
In [50]: x._mul = my
my_func mypub/
In [50]: x._mul = my_func
In [51]: x._mul(4)
Out[51]: 2.0

Related

How to find out if a function has been declared by `lambda` or `def`?

If I declare two functions a and b:
def a(x):
return x**2
b = lambda x: x**2
I can not use type to differentiate them, since they're both of the same type.
assert type(a) == type(b)
Also, types.LambdaType doesn't help:
>>> import types
>>> isinstance(a, types.LambdaType)
True
>>> isinstance(b, types.LambdaType)
True
One could use __name__ like:
def is_lambda_function(function):
return function.__name__ == "<lambda>"
>>> is_lambda_function(a)
False
>>> is_lambda_function(b)
True
However, since __name__ could have been modified, is_lambda_function is not guaranteed to return the correct result:
>>> a.__name__ = '<lambda>'
>>> is_lambda_function(a)
True
Is there a way which produces a more reliable result than the __name__ attribute?
AFAIK, you cannot reliably in Python 3.
Python 2 used to define a bunch of function types. For that reason, methods, lambdas and plain functions have each their own type.
Python 3 has only one type which is function. There are indeed different side effects where declaring a regular function with def and a lambda: def sets the name to the name (and qualified name) of the function and can set a docstring, while lambda sets the name (and qualified name) to be <lambda>, and sets the docstring to None. But as this can be changed...
If the functions are loaded from a regular Python source (and not typed in an interactive environment), the inspect module allows to access the original Python code:
import inspect
def f(x):
return x**2
g = lambda x: x**2
def is_lambda_func(f):
"""Tests whether f was declared as a lambda.
Returns: True for a lambda, False for a function or method declared with def
Raises:
TypeError if f in not a function
OSError('could not get source code') if f was not declared in a Python module
but (for example) in an interactive session
"""
if not inspect.isfunction(f):
raise TypeError('not a function')
src = inspect.getsource(f)
return not src.startswith('def') and not src.startswith('#') # provision for decorated funcs
g.__name__ = 'g'
g.__qualname__ = 'g'
print(f, is_lambda_func(f))
print(g, is_lambda_func(g))
This will print:
<function f at 0x00000253957B7840> False
<function g at 0x00000253957B78C8> True
By the way, if the problem was serialization of function, a function declared as a lambda can successfully be pickled, provided you give it a unique qualified name:
>>> g = lambda x: 3*x
>>> g.__qualname__ = "g"
>>> pickle.dumps(g)
b'\x80\x03c__main__\ng\nq\x00.'
You can check __code__.co_name. It contains what the name was at the time the function/lambda was compiled:
def a(x):
return x**2
b = lambda x: x**2
def is_lambda_function(f):
return f.__code__.co_name == "<lambda>"
>>> is_lambda_function(a)
False
>>> is_lambda_function(b)
True
And, contrary to __name__, __code__.co_name is read-only...
>>> a.__name__ = "<lambda>"
>>> b.__name__ = "b"
>>> a.__code__.co_name = "<lambda>"
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: readonly attribute
>>> b.__code__.co_name = "b"
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: readonly attribute
... so the results will stay the same:
>>> is_lambda_function(a)
False
>>> is_lambda_function(b)
True
I took the chance to dive in cpython's source to see if I could find anything, and I am afraid I have to second Serge's answer: you cannot.
Briefly, this is a lambda's journey in the interpreter:
During parsing, lambdas, just like every other expression, are read into an expr_ty, which is a huge union containing data of every expression.
This expr_ty is then converted to the appropriate type (Lambda, in our case)
After some time we land into the function that compiles lambdas
This function calls assemble, which calls makecode, which initializes a PyCodeObject (functions, methods, as well as lambdas, all end up here).
From this, I don't see anything particular that is specific to lambdas. This, combined with the fact that Python lets you modify pretty much every attribute of objects makes me/us believe what you want to do is not possible.

Can addition between classes be evaluated by the class of the 2nd argument? [duplicate]

This question already has an answer here:
Addition overloading in Python behaviour x+y vs y+x
(1 answer)
Closed 6 years ago.
I've defined a new modulus 7 class:
class mod7:
def __init__(self, n):
self.val = n % 7
def __repr__(self):
return str(self.val)
Then
In [2]: a = mod7(18)
In [3]: a
Out[3]: 4
I wanted to allow additions of not just mod7 objects but also of integers to the new class. So under the class I've added
def __add__(self, other):
if type(other) is int:
return mod7(self.val + other)
if type(other) is mod7:
return mod7(self.val + other.val)
and
In [11]: a + mod7(5)
Out[11]: 2
In [12]: a + 5
Out[12]: 2
However
In [13]: 5 + a
Traceback (most recent call last):
File "<ipython-input-13-14fd8adcbf4d>", line 1, in <module>
5+a
TypeError: unsupported operand type(s) for +: 'int' and 'mod7'
I understand why - The addition function is called by the class of the 1st argument. My question: Is there any way to override 5 + a according to the class of 2nd argument, i.e. a?
You'll want to also define __radd__. This would be called automatically if the left operand did not support +, but ints do, so you have to define it specifically to handle this situation. You can just define it to call your __add__ function so you don't have to rewrite anything.

Is it possible to modify the behavior of len()?

I'm aware of creating a custom __repr__ or __add__ method (and so on), to modify the behavior of operators and functions. Is there a method override for len?
For example:
class Foo:
def __repr__(self):
return "A wild Foo Class in its natural habitat."
foo = Foo()
print(foo) # A wild Foo Class in its natural habitat.
print(repr(foo)) # A wild Foo Class in its natural habitat.
Could this be done for len, with a list? Normally, it would look like this:
foo = []
print(len(foo)) # 0
foo = [1, 2, 3]
print(len(foo)) # 3
What if I want to leave search types out of the count? Like this:
class Bar(list):
pass
foo = [Bar(), 1, '']
print(len(foo)) # 3
count = 0
for item in foo:
if not isinstance(item, Bar):
count += 1
print(count) # 2
Is there a way to do this from within a list subclass?
Yes, implement the __len__ method:
def __len__(self):
return 42
Demo:
>>> class Foo(object):
... def __len__(self):
... return 42
...
>>> len(Foo())
42
From the documentation:
Called to implement the built-in function len(). Should return the length of the object, an integer >= 0. Also, an object that doesn’t define a __bool__() method and whose __len__() method returns zero is considered to be false in a Boolean context.
For your specific case:
>>> class Bar(list):
... def __len__(self):
... return sum(1 for ob in self if not isinstance(ob, Bar))
...
>>> len(Bar([1, 2, 3]))
3
>>> len(Bar([1, 2, 3, Bar()]))
3
Yes, just as you have already discovered that you can override the behaviour of a repr() function call by implementing the __repr__ magic method, you can specify the behaviour from a len() function call by implementing (surprise surprise) then __len__ magic:
>>> class Thing:
... def __len__(self):
... return 123
...
>>> len(Thing())
123
A pedant might mention that you are not modifying the behaviour of len(), you are modifying the behaviour of your class. len just does the same thing it always does, which includes checking for a __len__ attribute on the argument.
Remember: Python is a dynamically and Duck Typed language.
If it acts like something that might have a length;
class MyCollection(object):
def __len__(self):
return 1234
Example:
>>> obj = MyCollection()
>>> len(obj)
1234
if it doesn't act like it has a length; KABOOM!
class Foo(object):
def __repr___(self):
return "<Foo>"
Example:
>>> try:
... obj = Foo()
... len(obj)
... except:
... raise
...
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
TypeError: object of type 'Foo' has no len()
From Typing:
Python uses duck typing and has typed objects but untyped variable
names. Type constraints are not checked at compile time; rather,
operations on an object may fail, signifying that the given object is
not of a suitable type. Despite being dynamically typed, Python is
strongly typed, forbidding operations that are not well-defined (for
example, adding a number to a string) rather than silently attempting
to make sense of them.
Example:
>>> x = 1234
>>> s = "1234"
>>> x + s
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
You can just add a __len__ method to your class.
class Test:
def __len__(self):
return 2
a=Test()
len(a) # --> 2

How to implement "__iadd__()" for an immutable type?

I would like to subclass an immutable type or implement one of my own which behaves like an int does as shown in the following console session:
>>> i=42
>>> id(i)
10021708
>>> i.__iadd__(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__iadd__'
>>> i += 1
>>> i
43
>>> id(i)
10021696
Not surprisingly, int objects have no __iadd__() method, yet applying += to one doesn't result in an error, instead it apparently creates a new int and also somehow magically reassigns it to the name given in the augmented assignment statement.
Is it possible to create a user-defined class or subclass of a built-in immutable one that does this, and if so, how?
Simply don't implement __iadd__, but only __add__:
>>> class X(object):
... def __add__(self, o):
... return "added"
>>> x = X()
>>> x += 2
>>> x
'added'
If there's no x.__iadd__, Python simply calculates x += y as x = x + y doc.
The return value of __iadd__() is used. You don't need to return the object that's being added to; you can create a new one and return that instead. In fact, if the object is immutable, you have to.
import os.path
class Path(str):
def __iadd__(self, other):
return Path(os.path.join(str(self), str(other)))
path = Path("C:\\")
path += "windows"
print path
When it sees i += 1, Python will try to call __iadd__. If that fails, it'll try to call __add__.
In both cases, the result of the call will be bound to the name, i.e. it'll attempt i = i.__iadd__(1) and then i = i.__add__(1).
class aug_int:
def __init__(self, value):
self.value = value
def __iadd__(self, other):
self.value += other
return self
>>> i = aug_int(34)
>>> i
<__main__.aug_int instance at 0x02368E68>
>>> i.value
34
>>> i += 55
>>> i
<__main__.aug_int instance at 0x02368E68>
>>> i.value
89
>>>

User defined __mul__ method is not commutative

I wrote a class to represent vectors in Python (as an exercise) and I'm having problems with extending the built-in operators.
I defined a __mul__ method for the vector class. The problem is that in the expression x * y the interpreter calls the __mul__ method of x, not y.
So vector(1, 2, 3) * 2 returns a vector <2, 4, 6> just like it should; but 2 * vector(1, 2, 3) creates a TypeError because the built-in int class does not support multiplication by my user-defined vectors.
I could solve this problem by simply writing a new multiplication function
def multiply(a, b):
try:
return a * b
except TypeError:
return b * a
but this would require redefining every function that I want to use with my user-defined classes.
Is there a way to make the built-in function handle this correctly?
If you want commutativity for different types you need to implement __rmul__(). If implemented, it is called, like all __r*__() special methods, if the operation would otherwise raise a TypeError. Beware that the arguments are swapped:
class Foo(object):
def __mul_(self, other):
''' multiply self with other, e.g. Foo() * 7 '''
def __rmul__(self, other):
''' multiply other with self, e.g. 7 * Foo() '''
I believe you are looking for __rmul__

Categories