Skip to content

Setattr The Silent Killer

Updated: at 01:17 PM

image

Today I learned about setattr in python

  • Nested attributes cannot be set by setattr -> This creates problems when working with pytorch and replacing model weights

Example:

def replace(model: nn.Module, new_layers: dict[str, nn.Linear]) -> nn.Module:
	for name, modules in model.named_modules():
		if name in new_layers:
			setattr(model, name, new_layers[name])

This code does not do what you think it should do. Why? Well, Python allows . in attribute names. Here is a recent thread discussing this

https://discuss.python.org/t/enhancing-getattr-to-support-nested-attribute-access-with-dotted-strings/74305/15

To illustrate the problem, consider the following code:

class Potato:
	size = 100
class Casserole:
	potato = Potato()
	broth = "Yum!"

	def __repr__(self):
		return f"Casserole(potato.size={self.potato.size}, broth={self.broth})"
>>> class Potato:
...     size = 100
... class Casserole:
...     potato = Potato()
...     broth = "Yum!"
...
>>> stew = Casserole()
>>> getattr(stew, "potato.size")
Traceback (most recent call last):
  File "<python-input-10>", line 1, in <module>
    getattr(stew, "potato.size")
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Casserole' object has no attribute 'potato.size'
>>> stew.potato.size
100

Clearly, this does not work. What’s the problem? We get an attribute error and get on with our coding lives and accept the fact that we have to do extra coding to get around getting deeply nested objects. For example, this code solves the problem (stolen solution from the link):

import functools
def deep_getattr(obj: Any, attr: str) -> Any:
	return functools.reduce(getattr, attr.split('.'), obj)

However, check this out:

>>> setattr(stew, "potato.size", 9001)
>>>

No exception. Nothing went wrong? Let’s inspect the object

>>> stew.potato.size
100
>>> setattr(stew, "potato.size", 9001)
>>> stew.__dict__
{'potato.size': 9001}
>>> stew.potato.size
100

That is a silent error and it happens because having . in attribute names is valid Python. This is, from a user-perspective of Python, not nice. It is confusing. But now I know and you do too!