python / cpython

The Python programming language
https://www.python.org
Other
62.35k stars 29.94k forks source link

__getattr__ and metaclasses #39069

Closed 68a5ca3f-1963-4ae1-ba74-7b18edd83493 closed 18 years ago

68a5ca3f-1963-4ae1-ba74-7b18edd83493 commented 21 years ago
BPO 789262
Nosy @mwhudson, @birkenfeld

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields: ```python assignee = None closed_at = created_at = labels = ['interpreter-core'] title = '__getattr__ and metaclasses' updated_at = user = 'https://bugs.python.org/grodr' ``` bugs.python.org fields: ```python activity = actor = 'georg.brandl' assignee = 'none' closed = True closed_date = None closer = None components = ['Interpreter Core'] creation = creator = 'grodr' dependencies = [] files = [] hgrepos = [] issue_num = 789262 keywords = [] message_count = 4.0 messages = ['17782', '17783', '17784', '17785'] nosy_count = 4.0 nosy_names = ['mwh', 'georg.brandl', 'michele_s', 'grodr'] pr_nums = [] priority = 'normal' resolution = 'duplicate' stage = None status = 'closed' superseder = None type = None url = 'https://bugs.python.org/issue789262' versions = [] ```

68a5ca3f-1963-4ae1-ba74-7b18edd83493 commented 21 years ago

This came out from a thread in comp.lang.python: Here’s the reference:

http://groups.google.pt/groups?hl=pt-PT&lr=&ie=UTF- 8&threadm=md94jvccu02b9dv5890k34629rkot79roj% 404ax.com&rnum=6&prev=/groups%3Fq%3Dgon%25C3% 25A7alo%2Brodrigues%2Bgroup:comp.lang.python.*% 26hl%3Dpt-PT%26lr%3D%26ie%3DUTF-8%26group% 3Dcomp.lang.python.*%26selm% 3Dmd94jvccu02b9dv5890k34629rkot79roj% 25404ax.com%26rnum%3D6

Consider the following example (provided by M. Simionato)

>>> class M(type):
...     def __getattr__(self, name):
...         if name == '__iter__':
...             return lambda self: iter([])
...
>>> class C(object):
...     __metaclass__ = M
...     
>>> C.__iter__
<function <lambda> at 0x0110E8F0>
>>> c = C()
>>> iter(c)
Traceback (most recent call last):
  File "<interactive input>", line 1, in ?
TypeError: iteration over non-sequence
>>>

This means that the iterator machinery does not check __getattr (or __getattribute for that matter – I have made the test). The Language Reference says:

“A class can implement certain operations that are invoked by special syntax (such as arithmetic operations or subscripting and slicing) by defining methods with special names.”

Which does not throw much light on the matter at hand. So there are two ways we can view the above:

(A) It’s a bug.

This is the one I favour ;-). Arguing by contradiction, not being considered a bug means that there is a very special distinction being made when it comes to attribute lookup of special names.

I tend to follow the Gang of Four’s main injunction “Prefer composition to inheritance”. Composition is great in Python precisely because of the __getattr hook. Not being able to use __getattr in metaclasses to trap special names surely hampers that role somewhat.

(B) It’s not a bug.

Then at least I think that the documentation should be worded more accurately. Quoting A. Martelli on the same thread

getattr is not a BINDING of the special method, though it may be considered a DEFINITION of it, which is why the current phrase in the Language Reference is not 100% correct and complete -- only 99.44%, and I agree that the remaining 0.56% _is_ a delicate defect in the documentation.”

With my best regards, G. Rodrigues

f37a96ae-2d9b-4301-ba9e-cd405e5f425c commented 21 years ago

Logged In: YES user_id=583457

Two comments:

  1. The credit for discovering this "issue" goes to Bjorn Pettersen, not to me.

  2. The issue is NOT with metaclasses, the title should be "special methods and __getattr__"

The metaclass works perfectly fine and

X.__iter__(x)

works. The problem is with the "iter" builtin, since

iter(x) DOES NOT call X.__iter__(x).

Same with str, len, etc.

Metaclasses are not guilty here!

Michele

mwhudson commented 20 years ago

Logged In: YES user_id=6656

Metaclasses are not guilty here!

It's more complicated than that.

iter(o) does (roughly)

o->ob_type->tp_iter(o)

At class definition time, if the class defines __iter__, a wrapper for it is stuffed into the type's tp_iter slot. If it doesn't, NULL is placed there instead.

What *could be done is, if the *meta\class defines __getattr or __getattribute, all the tp_ slots could be filled with a special wrapper that calls the generic attribute getter. But that would be quite a coding effort, and these classes would have pretty atrocious performance.

And making this work when you assign to __getattribute__ on the metaclass would be a truly crazy piece of code.

Or the docs could note this limitation.

birkenfeld commented 18 years ago

Logged In: YES user_id=1188172

Duplicate of bpo-729913.