Python hasattr() Considered Harmful
hynek.me
hynek.me
>Don’t use Python’s hasattr() unless you want its quirks or are writing Python 3-only code.
At this point, you should be writing Python 3-only code. Maybe I should write a post, "Python 2 considered harmful".
I love those comment threads.
At this point it should be obvious that Python 2 will never die. It's Good Enough (TM). Most users will never switch to Py3 because cost outweighs the benefits.
There is less and less reason to write code in Python 2. Not many libraries require it.
Not really no. I would expect hasattr() to just answer whether the attribute exists, and getattr() to actually invoke it. If "hasattr" actually invokes the attribute and just checks for a non-empty value returned, is it even needed? It would completely surprise me, coming from a language with static types + reflection, that a query for a types attributes (mere existence) would invoke them.
class P(object):
def __getattr_(self, attrname):
return "hello"
>>> p = P()
>>> print p.foo
hello
Does "p" have an attribute "foo"?It would maybe be useful with getting only the explicitly defined attributes (say e.g. if you wanted to convert an object to an xsd schema). Your __getattr__ based attribute would not show up in that list obviously.
try:
foo = x.y
except Exception:
foo = None
If getattr() and hasattr() can raise, then you have to wrap them in try/except blocks. So much for that previously useful shorthand.There's no good solution here. Either hasattr() can raise (confusing), or it can return false for attributes that exist (also confusing). Maybe if the name was changed (validattr()?), everyone would be OK with the Python 2 behavior.
Picture a type that has a "t.connect()" function, and a "t.data" attribute. The data attribute will fail if connect() hasn't been called prior to accessing it.
I'd expect hasattr(t, "data") to answer true and never raise an error, regardless of when I do it. The only way to do that (if you have to invoke the code in the attribute -- not sure if that is the case) is to catch and return true.
Validattr would have been a much better name, yes.
except Exception:
Why would you want to catch all exceptions? ideally you should catch just those that are applicable to your case and ignore others: except AttributeError: def kdtree(self):
if not hasattr(self, '_kdtree'):
self._kdtree = scipy.spatial.KDTree(self.xi.T)
return self._kdtree
like ruby's @foo ||= expensive_calculation
is there a better way? def kdtree(self):
try:
return self._kdtree
except AttributeError:
pass
self._kdtree = scipy.spatial.KDTree(self.xi.T)
return self._kdtree
And, of course, you could do all of that with a decorator.Perl has //= (http://perldoc.perl.org/perlop.html#Assignment-Operators)
Easier to ask for forgiveness than permission. This common Python coding style assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false. This clean and fast style is characterized by the presence of many try and except statements.
In practice this only matters if you have a property. If you use hasattr to find a method within an object, it will not invoke the method while checking for existence.
There can be bugs in the OS, or network driver. Life happens.
At some point you have to assume something works bug free. A common and reasonable point for that is "I assume language and builtins to actually work as documented".
The reason for this has little to do with "clean code" and more to do with the fact that when checking preconditions, programmers have a tendency to check proxies rather than the actual thing they want to do. An an example, it is common to see the following:
if not file_exists(filepath):
stderr("Invalid config filepath!")
config = read_file(filepath)
...
This code has the glaring problem that even if the file exists, that doesn't mean you have the required permissions to read it. What you really want to do is read the config file, but you check a proxy (does the file exist at all?) as your precondition.How would you check for the right thing, that you can read the config file? Well, by trying to read the config file, of course!
try:
config = read_file(filepath)
except IOError: # can make this more fine-grained
stderr("Invalid config filepath!")
This is longer code (one extra line, gasp!) but it avoids the problem of checking the wrong precondition which is so easy to do by mistake.You may have heard of this concept in a different situation – e-mail validation. A lot of people go to great lengths to validate e-mail addresses by regex. Even if the address is perfectly conforming and passes the regex with flying colours it might not lead to an inbox. To really verify that the user can receive e-mail at the address they've specified, guess what you have to do? Send an e-mail and ask them to confirm they got it. It's the only way.
if not file_exists(filepath):
stderr("invalid config filepath!")
# filepath is removed by an external tool
config = read_file(filepath) # blows up file does not exist with open(fn, 'r'):
...
then generally the answer is no, since that is the same thing as the equivalent `try…finally`. If someone removes the file after it's being opened it will not be completely wiped until it's closed.Then even a bare statement like foo.bar can have side-effects in the foo-object.
What if the method y is mutating the object? Then when I just check for existence of y with hasattr, it will mutate the object. I wouldn't want that.