Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MRO vs. inherited attributes #428

Closed
galcik opened this issue Aug 20, 2018 · 5 comments · Fixed by #635
Closed

MRO vs. inherited attributes #428

galcik opened this issue Aug 20, 2018 · 5 comments · Fixed by #635
Labels
Milestone

Comments

@galcik
Copy link

galcik commented Aug 20, 2018

The way how attributes are collected from super classes is different comparing to how attributes and methods are searched by getattr (based on MRO for new-style classes).

Example:

@attr.s
class A(object):

    x = attr.ib(10)

    def xx(self):
        return 10


@attr.s
class B(A):
    y = attr.ib(20)


@attr.s
class C(A):
    x = attr.ib(50)

    def xx(self):
        return 50


class D(B, C):
    pass


d = D()
print(d.x)  # prints 10
print(d.xx())  # prints 50

I think it would be great to the use the same approach for collecting attributes as used for searching methods and attributes.

The difference is caused by the fact that the function _transform_attrs in attrs/_make.py considers all attributes (own+inherited stored in __attrs_attrs__) and not only own attributes of super classes:

attrs/src/attr/_make.py

Lines 367 to 368 in 6a07b03

for super_cls in cls.__mro__[1:-1]:
sub_attrs = getattr(super_cls, "__attrs_attrs__", None)

@hynek hynek added the Bug label Sep 1, 2018
@hynek
Copy link
Member

hynek commented Sep 1, 2018

Hm that’s unfortunate and fixing it likely means breakage. :|

@hynek hynek added this to the import attrs milestone Oct 27, 2018
@hynek
Copy link
Member

hynek commented Aug 19, 2019

So this is interesting.

The underlying issue is that attrs always writes all methods optimized for the current class to avoid super() calls/traversal which means we have to simulate the __mro__ ourselves when building the lists of attributes. FTR, dataclasses handle it the same way.

As such, I was able to make your test case work by ignoring inherited attributes iff D is also decorated with @attr.s because then attrs has the chance to intervene.


There's another problem too: users expect the attributes to be ordered (in __init__ parameter lists and reprs for example) in the order they are defined. E.g. if you have:

        @attr.s
        class C(object):
            c = attr.ib(default=100)
            x = attr.ib(default=1)
            b = attr.ib(default=23)

        @attr.s
        class D(C):
            a = attr.ib(default=42)
            x = attr.ib(default=2)
            d = attr.ib(default=3.14)

The attribute order is c, b, a, x, d (N.B. that x changed position due to overwrite).

If I ignore inherited attributes and walk the mro, the order becomes a, x, d, c, b.

I'm open to suggestions how to resolve this to make everyone happy but I banged my head against it for over an hour and didn't really come up with anything viable. :(

I guess traversing it twice would work? Once for getting the order of the names right and then again to resolve them to the correct classes? 🤔

@hynek hynek modified the milestones: import attrs, 20.1.0 Mar 6, 2020
@hynek
Copy link
Member

hynek commented Mar 30, 2020

If you don't mind, have a look at #635 whether it covers everything you'd expect.

@hynek hynek closed this as completed in #635 Apr 6, 2020
@hynek
Copy link
Member

hynek commented Apr 6, 2020

I have merged it now, please let me know if something is not OK.

@jacobg

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants