-
-
Notifications
You must be signed in to change notification settings - Fork 373
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
[RFC] Implement immutability #60
Conversation
Current coverage is 100% (diff: 100%)@@ master #60 diff @@
===================================
Files 7 8 +1
Lines 362 384 +22
Methods 0 0
Messages 0 0
Branches 86 89 +3
===================================
+ Hits 362 384 +22
Misses 0 0
Partials 0 0
|
Cool :) Some quick opinions:
You might not like this, but I'd also consider defaulting I think it'd be valuable for |
|
|
||
.. doctest:: | ||
|
||
>>> @attr.s(frozen=True) |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Maybe we can have our cake and eat it too? Leave
|
@Tinche I think if we're going to do that, we don't need to discuss it here - this PR stands alone, and would be necessary for that naming strategy anyway :) |
from __future__ import absolute_import, division, print_function | ||
|
||
|
||
class FrozenInstanceError(AttributeError): |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
@glyph If we're going to do that, I'm 👍 on this. |
I'm assuming it'd be a compatibility break, and thus a no-no, to make Factory and the validators frozen and slotted? |
Factory was always an opaque object so slotted shouldn’t be a problem. But frozen probably has a runtime impact so unless you can disprove that using benchmarks, I’m not making attrs slower for rather esoteric reasons. |
Hm, testing on my work Mac, CPython 3.5:
So it would appear there is a slight performance penalty to frozen, but a performance benefit to slots, at least on CPython. We're talking hundreds of nanoseconds, however. Maybe we could optimize the
Unfortunately, the perf tool doesn't work on PyPy yet (psf/pyperf#12) so this is all the data I have right now. |
I’ve tried caching too like this: diff --git a/src/attr/_make.py b/src/attr/_make.py
index b381b09..9a37b10 100644
--- a/src/attr/_make.py
+++ b/src/attr/_make.py
@@ -398,10 +398,17 @@ def _add_init(cls, frozen):
locs = {}
bytecode = compile(script, unique_filename, "exec")
attr_dict = dict((a.name, a) for a in attrs)
- exec_(bytecode, {"NOTHING": NOTHING,
- "attr_dict": attr_dict,
- "validate": validate,
- "_convert": _convert}, locs)
+ globs = {
+ "NOTHING": NOTHING,
+ "attr_dict": attr_dict,
+ "validate": validate,
+ "_convert": _convert
+ }
+ if frozen is True:
+ # Save the lookup overhead in __init__ if we need to circumvent
+ # immutability.
+ globs["_setattr"] = object.__setattr__
+ exec_(bytecode, globs, locs)
init = locs["__init__"]
# In order of debuggers like PDB being able to step through the code,
@@ -471,11 +478,11 @@ def _attrs_to_script(attrs, frozen):
Return a valid Python script of an initializer for *attrs*.
If *frozen* is True, we cannot set the attributes directly so we use
- ``object.__setattr__``.
+ a cached ``object.__setattr__``.
"""
if frozen is True:
def fmt_setter(attr_name, value):
- return "object.__setattr__(self, '%(attr_name)s', %(value)s)" % {
+ return "_setattr(self, '%(attr_name)s', %(value)s)" % {
"attr_name": attr_name,
"value": value,
} and I couldn’t find any significant or even consistent performance advantage (MBP late 2013) even with 6 arguments:
Am I doing something wrong? ISTM that we’re trying to be too clever here and Python does quite a bit of optimizations that we may even be sabotaging. |
The classes you're testing aren't frozen? |
lol dumbass OK second try:
Holy shit that’s slow in both cases. 😱 More than 2x slower…I know why I’m not gonna use it. :) As for factories: making them slotted is great and fine with me but frozen? Nah, not worth it. |
Saves memory without downsides. Ref #60
I’m kind of still waiting for general approval/feedback btw. As I’ve made clear, this feature is not for me. I'm not gonna make my code artificially slower just to get sort-of-immutability. I just don’t mutate. ;) What I’m saying is, that someone has to be happy, so I can’t merge this until things are clear to me, that y’all have what y’all want. |
I wonder why immutability is necessary applied to all attributes of a class ? Couldn't immutability only be applied to desired attributes ? |
@Insoleet it's just a design choice we've agreed on. A question for someone more knowledgeable than I (i.e. other people in the conversation): is there a dirtier, faster hack we could be using instead? |
Circumventing the descriptor gave us some more vroom vroom:
vs.
But I’m pretty sure we’re arriving at a point of diminishing returns. |
OK everyone, last call for conceptual feedback/vetoes? |
if frozen is True: | ||
lines.append( | ||
"_setattr = _cached_setattr.__get__(self, self.__class__)" |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Personally I think this is useful and should go in. Maybe I won't use it for classes that get instantiated a lot in inner loops, but for long-lived classes where you'd like to avoid deepcopying frozen is surely the way to go. (Think of Maybe add a note to the docs that a frozen |
Unfortunately, as stated,
There's an overhead on PyPy too. 12 ns vs 0.9 ns? Not even sure these numbers are to be trusted. In any case I could spare 12 ns to instantiate a class. |
First shot at immutability to fix #3 and #50.
I would very much like some input from y’all that asked for it: @glyph @radix @Tinche
A couple of notes:
frozen
because most Pythonista have heard offrozenset
.immutable
is a bit long,value
seems confusingly generic and only understandable to people with FP/theory background.__setattr__
at the end of__init__
but that doesn’t seem to work unfortunately. Soobject.setattr
it is.I’m not set on any of those decisions and will happily be converted to any other way. Just please keep in mind, that the feature has to be discoverable and understandable to people who just started programming.
Please paint this bikeshed.