diff --git a/py/builtin.c b/py/builtin.c
index 8d5779e42c6fdd572adcc82b469628aaf271a989..7f0d2a4d9f73941f812bc41299ffc919611cb79a 100644
--- a/py/builtin.c
+++ b/py/builtin.c
@@ -462,6 +462,21 @@ STATIC mp_obj_t mp_builtin_getattr(uint n_args, const mp_obj_t *args) {
 
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_getattr_obj, 2, 3, mp_builtin_getattr);
 
+STATIC mp_obj_t mp_builtin_hasattr(mp_obj_t object_in, mp_obj_t attr_in) {
+    assert(MP_OBJ_IS_QSTR(attr_in));
+
+    mp_obj_t dest[2];
+    // TODO: https://docs.python.org/3.3/library/functions.html?highlight=hasattr#hasattr
+    // explicitly says "This is implemented by calling getattr(object, name) and seeing
+    // whether it raises an AttributeError or not.", so we should explicitly wrap this
+    // in nlr_push and handle exception.
+    mp_load_method_maybe(object_in, MP_OBJ_QSTR_VALUE(attr_in), dest);
+
+    return MP_BOOL(dest[0] != MP_OBJ_NULL);
+}
+
+MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_hasattr_obj, mp_builtin_hasattr);
+
 // These two are defined in terms of MicroPython API functions right away
 MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_globals_obj, mp_globals_get);
 MP_DEFINE_CONST_FUN_OBJ_0(mp_builtin_locals_obj, mp_locals_get);
diff --git a/py/builtin.h b/py/builtin.h
index baf444a208dd7cb659b022b5fc2748517d80b476..2929c1018a0f19b01da32ddaa4a188ba89912fa2 100644
--- a/py/builtin.h
+++ b/py/builtin.h
@@ -41,6 +41,7 @@ MP_DECLARE_CONST_FUN_OBJ(mp_builtin_eval_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_exec_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_getattr_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_globals_obj);
+MP_DECLARE_CONST_FUN_OBJ(mp_builtin_hasattr_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_hash_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_hex_obj);
 MP_DECLARE_CONST_FUN_OBJ(mp_builtin_id_obj);
diff --git a/py/builtintables.c b/py/builtintables.c
index a1888a6e971a9725d9516f1ab844c7323a1f1ea3..133c14a26900ec9bd078c2e510ff8b138136d4b7 100644
--- a/py/builtintables.c
+++ b/py/builtintables.c
@@ -90,6 +90,7 @@ STATIC const mp_map_elem_t mp_builtin_object_table[] = {
     { MP_OBJ_NEW_QSTR(MP_QSTR_exec), (mp_obj_t)&mp_builtin_exec_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_getattr), (mp_obj_t)&mp_builtin_getattr_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_globals), (mp_obj_t)&mp_builtin_globals_obj },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_hasattr), (mp_obj_t)&mp_builtin_hasattr_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_hash), (mp_obj_t)&mp_builtin_hash_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_hex), (mp_obj_t)&mp_builtin_hex_obj },
     { MP_OBJ_NEW_QSTR(MP_QSTR_id), (mp_obj_t)&mp_builtin_id_obj },
diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index 13476b3be80062dfb1e6c3396dc1398ac9d34ad6..1679d8b39c3bf87ff60a16dad4b306afc05639c8 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -142,6 +142,7 @@ Q(float)
 Q(from_bytes)
 Q(getattr)
 Q(globals)
+Q(hasattr)
 Q(hash)
 Q(hex)
 Q(%#x)
diff --git a/tests/basics/hasattr1.py b/tests/basics/hasattr1.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1c4b5ceb6180c5dd66b146e0689ce9152ba4e42
--- /dev/null
+++ b/tests/basics/hasattr1.py
@@ -0,0 +1,29 @@
+class A:
+
+    var = 132
+
+    def __init__(self):
+        self.var2 = 34
+
+    def meth(self, i):
+        return 42 + i
+
+
+a = A()
+print(hasattr(a, "var"))
+print(hasattr(a, "var2"))
+print(hasattr(a, "meth"))
+print(hasattr(a, "_none_such"))
+print(hasattr(list, "foo"))
+
+class C:
+
+    def __getattr__(self, attr):
+        if attr == "exists":
+            return attr
+        raise AttributeError
+
+c = C()
+print(hasattr(c, "exists"))
+# TODO
+#print(hasattr(c, "doesnt_exist"))