-
-
Notifications
You must be signed in to change notification settings - Fork 382
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
custom init method #393
Comments
I am not entirely opposed to add more control to |
I read the whole #68 thread in looking for hints on how to proceed with an attrs-styled VIMscript OO library. I like how you made the As for
|
Just to be clear, you’re making the case for
Oh sweet summer child. ;) |
Yep, I am for the attrs_init. |
And… are we settling on the mechanism for enabling this? I think I prefer option two, where The parameter to |
what does this add that cant be done by say a named ctor? |
@RonnyPfannschmidt: What a named constructor doesn't let you do is… change the behavior of the default constructor. I get the argument that named constructors are cool and may be generally preferable, but that's not always an option, and |
@wsanchez but its still not clear to me why its valuable to have that capability - in particular its not clear if its worth the complexity |
to elaborate more - my personal experience is that smart ctors have a tendency to eventually be too smart to sanely debug and/or refactor - im facing that issue now with pytest nodes for example |
Sure, but that's your experience and YYMV. Customization of |
I'm mostly with Ronny on this issue however I keep running into legit uses for this kind of behavior, especially if I need attrs classes to integrate with other stuff. So yeah, Ronny is 💯 right, but it looks like there's enough edge cases that would warrant the complexity. Although I'd totally have to see it first. BTW with Wilfredos suggestion this became related to #324 and #416 should be taken into account. I'm not positive we should make auto-detection opt-out since it would cause potential breakage. It might become opt-out in “Project import attrs” but I doubt anybody is gonna step up and implement it by then. 🤔 |
@hynek I want to start by saying, thanks for making such a useful library! That said, I find myself in the camp where I need the ability to call the super class's __init__ before setting attributes (in conjunction with Pytorch). I've read through #68 and it seems you all did consider enabling this usecase at one point, but it got lost in the fold. I'm sure you'll continue to get asked questions about supporting it. I came up with a workaround to the issue without needing to modify the library. It seems to support my usecase well and is reasonably flexible. I figured I would at least document it in this comment in case others want a similar workaround (or in case it inspires a solution that can be incorporated into attrs). Here's a minimal working example showing the approach in action: from attr import attrs, attrib
def attrs_wrapper(maybe_cls=None, super_init=None, **kwargs):
'''
Decorator that wraps attrs to allow calling a super class initializer. Note
that super_init must return the args and kwargs that should be passed to the
underlying __init__ that attrs automatically creates.
'''
def wrap(cls):
''' The internal wrapper method for the decorator. '''
new_cls = attrs(**kwargs)(cls)
if super_init is not None:
__init__ = getattr(new_cls, '__init__')
def __super_init__(self, *args, **kwargs):
'''
This is a version of __init__ that calls the user defined super
class initializer, which may consume some args and kwargs as
needed.
'''
args, kwargs = super_init(super(new_cls, self), *args, **kwargs)
__init__(self, *args, **kwargs)
setattr(new_cls, '__init__', __super_init__)
return new_cls
if maybe_cls is None:
return wrap
return wrap(maybe_cls)
# an example when called without arguments
@attrs_wrapper
class BaseClass(object):
''' A base class '''
foo = attrib(type=int, default 1)
def child_base_init(_super, *args, foo=1, **kwargs):
''' A method to call the initializer of child's super class '''
_super.__init__(foo=foo)
# return the args and kwargs not consumed by the base class
return args, kwargs
# an example with arguments
@attrs_wrapper(auto_attribs=True, super_init=child_base_init)
class ChildClass(BaseClass):
''' A child class '''
bar: int = 2
BaseClass() # BaseClass(foo=1)
BaseClass(2) # BaseClass(foo=2)
ChildClass() # ChildClass(foo=1, bar=2)
ChildClass(3) # ChildClass(foo=3, bar=2)
ChildClass(3, 4) # ChildClass(foo=3, bar=4)
ChildClass(bar=4) # ChildClass(foo=1, bar=4) |
I'm not entirely sure that this makes the case for For whatever reason, I really need |
I don’t think I’m comfortable for another ad-hoc solution like this. The repr thing was just very obvious and pragmatic. This one seems more like an candidate for pluggable method makers plus metadata. 🤔 |
I have found another instance where the idea of When you subclass Tensorflow models, it will intercept I wanted to implement auto-detection of |
@hynek This is likely a similar issue to the one I reported above using Pytorch. I wonder if my workaround would help for Tensorflow as well. |
Ohhh, maybe? But I suspect it wouldn't be very convincing to someone without any buy in so far. 🤔 But I'll link it, thanks for pointing it out! |
Also thanks for the kind words above, currently there seems to be some law of physics that whenever someone asks an attrs question on SO, someone will suggest to use dataclasses instead 🙄 It is left as an exercise to the reader to figure out how demotivating that is. |
@hynek I'm trying to do the opposite whenever I have an opportunity:) Lang lebe |
Hi all, To add food for thought, I have found the following behavior when using The behavior is different when subclassing directly (case import attr
class Mixin(object): # just a humble mixin providing orthogonal functionality
def __init__(self, *args, **kwargs):
self.param = []
super().__init__(*args, **kwargs)
# Inheritance case 1
@attr.s
class Data1(Mixin, object):
value: int = attr.ib(default=1)
# Inheritance case 2
@attr.s
class Base2(object):
value: int = attr.ib(default=1)
class Data2(Mixin, Base2): pass
# make instances
d1 = Data1()
d2 = Data2()
assert isinstance(d1, Mixin)
assert isinstance(d2, Mixin)
assert not hasattr(d1, "param")
assert hasattr(d2, "param") # < - extra whitespace for emphasis |
It does make sense though:
It is somewhat unintuitive, but that's not really |
I understand how the MRO differs in both cases. As a new user of I was expecting a call to super given it's the standard procedure in python. Also because I read this bit in the Multiple inheritance has the drawback that it is highly sensitive to the MRO, it is easy to get wrong and is often brittle. One of the safer use cases is to mix classes with orthogonal functionality and short disjoint MROs. In the example provided above, the pitfalls of the MRO can be safely avoided in most cases (one exception is My use case seems closely related to other use cases in this thread. I am working on a library to notify GUI widgets (Pyside2) whenever an attribute changes. This accomplishes the very important task of keeping multiple independent widgets in sync with the underlying data, cutting on the boilerplate code, and without forcing the user to rewrite their entire codebase when they decide they want a GUI for their task (go read the docs about Qt Model/View, I'll wait...). To minimize potential conflicts, I add a single attribute, a single method, and intercepting calls to Finally, here is one more example to ponder: import attr
class Mixin(object): # test mixin please ignore
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.param = []
@attr.s(auto_attribs=True)
class Data1(Mixin):
value: int = 1
class Data2(Mixin):
value: int = 1
d1 = Data1()
d2 = Data2()
assert not hasattr(d1, "param") # I think that qualifies as a surprise!
assert hasattr(d2, "param") |
From
attrs
docs:attrs
inject init method directly to class and provide only__attrs_post_init__
hook for customization, so we can't use method overriding (inheritance), this behavior make customization of__init__
method very limited.attrs
must be able to inject__init__
method with different name, as an example:So we can add custom logic before or after
attrs
init.There are two options how this behavior can be added
1 - Add new
attrs
boolean parameter i.e.attrs_init
orcustom_init
, if set toTrue
,attrs
will inject__attrs_init__
instead of default__init__
method.2 - inject
__attrs_init__
automatically if class has__init__
method.This feature will resolve:
#342
#354
The text was updated successfully, but these errors were encountered: