Using dot chaining is tedious if it’s necessary to apply the same procedure to all the properties in a class. If there are 20 properties in the class, the code length becomes at least 20. It will be hell if the number of properties is 50 or 100. I hope no one creates such a class though…
Access an attribute with string variable via getattr
Firstly, let’s define a class. Product class has 4 properties.
class Product:
def __init__(self):
self.count: int = 0
self.name: str = ""
self.price: float = 0
self.remark: str = ""
The attributes can be accessed in 3 ways.
item.count = 11
# Unnecessarily calls dunder method __getattribute__. Access attribute directly or use getattr built-in function.
print(item.__getattribute__("count")) # 11
print(getattr(item, "count")) # 11
print(item.count) # 11
__getattribute__
is usually not used. Pylint shows an error.
The attributes can be read by using dot chaining item.count
but getattr
function can also be used to read it. It requires a string at the second parameter, and thus, we can pass a string variable there to read the data.
If the attribute doesn’t exist in the class, AttributeError
is thrown.
# AttributeError: 'Product' object has no attribute 'not_exist_prop'
print(getattr(item, "not_exist_prop"))
Check if an object has the desired attribute by hasattr
We need to check if the object has the desired attribute before the actual reading if it’s not good to throw an Error. We can check it by hasattr
.
print(hasattr(item, "not_exist_prop")) # False
print(hasattr(item, "count")) # True
Add a new property if it doesn’t exist in an object via setattr
If getattr
and hasattr
exist, setattr
should also exist. Yes. It exists.
item.count = 100
print(item.count) # 100
item.__setattr__("count", 66)
print(item.count) # 66
setattr(item, "count", 12)
print(item.count) # 12
Why is it __setattr__
while it is __getattribute__
for get…? It’s not consistent. It’s bad.
setattr
requires three parameters. Object, key name, and value.
The property is newly added if it doesn’t exist in the object.
setattr(item, "unknown_prop", "this is value")
# Instance of 'Product' has no 'unknown_prop' member
print(item.unknown_prop) # this is value
print(getattr(item, "unknown_prop")) # this is value
IntelliSense shows an error that the property name doesn’t exist but actually exists at this point. Use getattr
function to read the data in this case.
When should we use xxxattr function?
Let’s see an example of to use xxxattr
functions. I defined the following list but assume that you download or fetch the data from somewhere. The data is JSON file and we parse it into the following object.
product_list: list[dict[str, Any]] = [
{"count": 3, "name": "Item 1", "price": 55},
{},
{"count": 56, "name": "Item 2", "remark": "Remark for item 2"},
{"name": "Item 3", "price": 10, "remark": "No stock"},
]
Now, we want to assign the value only for the items that exist in the object.
result: list[Product] = []
for item in product_list:
new_item = Product()
count = item.pop("count", None)
if count is not None:
new_item.count = count
name = item.pop("name", None)
if name is not None:
new_item.name = name
price = item.pop("price", None)
if price is not None:
new_item.price = price
remark = item.pop("remark", None)
if remark is not None:
new_item.remark = remark
result.append(new_item)
for item in result:
print(
f"name: {item.name}, count: {item.count}, price: {item.price}, remark: {item.remark}"
)
# name: Item 1, count: 3, price: 55, remark:
# name: , count: 0, price: 0, remark:
# name: Item 2, count: 56, price: 0, remark: Remark for item 2
# name: Item 3, count: 0, price: 10, remark: No stock
The same thing is repeated. If using dot chaining, it’s not possible to refactor this code.
By using setattr
, we can refactor the code in the following way.
result: list[Product] = []
for item in product_list:
new_item = Product()
for key in ["count", "name", "price", "remark"]:
value = item.pop(key, None)
if value is not None:
setattr(new_item, key, value)
result.append(new_item)
for item in result:
print(
f"name: {item.name}, count: {item.count}, price: {item.price}, remark: {item.remark}"
)
# name: Item 1, count: 3, price: 55, remark:
# name: , count: 0, price: 0, remark:
# name: Item 2, count: 56, price: 0, remark: Remark for item 2
# name: Item 3, count: 0, price: 10, remark: No stock
It’s easy to extend the number of properties. What we need to do is only to add new properties to the property list used in for loop.
The following post is also a good example to learn how to use it.
Comments