__getattribute__ method
This method usually implements a class's getter to get whatever you request (attributes and methods) from an object/a class.
I don't know for sure the difference with __getattr__
.
A common way of implementing this method is as follows:
import numpy as np
class test:
def __init__(self, lo=1, hi=20, size=10):
self.array = np.random.randint(low=lo, high=hi, size=size)
def __getattribute__(self, name):
builtin_members = ['count']
if name in builtin_members:
return super().__getattribute__(name)
def count(self):
return np.size(self.array)
Question: What is the following codes' output?
The answer is 1.
And what about the results when we change the test's structure to this:
import numpy as np
class test:
def __init__(self, lo=1, hi=20, size=10):
self.array = np.random.randint(low=lo, high=hi, size=size)
def __getattribute__(self, name):
builtin_members = ['count']
if name in builtin_members:
return super().__getattribute__(name)
else:
return object.__getattribute__(self, name)
def count(self):
return np.size(self.array)
The answer is 10.
And the reason is 1. test
inherits from object
, so super().__getattribute__(name)
is equivalent to object.__getattribute__(self, name)
. 2. the first class doesn't implement the case when non-built-in members (which in this case is array
) are retrieved, so it automatically returns None
whose size is 1.
Sometimes, there are many other complex implementation of an attribute getter.
__iadd__ and __add__ method
Methods like __iadd__
are called to implement augmented arithmetic assignments. Specifically, these two methods are the implementations of the operators +=
and +
. To overload these 2 operators, we must implement and override __iadd__
and __add__
methods of a class. If __iadd__
is not implemented, the augmented assignment +=
falls back to the normal methods. Originally, x += y
is equivalent to x = x.__iadd__(y)
. When __iadd__
is not implemented, __add__
or __radd__
will be the alternative method to overload +=
. We can look at some examples below
class A:
def __init__(self, val):
self.val = val
def __str__(self):
return str(self.val)
def __add__(self, other):
return A(self.val + other.val)
def __iadd__(self, other):
self.val += other.val
return self
a1, a2 = A(1), A(2)
a3 = a1 + a2
print(a1, a2, a3) #1, 2, 3
a3 += a1
print(a1, a2, a3) #1, 2, 4
If __iadd__
is to be implemented, then there are a few things to watch out for. Typically, __iadd__
should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self). And consequently, __iadd__
is implemented for mutable objects like list
and dict
. For immutable objects like int
and string
, __iadd__
simply uses __add__
or __radd__
as alternatives to maintain the immutability. Let's look at a few examples to get familiar with this idea.
The above example clearly displays the difference between __add__
and __iadd__
on mutable lists. a += [2]
changes what a
is referencing but a + [3]
simply returns a new list without changing a
or [3]
. The assignment =
only makes a
reference the new list but doesn't change what b
references. Also, for lists, a += [3]
is just like a.append(ele)
because they both mutate the list in-place and maintain a
's reference while a = a + [3]
returns a new list and performs an assignment and finally changing a
's reference.
For data of int
types, __iadd__
is the same as __add__
. Then let's take a look at an example with unexpected errors concerning __iadd__
.
a_tuple = ([1, 2], [3])
a_tuple[0] += [3]
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
As we can see, +=
operations to a mutable list fails because it is at the same time an element of a immutable tuple. But if we take a look at a_tuple[0]
and we will find a_tuple[0]
has become [1, 2, 3]
successfully. Why? If we recreate this process with intermediate results, we will get
result = a_tuple[0].__iadd__([1])
a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Simply speaking, the object a_tuple[0]
references (which is [1, 2]
) has been smoothly mutated to [1, 2, 3]
because of __iadd__
. However, a_tuple[0] = result
serves to mutate the reference of a_tuple
's first element while it is prohibited by Python to maintain tuple
's immutability. However, we can make some minor changes to make this happen
This actually doesn't change any reference within that tuple because the only different thing is the list instead of the reference. And this can be proved as below
a_tuple = ([1, 2], [3])
print([id(x) for x in a_tuple]) # [1341325857224, 1341325857736]
a_tuple[0].append(3)
print([id(x) for x in a_tuple]) # [1341325857224, 1341325857736]
In a summary, you cannot make the tuple reference different things but you can change what is inside the list as you leave the reference alone. For more info, please refer to the official docs Python Assignment Statements.