Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog.d/298.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The traversal of of MROs when using multiple inheritance was backward:
If you defined a class ``C`` that subclasses ``A`` and ``B`` like ``C(A, B)``, ``attrs`` would have collected the attributes from ``B`` *before* those of ``A``.

This is fixed now however due to its nature, it may require fixes on your side too unfortunately.
4 changes: 4 additions & 0 deletions changelog.d/299.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The traversal of of MROs when using multiple inheritance was backward:
If you defined a class ``C`` that subclasses ``A`` and ``B`` like ``C(A, B)``, ``attrs`` would have collected the attributes from ``B`` *before* those of ``A``.

This is fixed now however due to its nature, it may require fixes on your side too unfortunately.
4 changes: 4 additions & 0 deletions changelog.d/304.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
The traversal of of MROs when using multiple inheritance was backward:
If you defined a class ``C`` that subclasses ``A`` and ``B`` like ``C(A, B)``, ``attrs`` would have collected the attributes from ``B`` *before* those of ``A``.

This is fixed now however due to its nature, it may require fixes on your side too unfortunately.
2 changes: 1 addition & 1 deletion docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ This is useful in times when you want to enhance classes that are not yours (nic
... class B(object):
... b = attr.ib()
>>> @attr.s
... class C(B, A):
... class C(A, B):
... c = attr.ib()
>>> i = C(1, 2, 3)
>>> i
Expand Down
28 changes: 8 additions & 20 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def _transform_attrs(cls, these, auto_attribs):
if isinstance(attr, _CountingAttr)
), key=lambda e: e[1].counter)

non_super_attrs = [
own_attrs = [
Attribute.from_counting_attr(
name=attr_name,
ca=ca,
Expand All @@ -274,34 +274,22 @@ def _transform_attrs(cls, these, auto_attribs):
in ca_list
]

# Walk *down* the MRO for attributes. While doing so, we collect the names
# of attributes we've seen in `take_attr_names` and ignore their
# redefinitions deeper in the hierarchy.
super_attrs = []
taken_attr_names = {a.name: a for a in non_super_attrs}
taken_attr_names = {a.name: a for a in own_attrs}

# Traverse the MRO and collect attributes.
for super_cls in cls.__mro__[1:-1]:
sub_attrs = getattr(super_cls, "__attrs_attrs__", None)
if sub_attrs is not None:
# We iterate over sub_attrs backwards so we can reverse the whole
# list in the end and get all attributes in the order they have
# been defined.
for a in reversed(sub_attrs):
for a in sub_attrs:
prev_a = taken_attr_names.get(a.name)
# Only add an attribute if it hasn't been defined before. This
# allows for overwriting attribute definitions by subclassing.
if prev_a is None:
super_attrs.append(a)
taken_attr_names[a.name] = a
elif prev_a == a:
# This happens thru multiple inheritance. We don't want
# to favor attributes that are further down in the tree
# so we move them to the back.
super_attrs.remove(a)
super_attrs.append(a)

# Now reverse the list, such that the attributes are sorted by *descending*
# age. IOW: the oldest attribute definition is at the head of the list.
super_attrs.reverse()

attr_names = [a.name for a in super_attrs + non_super_attrs]
attr_names = [a.name for a in super_attrs + own_attrs]

AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ class D(A):
d2 = attr.ib(default="d2")

@attr.s
class E(D, C):
class E(C, D):
e1 = attr.ib(default="e1")
e2 = attr.ib(default="e2")

Expand Down