I agree. Change the class slightly and several of those methods become a big negative. As I've argued, I don't want to include API features "for free" if I don't really need them, because they will become a support headache for me in the future
Here's what I mean. Let's start with the Point(x, y, z) class. If I want to tweak it a bit to add a pre-computed 'r' attribute, I can do the following in a normal class:
class Point:
__slots__ = ("x", "y", "z", "r")
def __init__(self, x, y, z):
self.x, self.y, self.z = x, y, z
self.r = (x*x+y*y+z*z)**0.5
(If I want to enforce immutability I would use the attrs library.)
I can't simply tweak a namedtuple to support that, because the "r" will leak in to the __init__, _as_dict(), __iter__, __repr__, and other methods. Nor can I write "for x, y, z in points" any more.
Now, consider a point expressed in spherical coordinates (r, theta, phi). The two points (0, 0, 0) and (0, 45, 180) are identical, because both are at the origin. The two points (10, 0, 0) and (10, 360, 0) are also identical, because both are at (10, 0, 0) in Cartesian coordinates. (Angles expressed in degrees.) Either the __init__ needs to normalize the values to some standard range, or the __eq__ needs to check for degenerate cases. The default __eq__ and __hash__ for namedtuple are valid if all coordinates are in standard form, but that's incomplete.
(Huh, and I see that the tuple behavior is that (nan,) == (nan,) even though nan != nan. This doesn't makes sense for some cases.)
I disagree with the proposal that slicing is "useful for computing projections or partials to 2D points". In addition to the weirdness in slicing an Employee record, how do you project along the y axis using slicing? Are people really supposed to write [0::2]? Or [2:-1:-2] if they want the projection on the other side? That seems like a horrible API. Plus, the result is an anonymous tuple, when a Point2D makes more sense.
For that matter, in spherical coordinates there is no meaning to point[::-1], so why do I want the API to support that in the first place?
Next, consider a point expressed in homogeneous coordinates x, y, z, w. Slicing here is worthless as a way to give a projection. What does point4[2:] mean?
In a real system of course, you project given a view point and orientation, and are not limited to a projection along the axis, which is all that a slice might be able to give you. Of course, in real system you store all of the points in an array because that "is dramatically more space efficient".
Then there's the _make() method, which I'm struggling to understand why it's useful. It seems to save only a few characters over either of the following:
for emp in (EmployeeRecord(*x) for x in csv.reader(file)):
print(emp.name, emp.title)
for row in csv.reader(file):
emp = EmployeeRecord(*row)
print(emp.name, emp.title)
The latter is also, IMO, easier to understand and maintain for a wider number of people.
On a deeper level, I can't see using this because most code would expect EmployeeRecord.age to be an integer. It's not easy to change the example code to, say, list those which are legally allowed to sell alcohol in the US:
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
if emp.age >= 18:
print(emp.name, emp.title)
Even changing it to "18" gives a problem because "100" < "18".
At least the sqlite example will (likely) put the right types in the object, but again it's only saving a couple of keystrokes at most. Why learn a specialized API function for that?
As for the "Many other little things", those appear to be solutions in search of a problem. As Too points out, if I needed an _asdict() I could often do obj.__dict__.copy(). In practice, I use __dict__ access in debugging - rarely - and don't recall ever using that in production code. Why is _asdict() so important that it should be part of my API? If it is so important, why don't more people include it with their own class definitions?
I don't want to litter my API with functions that are useless, which is what namedtuple invites me to do by having me pretend that my data is "super" tuple-like.