9 ms·
Python hasattr() Considered Harmful
- SomeGermanGuy 11y agoIsn't it either way best practise to better be sorry than to be safe? So the second approach is as far as I can see favoured by the community, because it declutters the code and keeps it simple.
- piqufoh 11y agoI think it's usually better to be safe than sorry. If the community favours the second approach it's probably because they're unaware of the side-effects (hence this article) or because they're writing Python 3 - as they should be.
- kqr 11y agoThe principle the above commenter refers to is known as "Easier to Ask Forgiveness than Permission", i.e. you're not meant to check preconditions – you should just try to do whatever thing you want to do, and it should signal an error if it didn't succeed. 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.
- masklinn 11y agoDon't forget the part where checking for preconditions opens you up to race conditions e.g. 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
- bbradley406 11y agoIs there still a chance of a race condition when you use the "with" operator? It seems like this is one of the major use cases it was designed for
- kqr 11y agoIt depends on how you use it! If you use it to acquire the resource, as in 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.
- Sir_Cmpwn 11y agoThe opening line: >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".
- the_mitsuhiko 11y agoIt's a stupid function either way. As it invokes a potentially expensive descriptor just to throw away the result. Silent getattr and check is always more appropriate.
- andrea_s 11y agoWrecking any hope of retrocompatibility considered harmful (who could have known?)
- joslin01 11y agoYou should so it can give us a reason to discuss Python and how it's probably not going anywhere with its crazy versioning and how 2020 will probably never happen and people will still be using Python 2 and Guido will have to fly from the sky and hit us every time we use Python 2 in order for us to upgrade. I love those comment threads.
- deleted 11y ago[deleted]
- Grue3 11y ago>At this point, you should be writing Python 3-only code. 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.
- rbanffy 11y agoThere will be a time every civilized OS will default to Python 3. In fact, the next Ubuntu does so. For us, that time is next March. There is less and less reason to write code in Python 2. Not many libraries require it.
- deleted 11y ago[deleted]
- gbog 11y agoPeople usually don't believe these advices until they get bitten. I'm all the same, and got bitten: a hasattr was hiding a deeper error and it is a nightmare to debug, one couldn't even know where to start.
- tomp 11y agoEverything can be abused. But most things are not, most of the time.
- hynek 11y agoIt’s not about abuse, it’s about behavior/consequences most people don’t realize.
- voidz 11y agoIt's raining "Considered Harmful" stories. Come on. Be a little bit more creative with titles, please!
- hynek 11y agoTBH it was just a quick write up to point ppl to in discussion as said in the intro. I never spent much time to ponder the attractiveness/SEOability etc.
- DyslexicAtheist 11y agothe point is it's not "considered" harmful <-- emphasis on quotes around considered but not on harmful because nobody "considers" it. Some might have a strong use-case for using hasattr(). The same applied to goto (and still does).
- hynek 11y agoThere is absolutely nothing hasattr does that you cannot achieve with other means in a more robust way and with minimal impact. It’s not like ditching goto that requires a completely different approach to programming.
- DyslexicAtheist 11y agoalso the downvote-fairies on here today should consider reading https://news.ycombinator.com/item?id=9744916 https://news.ycombinator.com/item?id=9744916
- hynek 11y agoLook, nobody is defending the title. I’ve even apologized for it here and in the article and explained how it happened. But if you insist on keeping to beat the dead horse tells more about you than the title.
- mSparks 11y agowow, what a horrible language.
- vesinisa 11y agoWhat about just agreeing that property getters should not throw? C# for example has similar code-defined properties, but the agreed and recommended practice is that property getters should never throw.
- orf 11y agoWe could all agree never to have bugs in our code while we are at it as well.
- alkonaut 11y agoThat isn't quite the same thing now is it. There are always cases that may cause a getter to throw (Unpredictable things like OutOfMemory or IO errors), but the point is that they shouldn't be designed to throw. E.g. they shouldn't have "throw new InvalidOperationException("You must connect to the DB before fetching the Data")" exceptions thrown.
- njharman 11y agoyeah, but we don't double check the calling/return of every builtin and we don't double check our double check, recurse. 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".
- elthran 11y ago“Considered Harmful” Essays Considered Harmful http://meyerweb.com/eric/comment/chech.html http://meyerweb.com/eric/comment/chech.html
- matthewmacleod 11y agoPointless, pedantic, patronising comments considered harmful…
- DyslexicAtheist 11y agoIt is silly IMO - those people using such click-baity titles for everything show a lack of creativity and really ought to try harder. "considered harmful" was already annoying when Dijkstra coined the term (and for very good reason).
- annnnd 11y agoIsn't this simply a bug? Why would hasattr() catch all exceptions instead of just AttributeError? IMHO a better title would be: "Python2 hasattr() has a bug".
- ggreer 11y agoNot a bug, just Python 3 choosing different trade-offs. getattr() and hasattr() are useful for replacing annoying boilerplate like: 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.
- alkonaut 11y agoI prefer the Python2 solution actually. If we disregard for a moment the madness of "hasattr" actually invoking the attribute code -- if it does invoke it and an error occurs, hasn't that proven that the attribute does in fact exist? 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.
- annnnd 11y agoMaybe i wasn't clear enough, I think the bug is this line: 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:
- jjgreen 11y agoI've previously used hasattr() to check for the existence of an attribute qua instance variable to lazy-load and cache that attribute 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?
- jimmytucson 11y ago> ruby's Perl has //= (http://perldoc.perl.org/perlop.html#Assignment-Operators http://perldoc.perl.org/perlop.html#Assignment-Operators)
- dietrichepp 11y agoWell, you can do it this 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.
- scrollaway 11y agoIn this particular case you should initialize self._kdtree to None in __init__, and just check if self._kdtree is None: <calculate>
- maweki 11y agoThis shows that get __getattr__-code is executed even on lookup. If your __getattr__ or your property-getter have side-effects, then even the lookup will trigger them. Then even a bare statement like foo.bar can have side-effects in the foo-object.
- magoon 11y agoI am surprised to see a recommendation to use an exception handler for conditionals, especially for Python.
- lumpypua 11y agoEAFP is in the Python coding glossary: 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. http://stackoverflow.com/a/11360880/36092 http://stackoverflow.com/a/11360880/36092
- flooo 11y agoThis style using try/except instead of if/else is known as EAFP (Easier to Ask Forgiveness than Permission). It's actually recommended in the Python glossary [0]. [0] https://docs.python.org/3.5/glossary.html#term-eafp https://docs.python.org/3.5/glossary.html#term-eafp
- collyw 11y agoI though this style was encouraged in Python.
- alkonaut 11y ago> For what’s it worth, Python 3 gets it right: > ... > Also, would you expect hasattr() to raise that error? 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.
- icebraining 11y agoThing is, what does it mean for a property to "exist" in a dynamic language like Python? E.g. class P(object): def __getattr_(self, attrname): return "hello" >>> p = P() >>> print p.foo hello Does "p" have an attribute "foo"?
- alkonaut 11y agoThat is the snag isn't it. It's really difficult to provide any meaningful "reflection" without having proper static types. So in your example you could say "yes, because it gets a value when called" or "no, because it isn't explicitly declared". Since python is dynamic, the "hasattr" method really just means "does_invoking_this_attribute_return_something", which is very much not reflection. 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.
- aladagemre 11y agoRegarding Python3 sample at the end, 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.
- icebraining 11y agoThe reality is that almost any interaction with an object can cause it to mutate itself, since Python allows classes to override just about any operator. For example: "a > 3", "5 in a", "x = a.b", "len(a)", "print a", etc - all of these can cause a to mutate. In the end, you have to trust the code to do the Right Thing, or just not use it.
- true_religion 11y ago> hasattr() also is not faster than getattr() since it goes through the exactly same lookup process and then throws away the result 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.