5 ms·
For simple classes, you can also just use `NamedTuple` like: from typing import NamedTuple class Foo(NamedTuple): bar: int = 0 baz: str = 'defa
by gipp 9y ago
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)
- nerdponx 9y agoUnless 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.
- shakna 9y agoUse "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-metaclasses-and-dynamic-classes-with-type/ https://jeffknupp.com/blog/2013/12/28/improve-your-python-me...
- nerdponx 9y agoNot 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.
- shakna 9y agoGood 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!
- gshulegaard 9y agoI believe this is just a typing shorthand for `collections.namedtuple`: https://docs.python.org/3/library/typing.html?highlight=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-fastest-to-access-struct-like-object-in-python 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-python-performance.html 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/collections/__init__.py#l232 https://hg.python.org/cpython/file/b14308524cff/Lib/collecti...
- gipp 9y agoThat 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)