failing example
import attr
def test_example():
@attr.s
class Base(object):
attr = attr.ib(default=False)
@attr.s
class Sub(Base):
needed = attr.ib()
This has been reported before and I’m not sure what to do about it. putting mandatory always before optionals? iow reordering?
they can always be given as keyword
the order should stay the same
it should be possible to give them as keyword instead of a positional argument
once inheritance is involved it seems sensible to go towards keywords in any case
basically all mandatory arguments after a optional one can be primary keyword based
btw, i do in fact use them as keywords, but putting in a default of None seems the wrong way
I kind of agree, but what’s better? having a sentinel and explode on it?
not quite sure what you mean by the sentinel
for the code in the example i posted i think that Sub(needed='foo') should work since all mandatory arguments where given
what you sketched it results in
def __init__(self, attr=False, needed):
pass
which is a syntax error.
So we need some default value for needed here.
i see what you mean, in python3 its *, and valid, for python2 there is need for a workaround in form of a sentinel object and exploding then
I accidentally opened up a duplicate of this.
My personal opinion™ is that we should just allow users to mark attributes as init='kwonly'. That will allow users to fix the problem themselves on Python 3 and consider upgrading on Python 2. 😽
Full example:
@attr.s
class A:
a = attr.ib()
b = attr.ib(init='kwonly', default=1)
# __init__ signature is:
def __init__(self, a, *, b=1):
pass
@attr.s
class B(A):
c = attr.ib()
# __init__ signature is:
def __init__(self, a, c, *, b=1):
pass
Also, in the case of the error we have now, a helpful error message can direct users to use kwonly.
I ran into this problem as well and I have a slightly different take on it that I wanted to submit. A more natural solution to me would be to write something like:
import attr
def test_example():
@attr.s
class Base(object):
attr = attr.ib(default=False)
@attr.s
class Sub(Base):
needed = attr.ib()
attr = attr.ib(default=False)
At the cost of a very minor repetition of code, this lets the programmer explicitly control the order in which the attributes should appear for representation purposes or when converting to a tuple. However what I wrote obviously doesn't work in the current version of attr: because of the way inheritance is implemented, Sub receives two 'attr' attributes, which happens to break the __init__ method among other things. I do have a bit of beef with that and was both curious about why attributes don't get overridden in subclasses, and willing to do something about it -then I saw this issue and thought that would be a good starting point for my comment.
how about this:
since this is starting to turn into a mirad of dozens and dozens of options i believe this should either not be supported for the sake of simplicity or be supported only by disabling positional constructor arguments in the complete inheritance tree
I tend to agree. I’m thinking about an option to @attr.s that changes the mode of __init__ from building a proper signature to def __init__(x=NOTHING, y=NOTHING, z=42, a=NOTHING) and so forth. Then it checks at the beginning if there’s a NOTHING in mandatory attributes. It’s slower but only for people who insist to subclass (I would add electro shocks if I could ;)).
Yea I run into this issue a lot when I have some common optional attributes that I want to share across many classes. e.g.
@attr.s()
class SmallOptions(object):
verbose = attr.ib(default=None)
other_option = attr.ib(default=None)
@attr.s()
class Main(SmallOptions):
required = attr.ib()
I'm kinda inspired by go's embedded struct types:
https://www.goinggo.net/2014/05/methods-interfaces-and-embedded-types.html
http://www.hydrogen18.com/blog/golang-embedding.html
which I'd like to replicate that same sort of composability here. Instead of inheriting from SmallOptions, I'd want class Main to 'embed' SmallOptions instance. Maybe it would look something like so.
@attr.s()
class Main(object):
required = attr.ib()
__attrs_embedded__ = {'options': SmallOptions}
with some __getattr__ generated functionality. There would be a SmallOptions instance embedded within Main instance that would be proxied, and can be accessed directly by Main().options as well.
or like this
@attr.s()
class Main(object):
required = attr.ib()
options = attr.embed(SmallOptions)
This is a closed issue, and I'm definitely out of my depth as I wade into this discussion, but I'm working (with @asford, fwiw) on a project using attrs, and am getting the "No mandatory attributes after..." error message. I'm at the moment stumped as to what the order of attributes is for my class and which ones have and which lack defaults/factory methods. It would be super useful if the exception printed more about the offending attributes. In my case, I want to know:
what is the first attribute that has a default that appears ahead of an attribute which lacks one
right now, the error message only tells me what attribute lacks a default.
Could you open a feature request please? Comments on closed bugs get inevitably lost.
For anyone stumbling on this issue and still unclear on how to implement a keyword-only attribute, here's the syntax:
@attr.s
class MyClass:
x = attr.ib(kw_only=True)
it is still unclear what the solution is in python 2, which is not yet deprecated
@hynek can you clarify why this issue is closed even though #411 only tackles python 3?
Because we didn't find a good way to implement it in Python 2 without introducing a forbidding amount of complexity.
While we're committed to maintain Python 2-compatibility as long as it's feasible, we reserve to not implement new features for it if they are either impossible or too painful to implement/maintain.
@hynek while I completely agree, I also think it would be a good idea to state exactly that in the documentation, and/or the error
it is very misleading as the ticket is closed, everything is "fixed", py2 is alive... it makes people think they can get away with a workaround in py2, yet this is not true
Can anyone tell me what is the recommended way now?
Either make the subclass kw_only=True or overwrite the attribute with the default in the subclass:
import attr
def test_example():
@attr.s
class Base(object):
a = attr.ib(default=False)
@attr.s
class Sub(Base):
a = attr.ib()
needed = attr.ib()
I think this should be working code:
@attr.s
class Base():
optional = attr.ib( default = False, kw_only = True )
_internal = attr.ib( default = None, init = False )
@attr.s
class Subclass( Base ):
required = attr.ib()
It isn't though, currently, because:
_internal (which is kw_only = False) can not follow optional (which is kw_only = True), even though _internal is init = False and wouldn't appear in an __init__() arg list anyway. (Is this a separate bug?)
required (which is kw_only = False) can not follow optional (which is kw_only = True). While I understand the design here, I think it is an imposition on developers. At the very least it would be nice to be able to opt-in to being able to this with something like:
@attr.s( group_kw_only_last_in_init = True )
class Subclass:
required = attr.ib()
(Obviously with a better option name than that. :wink:)
My Python-2-Compatible solution is just
def validate_not_none(obj, attr_obj, val):
assert val is not None, "Attribute '{}' needs to be defined (not None)".format(attr_obj.name)
@attr.s
class Base():
optional = attr.ib(default=False, kw_only=True)
_internal = attr.ib(default=None, init=False)
@attr.s
class Subclass(Base):
required = attr.ib(default=None, validator=validate_not_none)
s = Subclass() # <- Will fail validator
Most helpful comment
For anyone stumbling on this issue and still unclear on how to implement a keyword-only attribute, here's the syntax: