'How to check if a class has an attribute with `'attr' in instance` syntax
I want to be able to use the 'attr' in instance syntax to check if my dataclass has the specified attribute but I can't seem to make it work.
What I want is same behavior as this example with pandas
import pandas as pd
df = pd.DataFrame(columns=['a', 'b', 'c'])
print('a' in df)
True
But just for a custom dataclass
from dataclasses import dataclass
@dataclass
class User:
email: int
password: str
blocked_at: float = None
def __getitem__(self, item):
return getattr(self, item)
user = User('[email protected]', 'password')
print(user['email'])
'email' in user
[email protected]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [35], in <cell line: 1>()
----> 1 'email' in user in User.__getitem__(self, item)
7 def __getitem__(self, item):
----> 8 return getattr(self, item)
TypeError: getattr(): attribute name must be string
Solution 1:[1]
What is happening is that you didn't define the correct hook. You want to implement the __contains__ method.
Because you didn't, the in operator switches to the fallback mode: iterating over the object as if it was a sequence, so it tries object[0],then object[1], etc. until it hits an IndexError or finds something that is equal to the value you were testing for. Hence, the exception, as item is 0.
Use hasattr instead of getattr, as you want a boolean result. And for your __getitem__, you want to make sure you turn AttributeError exceptions into KeyError exceptions, to keep the interface consistent:
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class User:
email: int
password: str
blocked_at: float = None
def __getitem__(self, item: str) -> str | int | float | None:
try:
return getattr(self, item)
except AttributeError:
raise KeyError(item) from None
def __contains__(self, item: str) -> bool:
return hasattr(self, item)
Demo:
>>> user = User('[email protected]', 'password')
>>> print(user['email'])
[email protected]
>>> 'email' in user
True
>>> user["nonesuch"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 12, in __getitem__
KeyError: 'nonesuch'
See the Python reference section on membership test operations for the details on how in will fall back to iteration:
For user-defined classes which do not define
__contains__()but do define__iter__(),x in yisTrueif some valuez, for which the expressionx is z or x == zis true, is produced while iterating overy. If an exception is raised during the iteration, it is as ifinraised that exception.Lastly, the old-style iteration protocol is tried: if a class defines
__getitem__(),x in yisTrueif and only if there is a non-negative integer indexisuch thatx is y[i] or x == y[i], and no lower integer index raises theIndexErrorexception. (If any other exception is raised, it is as if in raised that exception).
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
