There was no plan, and, in the initial stages, I'd imagine that the author(s) were surprised that they'd even gotten this far.
Famously, the whole object system in Python was implemented in like a few days. That included the design and testing too... It did see some changes over time, but there's a lot of petrified turd there that's increasingly difficult to get rid of.
https://peps.python.org/pep-0671/
Truth is, early vs late binding is a tradeoff. Python picked one, but not like the other is without edge cases either.
The whole “tradeoff” argument doesn’t make sense when you consider that Python is the odd one out here with early binding. In fact, there’s been so many bugs with it that they had to invent a whole new syntax to fix it.
When one says `intParam += 2`, why should they expect that to modify the default parameter? Yet when the default is a list, all of a sudden it’s fine. Python breaks the principle of least astonishment with default parameters, and it’s frustrating hearing people defend such a thing.
Unexpected emergent behaviors are extremely common in programming language design. Even when carefully planning every language feature, unexpected interactions can and do occur, sometimes these things are not ideal. I am not very informed about Python's history here, but it's not far fetched that this behavior was not planned and just happened due to unexpected interactions of other features.
Why would Python be immune to this? Are Guido and the rest of the Python contributors 1000x devs that can see infinitely far into the future and never make mistakes? All of Pythons quirks were actually planned from the very beginning I guess.
All the "discussion" wasn't really a discussion but post-rationalization and adoration of his "genius work".
No sane programmer with a modicum of experience would've designed objects to have both __slots__ and __dict__. The @property thing was also a crutch to deal with bad "design" decisions which were never properly addressed... and, basically, everything that was done afterwards was done in an additive way, so the old garbage stayed forever and became more entrenched.
But perhaps old-style classes were awful. I don't really remember. I haven't used them for a very long time, because new-style classes, which were definitely not implemented in a weekend, have been around for approximately forever. Old-style classes were obsoleted in 2002 and don't even exist in Python 3, which was first released in 2008. Old-style classes didn't have multiple inheritance or many of the customisation points that new-style classes have.
>No sane programmer with a modicum of experience would've designed objects to have both __slots__ and __dict__.
Python objects don't have both __slots__ and __dict__. __slots__ is an optimisation for those cases where performance is important and you don't need the dynamism of __dict__. There is also no need for such extreme language.
>The @property thing was also a clutch to deal with bad "design" decisions which were never properly addressed...
Not at all. @property is just one example of a descriptor. Python attributes are just attributes, but descriptors allow them to be more than just __dict__ accesses. It's a very clever and flexible system that allows for some very expressive metaprogramming. @property isn't special and it has nothing to do with "bad design decisions".
>and, basically, everything that was done afterwards was done in an additive way, so the old garbage stayed forever and became more entrenched.
Python is literally one of the only languages that has got rid of its "old garbage".
No, __slots__ isn't an optimization, it came before __dict__.
Old classes allowed multiple inheritance. And they are not so different from the new classes. The difference is superficial. There were and still are many things in Python that were written w/o any thought or cohesive organizing principle, but, over time, they put some effort to make it look more uniform.
For example, old errors didn't inherit from object, but eventually they were made to do so. Similarly, module type didn't inherit from object, but at some point it was made to do so. A lot of Python builtins were half-arsed reimplementation of object with a bunch of slots missing.
> Not at all. @property is just one example of a descriptor.
This is exactly why it's bad design. If one were to design object system with properties, they wouldn't need this patch. It would've been part of the system from the get go. There's no need for descriptors here or in general. The whole idea of decorators is a half-arsed attempt at macros...
> Python is literally one of the only languages that has got rid of its "old garbage".
No, absolutely not. They renamed things, but kept the implementation the same. Sometimes they added things, like in example with module type, to make it look more uniform. Sometimes they hid some functionality from the interpreted part of Python, but it is still accessible to C API etc.
It's the same story with multiprocessing -- it was a failure from the start, but they never admitted it, and kept incrementing to make it an even bigger failure... while deep inside their hearts they knew it was a failure, so they also tried other things... which also failed (like multiple interpreters).
When you read the C source of Python, the feeling that you get is that you are looking at someone who sucks at C, who struggles to make their code to run at least somehow. It's not the code of a person who knows their way around, can consider benefits and downsides of doing the same thing in different ways. It's a code of a person who adds asterisks and ampersands until it compiles, who's never heard of const-correctness, who doesn't try to minimize heap allocations... It's a code that you'd typically expect from a second-third year CS student.
mutable /ˈmjuːtəbl/ adjective liable to change.
I like the emphasis that it’s liability haha
def myfunc(x=[])
puts x
end
In Ruby is the same as def myfunc(x=lambda:[])
print(x())
in Python when the default is needed. The benefit is performance, you only have to evaluate the default once at function definition time. I won't say it's not a footgun but it's also I think a sane choice when in Python it's common to have functions with many default parameters.Sure, but when you design a language you are striking a balance between many things (which is why some features could take years to finalize and implement). In this case, the gain in performance is likely minimal, but the confusion and surprise it causes does a lot of damage.
(I recently read through the entire series of conversation with Anders Hejlsberg on the design of C# which is very insightful https://www.artima.com/intv/anders.html)
def foo(x, cache = {}):
value = cache.get(x)
if value is None:
...
cache[x] = value
return value
This simple example, thankfully, is obviated by the @cache decorator as of py3.9, but I still occasionally find uses for the feature. def foo(allowed=()):
truthy = ['true', 'True']
truthy += allowed # most things work
allowed.append('yes') # error
It's not perfect (`truthy = ['true', 'True'] + allowed` will not work). def foo(allowed: Optional[List[ElemType]] = None):
allowed = allowed or []
...