attrs icon indicating copy to clipboard operation
attrs copied to clipboard

Erroneous duplicate argument error

Open ericfrederich opened this issue 6 years ago • 4 comments

this line makes it impossible to have a class like the following. I believe it should be possible.

@attr.s
class Foo:
    spam = attr.ib()
    eggs = attr.ib()
    _eggs = attr.ib()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/eric/.virtualenvs/example/lib/python3.5/site-packages/attr/_make.py", line 391, in attributes
    return wrap(maybe_cls)
  File "/home/eric/.virtualenvs/example/lib/python3.5/site-packages/attr/_make.py", line 364, in wrap
    cls = _add_init(cls, effectively_frozen)
  File "/home/eric/.virtualenvs/example/lib/python3.5/site-packages/attr/_make.py", line 569, in _add_init
    bytecode = compile(script, unique_filename, "exec")
  File "<attrs generated init 2b427e104daf8a0e2c385316a27a312cdcc1c6d1>", line 1
SyntaxError: duplicate argument 'eggs' in function definition

ericfrederich avatar Sep 08 '17 12:09 ericfrederich

i do wonder if there is ever an actual case where such a attrbute naming adds value, from my pov its quite reckless to have those as you show, i believe there should only be better error message

RonnyPfannschmidt avatar Sep 08 '17 14:09 RonnyPfannschmidt

I'll write up my use-case after lunch. But for now I can say it feels a bit weird having the __init__ names to be different from the class attribute names.

>>> import attr
>>> 
>>> 
>>> @attr.s
... class Foo:
...   spam = attr.ib()
...   _eggs = attr.ib()
... 
>>> 
>>> 
>>> Foo(spam='a', _eggs='b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument '_eggs'
>>> Foo(spam='a', eggs='b')
Foo(spam='a', _eggs='b')

ericfrederich avatar Sep 08 '17 15:09 ericfrederich

Okay... here us my use case (perhaps justified, perhaps not).

I'm extracting GitLab information using python-gitlab. In the end I need to serialize it to json in this format to import into JIRA. The basic idea is that I have a single attr.s object which I can serialize directly into the format needed by JIRA.

To accomplish this, I have two different kinds of attributes. Ones that will be serialized, and ones that won't. They are differentiated by whether they have a leading underscore or not.

# I serialize it like this...
d = attr.asdict(extract, filter=lambda a, v: not a.startswith('_'))
json.dump(d, fp, indent=2)  # produces exactly what will be fed into JIRA

So... that is the need for the underscore (to mark whether to serialize it or not).

Now you may be wondering about why have two fields with the same name?

Well, these attr.s objects are basically a 1:1 mapping to the underlying python-gitlab objects. Unfortunately the python-gitlab objects aren't as nice as attr.s classes... two objects representing the same thing don't equate... user1 == user2 will always be False. Because of this, I like to keep the original python-gitlab objects around. For example:

@attr.s
class Comment:
    author = attr.ib()
    message = attr.ib()

@attr.s
class Issue:
    comments = attr.ib()  # list of the above attr.s Comment objects
    _comments = attr.ib()  # list of underlying GitLab Comment objects

ericfrederich avatar Sep 08 '17 17:09 ericfrederich

YYMV, but I think it's a bad pattern to tightly couple the names of symbols in a class def to your serialization format. This probably won't be the only time that this apparent simplicity bites you.

In this case, you are competing with attrs over what the leading underbar convention means. I guess the most flexible thing attrs can do is give attr.ib yet another arg, for naming the argument to __init__, but it seems a lot simpler to just rename _comments in the above example.

wsanchez avatar Sep 11 '17 21:09 wsanchez