class foo:
pass
obj = foo()
obj.bar = "I thought Python was strongly typed?"
print(obj.bar)
And even better: class foo:
a = 42
obj = foo()
print(obj.a)
del foo.a
print(obj.a)
Whatever your opinion on what the imprecise sentence "strongly typed language" should mean, these are definitely not features of one.Contrast Python (a strongly typed language):
>>> 1 + "1"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> [] + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "int") to list
With Javascript (a weakly typed language): 1 + "1"
"11"
[] + 1
"1"Did someone say PHP?
I'm always wary of these, because you can define that as strongly typed if it's the operation which is defined to perform the conversion internally, which IIRC is how it works in javascript.
For instance the first example will do the exact same thing in Java, because addition between a string and a non-string is defined as converting the non-string to a string then concatenating.
The second operation is not defined such in Java, but in theory you could have a universal toNumber protocol and define the addition of a non-integer and an integer as converting the non-number to a number then adding.
Yes, when objects are involved it's internally translated to:
([]).toString() + 1
This can be shown by changing the default implementation: > Array.prototype.toString = function() { return 'Boo!'; }
> [] + 1;
"Boo!1"
Changing the prototype for Number doesn't work so I assume there's something slightly different going on there.The answer is that addition first checks if either operand has a "primitive value" which is string-typed, if so it's a string concatenation, otherwise it's a numerical addition, at which point it converts both operands to numbers and adds them.
The primitive value of a `Number` is a `number`, so changing `Number.prototype.toString` has no effect (it's not even called). However if you set `Number.prototype[Symbol.toPrimitive]` then you can influence the rest of the process. Still won't affect an addition of primitive `number` values but:
> Number.prototype[Symbol.toPrimitive] = function(hint) { return String(this.valueOf()) }
> new Number(4) + 2
< "42"
> 4 + new Number(2)
< "42"
[numeric binops]: https://262.ecma-international.org/13.0/#sec-applystringornu...[numeric conversion]: https://262.ecma-international.org/13.0/#sec-tonumeric
Stand upon a language and look down. You get to raw physics as you go down. All the abstractions are a useful reconception, not the reality. Stand upon the language and look up. You see all the unrealized programs that can be built atop it. Focus in on the programs of a particular type: those that implement a language within the language. Spot one in particular - the one that uses say `@property` `isinstance` and `raise` and `TypeError` to always preserve type safety in every situation a person cares about.
So what I am concerned about is the behavior of the finite set of elements provided by the language and their properties. I can make claims about these concrete things - the addition operator in one language rejects by type but in another it doesn't. But I'm quickly overwhelmed by infinities when I try to do more.