Hacker News new | comments | show | ask | jobs | submit login
Let me introduce: __slots__ (chrisbarra.xyz)
126 points by trueduke 9 months ago | hide | past | web | favorite | 61 comments



Please excuse this off-topic comment, but the webpage is severely dysfunctional. Unless JavaScript is enabled in the browser, the page will just display a loading screen. All the content is right there in the HTML and it would be perfectly readable if it wasn't deliberately obscured by the "loading" screen.[1] I'm sure the idea is that the page will load perfectly all at once, there won't be flashes of unstyled text and so on, but for me it just means that the real content won't load at all. Curiously, mobile users (or other users with small screens) are spared the loading page.[2] That's nice for mobile users, I guess, but as a desktop user it's just salt in the wound. If the plain page is good enough for mobile users, why isn't it good enough for me?

Please, web developers, stop doing this. It's not just the minority of people who browse with JavaScript disabled who are bothered by this, think of all the people on slow Internet connections who have to wait for your megabyte+ JavaScript program to download and execute before they can read the content of the 50 KB HTML page.

I understand that it is not feasible to accommodate no-JS browsers for Single Page Applications because JavaScript is essential to their functionality, but this page is not an application and JS is obviously not essential to it. Use JavaScript to enhance webpages, not degrade them.

- [1] Yes, Reader View in Firefox (and similar) are able to render the page properly. It's a wonderful utility, but I shouldn't have to strip away your broken web design to read your content.

- [2] https://streamable.com/b390d (no JS mirror: https://cgt.name/files/fortheloveofgod.ogv)


I feel like every page posted to HN that has any JavaScript gets this comment. It's usually an overreaction in my opinion.

For this page, it wasn't. Holy moly, it took a rather generous amount of time until it loaded the loading spinner, followed by enough loading time that I would have closed the tab if not for my curiosity on how long it was going to take in the end. And all that for a brief article with mild syntax highlighting.


> HN that has any JavaScript

It's really only pages that use JS to create a user experience that's worse than not having it at all.


"Use Javascript to enhance webpages, not degrade them."

That sounds like something the user could do, client-side.

Why do users have to blindly accept and execute Javascript payloads from every website?

What if the website said, "Hey I have some cool Javascript that can really enhance the content of this website. Do you want to download it to memory and run it on your computer, in your browser?"

The user might respond, "Sure, run it!" or "No thanks, I have some Javascript I wrote myself to enhance website content and would prefer to use that instead. Just give me the content."

I recall in the 1990's browser pop-ups where users were asked if they wanted to (download and) run Java applets. I believe there are still some websites out there that use Java applets. Not sure how browsers deal with this today.

Doubtful that web developers will listen to your plea although it is admirable. What they care about directly is browsers. The way to send them a message as a user is in the choice of what browser you use, what shows up in their logs.

Using a browser that enables all Javascript by default signifies your agreement with what they are doing today. Using a browser that does not read Javascript sends a message that you can do without it.

If the logs started showing a majority of user agents were browsers that do not read Javascript how would web developers respond?

User Agent header is still surprisingly influential.


I think what you're describing is basically just RSS feeds, no?


Just profiled the page via Chrome's "Fast 3G" network preset. I saw 3.5 MB download over 21 seconds before the spinner disappeared. The DOM content was ready at 7.8 seconds – still far too long.


I ran a performance audit from the Chrome dev tools:

  First meaningful paint: 8,820 ms
  First Interactive : 11,420 ms
Those are brutal numbers for just about anything, let alone a simple blog post.


Info like this should be automatically added as a flair right next to each submission on HN. Time to first interactive, download sizes, number of ads, ...

This would actually be a nice side project. Throw each link which was submitted to HN at a headless Chrome/Firefox, do performance/ads/size analysis, save everything to a database and then make it available to the HN user through a browser plugin which adds this info next to each submission. And then you can even display badges of shame for the worst offender or block links and other stuff. Any takers?


This is why I always go to the comments on HN first. I can usually rely on the top comment to do one or all of the following:

* neutralize clickbait * provide an instant endorsement (worth reading) or rebuttal (not) * warn of slow/heavy/ad-ridden user experience


Wow it got a 15/100 Pagespeed score on Mobile https://developers.google.com/speed/pagespeed/insights/?url=...


What's amusing, and by amusing, I mean not amusing at all, is that there isn't any heavy content in the page - it's all text until you get to the comments, which aren't going to be seen until you get to the end of the article.

When all you have is a hammer and so on and so forth ...


Ye gods! uBlock Origin had a whopping 29 items blocked here here. I know mainstream corporate news sites with less garbage in them!


I felt the same way. I was expecting an article about Python, not a loading indicator on my broadband connection.


I have flagged the submission for exactly this reason: the page is not fit for purpose, and indeed appears to maliciously require code execution in order to display some text. Even after I allow one bit of JavaScript, I can briefly see the text and then … back to the loading animation.


let's be honest here, no company is going to make change for the 0.001% who happen to disable javascript


As I said, this isn't just a problem for no-JS browsers, it's also a problem for people with slow Internet connections. They might be able to load the HTML and CSS reasonably fast, but then they'll have to sit around waiting while megabytes of JS is downloaded and then executed before they can finally read the article.

I'm not asking developers to do extra work to accommodate no-JS browsers, I'm asking them to not do extra work to deliberately prevent sites from working without JS. The page would have worked perfectly if the author had not done extra work to add a useless loading screen. I'm asking developers to do less work, not more.


No reason to generate that page using Javascript, it's just a blog post. It took 10 seconds to load on my computer by the way, when loading should be almost immediate considering the little amount of text on that page.

If you are going to show me a pre-loader at least inform me about what is about to be loaded. 1 second more of wait and i would have left that page.


I have javascript on and gigabit internet. It still took over 10 seconds for this page to show me readable content.

This doesn't just apply to the people with JS off.


It was even sillier for me... I have broadband. The content loaded just fine and I started reading it and the loading screen came up over it and blocked it. Stupid-ware.

uBlock Origin: right-click > block element... gone.


>It's not just the minority of people who browse with JavaScript disabled who are bothered by this, think of all the people on slow Internet connections who have to wait for your megabyte+ JavaScript program to download and execute

For example, what about the growing population of users in the developing world for whom "slow" internet is the norm?


I've seen this point being brought up a lot, but I'm wondering how many of those people that actually lack good internet connection and live in developing world may be your target anyway?


I'm currently in South Florida, that just had Hurricane Irma blow through. I have no power (but I do have a generator [1]) and the DSL is down (no dial tone on the landline). I'm currently using my cellphone as a hotspot and an iPad as a laptop. I don't have a large data plan because, frankly, I don't use my cellphone as a hotspot all that often.

But when I do, I am painfully aware of just how much crap is included on every page. Oh, Verizon makes me painfully aware. Yes, I could get the "unlimited" dataplan, but I don't use the dataplan to make it cost effective for me (the last time a hurriane knocked the power out for me was back in 2005).

And I have it good! A few of my fellow cow-orkers have even worse cell coverage than I do.

[1] Keeping the refridgerator, freezer, a couple of lamps, a few fans, and iPhone/iPad chargers going.


For bandwidth emergencies I use Opera Mini, which can run everything through a compressing proxy before you get it- lossily compressing images (or even stripping them out) and reducing my bandwidth due to casual web browsing by about an order of magnitude. It works wonders on pages that are mostly text.

You do have to not mind routing all your traffic through Opera's servers, though.


I understand, but your situation is state emergency in which the very infrastructure is damaged, and not "the default" - you'll priorities will likely be different than ones people I'm targeting have and focused on recovering access to electricity/clean water, etc. ect. This will likely differ from people in so called rising markets too.


I would assume that more than 0.001% of the people use the Tor browser.


The number of people using Tor is probably less then the number if people who disable JavaScript.


If you count bridges, then .001% is pretty close (not more than .005%). Apparently about 1/3 of people on earth have a mobile broadband connection, so approximately 2 billion of those, and ~60K tor bridge connections.


For simple classes, you can also just use `NamedTuple` like:

  from typing import NamedTuple
  class Foo(NamedTuple):
      bar: int = 0
      baz: str = 'default'
  foo = Foo(1, baz='not default')
This provides the same performance benefits, and is very similar in a lot of ways to a Scala case class -- aside from removing the boilerplate of stating each attribute twice in __init__ and again in __slots__, it also provides nice default implementations for __repr__, __str__ (something like `Foo(bar=1, baz='quux')`), __hash__, and __eq__ (using member equality rather than identity).

The downsides are A, you can't inherit from any other class (it's syntactically valid, but silently ignored (!) though it will be caught by linters), and B, `isinstance(foo, tuple)` returns `True`, which messes with a lot of reflection code (e.g. the `json` module will not serialize them like you expect)


Unless you really need to iterate over your class attributes I see no reason to do this. If you really want to save yourself the extra typing, could just as easily write a "make_slotted_class" factory function or a "SlottedType" metaclass that generates these classes with the same syntax as namedtuple/NamedTuple.

Edit: I stand corrected. There's no easy way (that I'm aware of) to generate an __init__ method in Python without resorting to string manipulation. Perhaps you could do it at the C level, but that defeats the purpose IMO.


Use "type".[0]

The last argument should be the dict that __INIT__ is usually responsible for creating.

[0] https://jeffknupp.com/blog/2013/12/28/improve-your-python-me...


Not quite, AFAIU. The last argument is actually a dict of class attributes, one of which is the __init__ method. You can hack around it a few different ways (e.g. creating __init__ as a closure over the slot names and then inserting it into that 3rd argument), but none of them generate a proper init method.

I'm planning on trying again tonight and I will probably blog my results on Friday. I'll read over that post carefully in case there's something I missed about type() and metaclasses.


Good luck!

Last time I approached this was generating classes for Pyramid, an it was hell, because it was incredibly poorly documented, and the community view was that metaprogramming was the devil's work.

Community has started to shift, so there's a few more resources, but things don't behave exactly as expected so you will run into issues here and there.

Hopefully you can add to the knowledge out there!


I believe this is just a typing shorthand for `collections.namedtuple`:

https://docs.python.org/3/library/typing.html?highlight=name...

Last I checked `collections.namedtuple` had some infamous performance implications (although I vaguely recall some discussion about changing it's implementation):

https://stackoverflow.com/questions/2646157/what-is-the-fast...

I believe that `collections.namedtuple` is beneficial for memory consumption [1], but you pay a cost when accessing members by `__getitem__`.

In general, judging by timings of the StackOverflow above, I think it is better to use `__slots__` in most cases than `namedtuple`.

[1] http://blog.explainmydata.com/2012/07/expensive-lessons-in-p...

Edit:

Also, fun fact, is that namedtuple does a clever bit of metaprogramming where a string class definition is formatted and passed to `exec`:

https://hg.python.org/cpython/file/b14308524cff/Lib/collecti...


That post is from 2010, before even 2.7 was released. It is extremely out of date.

Re-running the same tests I get:

    In [1]: from typing import NamedTuple

    In [2]: class A(NamedTuple):
       ...:     a = 1
       ...:     b = 2
       ...:     c = 3
       ...: 

    In [3]: a = A()

    In [4]: class B:
       ...:     __slots__ = ('a', 'b', 'c')
       ...:     def __init__(self, a=1, b=2, c=3):
       ...:         self.a = a
       ...:         self.b = b
       ...:         self.c = c
       ...: 

    In [5]: b = B()

    In [6]: c = dict(a=1, b=2, c=3)

    In [7]: d = (1,2,3)

    In [8]: e = [1,2,3]

    In [9]: key = 2

    In [10]: %timeit z = a.c
    38.2 ns ± 0.07 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

    In [11]: %timeit z = b.c
    48.1 ns ± 0.0461 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

    In [12]: %timeit z = c['c']
    38 ns ± 0.062 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

    In [13]: %timeit z = d[2]
    38.8 ns ± 0.0425 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

    In [14]: %timeit z = e[2]
    39.8 ns ± 0.0641 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

    In [15]: %timeit z = d[key]
    48.6 ns ± 0.0713 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

    In [16]: %timeit z = e[key]
    49.4 ns ± 0.0369 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Namedtuple looks faster than slots here. And for comparison's sake:

    In [17]: class F:
        ...:     def __init__(self, a=1, b=2, c=3):
        ...:         self.a = a
        ...:         self.b = b
        ...:         self.c = c
        ...: 

    In [18]: f = F()

    In [19]: %timeit z = f.c
    53.7 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)


The question was asked in 2010, but the benchmark post I was referencing is from 2014.

It doesn't appear as if `namedtuple` has changed much:

https://hg.python.org/cpython/file/tip/Lib/collections/__ini...

Running the same tests from the post, I get similar results:

    from collections import namedtuple
    
    STest = namedtuple("TEST", "a b c")
    a = STest(a=1,b=2,c=3)

    class Test(object):
        __slots__ = ["a","b","c"]

        a=1
        b=2
        c=3
    
    b = Test()
    
    c = {'a':1, 'b':2, 'c':3}
    
    d = (1,2,3)
    e = [1,2,3]
    f = (1,2,3)
    g = [1,2,3]
    key = 2
    
    if __name__ == '__main__':
        from timeit import timeit
    
        print("Named tuple with a, b, c:")
        print(timeit("z = a.c", "from __main__ import a"))
    
        print("Named tuple, using index:")
        print(timeit("z = a[2]", "from __main__ import a"))
    
        print("Class using __slots__, with a, b, c:")
        print(timeit("z = b.c", "from __main__ import b"))
    
        print("Dictionary with keys a, b, c:")
        print(timeit("z = c['c']", "from __main__ import c"))
    
        print("Tuple with three values, using a constant key:")
        print(timeit("z = d[2]", "from __main__ import d"))
    
        print("List with three values, using a constant key:")
        print(timeit("z = e[2]", "from __main__ import e"))
    
        print("Tuple with three values, using a local key:")
        print(timeit("z = d[key]", "from __main__ import d, key"))
    
        print("List with three values, using a local key:")
        print(timeit("z = e[key]", "from __main__ import e, key"))
Output:

    Named tuple with a, b, c:
    0.147349834442
    Named tuple, using index:
    0.0364408493042
    Class using __slots__, with a, b, c:
    0.0334758758545
    Dictionary with keys a, b, c:
    0.0339260101318
    Tuple with three values, using a constant key:
    0.0425179004669
    List with three values, using a constant key:
    0.0301761627197
    Tuple with three values, using a local key:
    0.0436310768127
    List with three values, using a local key:
    0.0309669971466


I just noticed your slotted object uses class variables. That is completely wrong - so your analysis is probably wrong.

In Python, I think it's important to do what's directly semantically correct.

However, we can compare apples and oranges on what they have in common, so here's a bit of my own analysis:

  >>> from timeit import repeat
  >>> from collections import namedtuple
  >>> class Slotted: __slots__ = 'a', 'b', 'c', 'd', 'e'
  ... 
  >>> NT = namedtuple('NT', 'a b c d e')
  >>> 
  >>> nt = NT(1,2,3,4,5)
  >>> s = Slotted()
  >>> s.a, s.b, s.c, s.d, s.e = 1,2,3,4,5
  >>> min(repeat(lambda: (s.a, s.b, s.c, s.d, s.e)))
  0.2850431999977445
  >>> min(repeat(lambda: (nt.a, nt.b, nt.c, nt.d, nt.e)))
  0.418955763001577
 
I'm showing the namedtuple to be a little slower, but I think the thing to remember is to do the semantically correct thing first. If you've met your technical requirements, you're done.

Only if you're too slow, then you look for ways to optimize for bottlenecks.


I was just copying the benchmark I was quoting as a sanity check since I was just trying to validate references.

I also feel compelled to point out that __slots__ changes the way objects are initialized [1]:

> By default, instances of both old and new-style classes have a dictionary for attribute storage. This wastes space for objects having very few instance variables. The space consumption can become acute when creating large numbers of instances. > > The default can be overridden by defining __slots__ in a new-style class definition. The __slots__ declaration takes a sequence of instance variables and reserves just enough space in each instance to hold a value for each variable. Space is saved because __dict__ is not created for each instance.

Which means that, in actuality, simply defining __slots__ changes the nature of the object.

Quick trial demonstrates this:

    >>> class mySlottedObj(object):
    ...     __slots__ = ('a', 'b')
    ...     c = 1
    ...     
    >>> x = mySlottedObj()
    >>> x
    <mySlottedObj object at 0x10e6a3510>
    >>> x.c
    1
    >>> x.__dict__
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    AttributeError: 'mySlottedObj' object has no attribute '__dict__'
    >>> x.__weakref__
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    AttributeError: 'mySlottedObj' object has no attribute '__weakref__'
    >>> x.a = 1
    >>> x.c = 2
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    AttributeError: 'mySlottedObj' object attribute 'c' is read-only
The benefit of namedtuples, in the context of Python is reducing the object footprint to being no greater than a tuple. The performance hit, which I am trying to demonstrate, is in lookup and is shown in the source (linked above).

    _field_template = '''\
        {name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}')
    '''
As I understand it this means when you access using __getitem__, a named tuple first maps to a property that then calls a __getitem__ using the aliased index on the base `tuple` object that `namedtuple` is storing.

But at any rate, the benchmark script was not mine and refining the test to be as close to the same code path is welcome.

And I wholeheartedly agree:

> Only if you're too slow, then you look for ways to optimize for bottlenecks.

I was merely responding to the original assertion:

> This provides the same performance benefits, and is very similar in a lot of ways to a Scala case class -- aside from removing the boilerplate of stating each attribute twice in __init__ and again in __slots__,

And I think at this point it is quite clear that namedtuple has a bit more caveats than just "removing the boilerplate" of __slots__.


namedtuple's startup performance is terrible, because formatting a string class definition and passing it to exec is slow. The controversy (https://lwn.net/Articles/730915/) was about whether startup performance is worth optimizing (the decision was yes and namedtuple is apparently going to be rewritten in C). Instances of namedtuple are plenty fast.


Startup performance is understandably terrible, the bit of clever metaprogramming I reference above is the reason. I am talking about member access as far as performance goes.

The references I link I think go into better detail than I can.


Guido used to be hostile to __slots__, but apparently he's lightened up.

If you're not using "setattr" to dynamically add fields to your object, CPython's underlying machinery is overkill. Everything is a dict, and all accesses take a lookup. With __slots__, a Python object is more like a struct in other languages.

Slotted objects should be the default. You should have to inherit from "DynamicObject" or something like it to enable setattr on an object. Actually, dynamic objects stopped being that important once Python let you inherit from "dict". Then, if you wanted "foo[ix]", it was easy to do it.

There are some packages, such as BeautifulSoup, which use dynamic attributes heavily. BeautifulSoup uses it to map HTML tags to attributes. This is more trouble than it's worth, because when an HTML tag name clashes with a Python builtin name, workarounds are necessary.


> "should"

this would make python a different language completely


I mean it would certainly be a breaking change to existing code, and we know how well that goes over in the Python community, but it wouldn't be a completely different language.

The argument is that these objects are a sane default that would prevent a lot of accidental programming mistakes and I agree.


See my comment elsewhere about a "split" approach to this for a Ruby implementation. I get that there's a semantic difference to change from __dict__ to the way __slots__ currently works, but it might be feasible to change the way __dict__ works to inline instance variables in the instance by default in cases where they're readily statically inferred and get much of the same effect without changing the semantics of __dict__.


Since I don't think it was explicitly mentioned, this is because __slots__ is effectively a named tuple. The size reduction comes from dropping the keys in the dictionary (they're stored once on the class, not every time on the instances), and the speed reduction come from it being an array lookup instead of a dict.

It's also a great way to introduce some static typing. No more setting mistyped class attributes without errors!


I've opted for a similar approach "behind the scenes" for my (wildly incomplete, though now close to compiling itself) Ruby compiler: While you can usually statically determine most likely instance variables for Ruby classes, and optimize that by creating the equivalent of slots, you also need to be able to dynamically create more, so I allocate "slots" in the instances for any variables that I can see, and fall back on the equivalent of __dict__ for anything that's dynamically added.

I've always found it quite curious that Python made this explicit rather than an implementation detail (I get that there are slight semantic differences here in Python too) because it seems like such a useful optimization. And a similar approach can be used for method lookup too.

I wonder (and I don't have any measurements on this myself) what the relative benefits are to explicitly picking one over the other vs. automating it are. The downside of doing it automatically is that some types of objects might very well be very "sparse" in that it's not a given that all instance variables are used for all objects (this is more applicable in for vtables, especially in languages with single-rooted object hierarchies).

My intuition is that the overhead of a map/dict/hash table to store them is likely to usually outweigh the cost of quite a lot of unused instance variables, and so that inferring instance variables is generally likely to be an improvement.


Do you have a link to your Ruby compiler? EDIT found it in your profile http://www.hokstad.com/compiler and submitted to HN. Great series of posts.

About Python, it's a little older than Ruby (its design started in 1989) and Guido worked on the ABC language at the beginning of the 80s. IMHO that difference shows in many places, for example in having to explicitly pass self as argument in method definitions. Newer languages do without it and programmers don't get confused at all. All those double underscore methods and the general explicitness of the language smell of 80s and of C. Not necessarily a bad thing but reading some Python code is like getting on a time machine.


Thanks. It's long overdue some updates as I've done quite a lot of work since (though still moving slowly) and it's pretty close to being able to fully compile itself now.

What you say makes sense, and I understand that it takes a lot of effort to clean those type of things up without hurting backwards compatibility.

This specific distinction though seems like one where it'd still be possible to get most of the benefit by changing the implementation of __dict__ to make it do something similar to __slot__ under the hood, but fall back to a dict for dynamic properties.


I have updated the documentation on __slots__, and those changes have made it into the dev version of the Python docs.[0]

I have also written up __slots__ in great detail on Stack Overflow.[1]

I'm at a meetup right now, but I can try to answer any quick questions here tonight.

[0] https://docs.python.org/3.7/reference/datamodel.html#object....

[1] https://stackoverflow.com/q/472000/541136


I see __slots__ come up quite often (I think more often than it should). It's great to be aware of, but shouldn't be used unless necessary. Like any optimization, it can make code maintenance more difficult. Obviously, you have to update slots if you add more attributes to the class, but you have to be aware of slots when dealing with class inheritance.


A few years ago I had read about Python performance gains and some built in attributes, but couldn't find the reference when I looked. Well it was `__slots__`, thanks for posting!


I just discovered attrs(http://www.attrs.org/en/stable/). On paper it looks great, seems to reduce boilerplate, has support for immutability and slots too and claims to have no runtime overhead. If anybody here has experience using it, would love to hear from you. Is it as good as it looks on paper? Do you recommend using it?


I've been using it here and there in place of named tuples and data-only classes. It is very convenient.

I had a project where I had to connect to a data source and serialize some data. When I needed to add an attribute all I had to do was add it to two places: the class itself, and the @classmethod constructor. So in this case I got the serialization for free, but that's all I was using. In reality I also got __repr__, __cmp__, etc, etc for free too, I just wasn't using it. Serialization was free because attr.asdict() knows which attributes are attr.ib() attributes.

  @attr.s()
  class Foo:
      bar = attr.ib()
      spam = attr.ib()
      eggs = attr.ib()  # added this line
  
      @classmethod
      def from_something_else(cls, x):
          return cls(
              bar=x.bar.name,
              spam=x.spam,
              eggs=x.get_eggs().blah(),  # and this line
          )
  
  with open('blah.json', 'w') as fout:
      json.dump(fout, attr.asdict(o))  # got serialization for free


This is all nice and all, but of course such language hacks (as I would call them) make for a less elegant language with a higher barrier to entry. At some point it would make sense from a software-engineering standpoint to switch to a cleaner lower level language.

By the way, I like the approach taken in JavaScript engines such as V8, which determine the "slots" dynamically.


I just started learning Python, what does this syntax mean?

    **json.loads(my_json))
from

    with_slots = [get_size(MyUserWithSlots(**json.loads(my_json))) for _ in range(NUM_INSTANCES)]
Edit: I mean, I get what it's doing, but specifically I don't understand the double * (somehow it's not rendering in this comment)


A function in Python takes two kinds of arguments: positional arguments, and keyword arguments.

    def foo(a, b, c):
        pass

    # Arguments passed positionally:
    foo(1, 2, 3)
    # or passed by keyword:
    foo(a=1, b=2, c=3)
Now, say you want to make that second call somewhat dynamically, and you have the dict:

    a_dict = {'a': 1, 'b': 2, 'c': 3}
You can make that second foo call:

    foo(**a_dict)
    # because of the value of a_dict, expands to:
    # foo(a=1, b=2, c=3)
Functions themselves can take only positional arguments, only keyword arguments, or really, any combination of them. Here is a decent SO question and answer[1] that might help, a bit about it in the tutorial[2], and last, the formal documentation[3].

[1]: https://stackoverflow.com/questions/1419046/python-normal-ar...

[2]: https://docs.python.org/2/tutorial/controlflow.html#keyword-...

[3]: https://docs.python.org/2/reference/expressions.html#grammar...


Awesome, thank you for the detailed explanation!


Unpacks the object into a dictionary. It's syntactic sugar:

https://stackoverflow.com/a/2921893/66202


This uses the key-value pairs as keyword arguments.


I don't think it's necessarily a bad thing, but it's kind of odd to see an article on the front page of HN that amounts to a less-concise version of the documentation for a very simple to understand language feature (the doc page has four or five sentences and is just as easy to understand).


If you want to create a large number of Objects (like for rows in a data set) using __slots__ saves a lot of memory over just using the standard __dict__. I've always used named tuples for this in the past, but this is a nice way to do it.


One caveat though, object creation with `__slots__` is a bit slower than "normal" objects, if I remember my past Python benchmarks correctly.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: