From 476eedf85b9074b2244fc59003e5ae7dbd73c4e7 Mon Sep 17 00:00:00 2001 From: NotAFile Date: Wed, 28 Mar 2018 17:48:15 +0200 Subject: [PATCH] Add hasattr function The hasattr() function on python2 is famously dangerous and a frequent source of problems. This backports the slighly less dangerous Python 3 version to Python2. --- documentation/index.rst | 13 +++++++++++++ six.py | 14 ++++++++++++++ test_six.py | 22 ++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/documentation/index.rst b/documentation/index.rst index d2d9d79a2..374cca8a3 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -473,6 +473,19 @@ string data in all Python versions. that returns the result of ``__unicode__()`` encoded with UTF-8. +Behavior changes +>>>>>>>>>>>>>>>> + +Six also provides shims for changes in behavior. + +.. function:: hasattr(obj, name) + + In Python 2, hasattr() catches all exceptions, while on Python 3 it only + catches `~py3:AttributeError`. On Python 3, this function is an alias to + `~py3:hasattr`. On Python 2, it implements the (more sensible) Python 3 + behaviour + + unittest assertions >>>>>>>>>>>>>>>>>>> diff --git a/six.py b/six.py index 8d9ac41a8..a2b67e468 100644 --- a/six.py +++ b/six.py @@ -925,6 +925,20 @@ def python_2_unicode_compatible(klass): return klass +if PY2: + def hasattr(obj, name): + """Return whether the object has an attribute with the given name. + + This is done by calling getattr(obj, name) and catching AttributeError.""" + try: + getattr(obj, name) + return True + except AttributeError: + return False +else: + hasattr = hasattr + + # Complete the moves implementation. # This code is at the end of this module to speed up module loading. # Turn this module into a package. diff --git a/test_six.py b/test_six.py index 980cdf3aa..5d239efaf 100644 --- a/test_six.py +++ b/test_six.py @@ -934,6 +934,28 @@ def __bytes__(self): assert getattr(six.moves.builtins, 'bytes', str)(my_test) == six.b("hello") +def test_hasattr(): + class MyTest(): + @property + def no_error(self): + pass + + @property + def div_error(self): + raise ZeroDivisionError + + @property + def attribute_error(self): + raise AttributeError + + my_test = MyTest() + assert six.hasattr(my_test, "no_error") + assert not six.hasattr(my_test, "doesnotexist") + assert not six.hasattr(my_test, "attribute_error") + with py.test.raises(ZeroDivisionError): + six.hasattr(my_test, "div_error") + + class EnsureTests: # grinning face emoji