Skip to content

Conversation

@ilevkivskyi
Copy link
Member

@ilevkivskyi ilevkivskyi commented Dec 5, 2017

This is a basic implementation of the PEP. Suggestions are welcome.

@gvanrossum @serhiy-storchaka Can one of you (or both?) please review?

https://bugs.python.org/issue32225

@ilevkivskyi ilevkivskyi changed the title [skip-issue] [skip-news] Implementation of PEP 562 [skip-news] bpo-32225: Implementation of PEP 562 Dec 5, 2017
@ilevkivskyi ilevkivskyi changed the title [skip-news] bpo-32225: Implementation of PEP 562 bpo-32225: Implementation of PEP 562 Dec 5, 2017
Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a What's New entry and document this feature in corresponding places.

}
return _PyObject_FastCall(getattr, stack, 1);
}
else if (PyErr_Occurred()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This never happen with _PyDict_GetItemId().

I prefer to use _PyDict_GetItemIdWithError() and don't silence errors, but this is a different large issue.

"module __getattr__ must be callable");
return NULL;
}
return _PyObject_FastCall(getattr, stack, 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please test the following case?

def __getattr__(name):
    if name != 'delgetattr':
        raise AttributeError
    del globals()['__getattr__']
    # do something more

I suspect that it can crash (but not sure).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for review! I think I addressed all the comments, but there is a little problem. This test that I added as test_module_getattr_tricky fails with AttributeError in refleak hunting mode -R 3:3 (it passes in normal mode). Presumably this is because the module stays in memory between sequential runs and __getattr__ is already deleted after the first test. How do I fix this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the test didn't crash then perhaps we shouldn't add it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to keep something like this just in case. I simplified the test (__getattr__ always raises AttributeError, but will also delete itself). This way it works also in refleak hunting mode.

result = NULL;
}
else {
result = _PyObject_FastCall(dirfunc, NULL, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_PyObject_CallNoArg()

if (PyDict_Check(dict)) {
PyObject *dirfunc = PyDict_GetItemString(dict, "__dir__");
if (dirfunc) {
if (!PyCallable_Check(dirfunc)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no a special check for the callability of a regular __dir__.

self.assertEqual(test, "There is test")
self.assertEqual(gga.x, 1)
self.assertEqual(gga.y, 2)
with self.assertRaises(AttributeError):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test the error message.

@serhiy-storchaka serhiy-storchaka added the type-feature A feature request or enhancement label Dec 5, 2017
@@ -0,0 +1,2 @@
PEP 562: Add support for module __getattr__ and __dir__. Implemented by Ivan
Levkivskyi.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Escape the underscores to make this valid reST:

module ``__getattr__`` and ``__dir__``

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fixed!

@ilevkivskyi
Copy link
Member Author

@serhiy-storchaka @merwok is something left to do here?

@csabella
Copy link
Contributor

@ilevkivskyi Nick made a comment on the ticket about adding documentation. @serhiy-storchaka and @merwok would need to let you know if anything else was needed.

_Py_IDENTIFIER(__getattr__);
getattr = _PyDict_GetItemId(m->md_dict, &PyId___getattr__);
if (getattr) {
if (!PyCallable_Check(getattr)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no a special check for callability of a regular __getattr__ (as well as other special methods).

>>> class A:
...     __getattr__ = "Surprise!"
... 
>>> A().x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>> A()[1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>> A() + 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable

Of course special checks can be added in all these cases. This would help recognition marginal mistakes. But this will increase the size of the code and can slow down it (because optimizing this code can worsen the optimization of the normal path) and decrease readability. I would defer this change to a separate issue if it is worth to do it at all.

@serhiy-storchaka
Copy link
Member

The documentation is required. @ncoghlan given a good suggestion.

@ilevkivskyi
Copy link
Member Author

OK, I added docs as proposed by @ncoghlan (I also mentioned __class__).

returned. :func:`dir` converts the returned sequence to a list and sorts it.


Customizing module attribute access
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the index entries:

.. index::
   single: __getattr__ (module attribute)
   single: __dir__ (module attribute)
   single: __class__ (module attribute)

computed value or raise an :exc:`AttributeError`. If an attribute is
not found on a module object through the normal lookup, i.e.
:meth:`object.__getattribute__`, then ``__getattr__`` is searched in
the module ``__dict__`` before raising an :exc:`AttributeError`. If found,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to make a reference to __dict__ as the module attribute (but not to the object attribute) if possible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if it is possible. The sub-section "Modules" itself refers to object.__dict__.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an index entry for __dict__ as the module attribute. But I don't know if it is possible to refer to the target of an index entry.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you can you use :data: directive. Here's the definition in the Documentation Guide:

data

    Describes global data in a module, including both variables and values used as
   “defined constants.” Class and object attributes are not documented using this directive.

Where the __dict__ is defined:

   .. index:: single: __dict__ (module attribute)
   .. data:: __dict__

   Special read-only attribute: :attr:`~object.__dict__` is the module's
   namespace as a dictionary object.

and then where you want to link:

:meth:`object.__getattribute__`, then ``__getattr__`` is searched in
the module :data:`__dict__` before raising an :exc:`AttributeError`.

As you mentioned, this may require fixing other links for module.__dict__ that now point to object.__dict__.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is applicable here. In any case we can experiment with this in a separate issue. I prefer to keep the current markup for now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case we can experiment with this in a separate issue. I prefer to keep the current markup for now.

I also think it can be deferred to a separate issue. IIUC the only thing remaining here is to get an OK from @ncoghlan

@serhiy-storchaka
Copy link
Member

And please document PEP 562 in the What's New document.

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. But perhaps @ncoghlan has other suggestions about the documentation.

Thanks, this is a nice improvement.

computed value or raise an :exc:`AttributeError`. If an attribute is
not found on a module object through the normal lookup, i.e.
:meth:`object.__getattribute__`, then ``__getattr__`` is searched in
the module ``__dict__`` before raising an :exc:`AttributeError`. If found,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an index entry for __dict__ as the module attribute. But I don't know if it is possible to refer to the target of an index entry.

@ncoghlan
Copy link
Contributor

ncoghlan commented Dec 14, 2017 via email

@ilevkivskyi
Copy link
Member Author

+1 to merging from me

OK, then I am going to merge this now.

@ilevkivskyi ilevkivskyi merged commit 5364b5c into python:master Dec 14, 2017
@ilevkivskyi ilevkivskyi deleted the module-getattr branch December 14, 2017 10:59
@gvanrossum
Copy link
Member

gvanrossum commented Dec 14, 2017 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type-feature A feature request or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants