That's basically the definition of dynamic typing, looked at from a static implementation.
Exactly: when we're comparing static typing to dynamic typing we have to use some common ground for comparison. That common ground is static typing, since dynamic typing is just a special case ( http://existentialtype.wordpre... )
The name of a type, and its lineage, while sometimes checked, are usually considered unimportant. Having certain attributes, and methods, of certain types, are important. It allows for similar uses as compositional inheritance, but without actually having that.
No, that has nothing to do with types. That's just a (rather limited) way of treating functions as values. For example, we can store first-class functions in dictionaries:
def c1():
s = {"p": "foo"}
return {"f": lambda _*: s["p"]}
def c2():
s = {"q": "bar"}
return {"f": lambda _*: "hello " + s["q"] + " world"}
o1 = c1()
o2 = c2()
def show(x):
print x["f"]()
show(o1)
show(o2)
Here "c1" and "c2" act like class constructors, "s" acts like "self", the "o1" and "o2" dictionaries act like class instances, "p" and "q" act like object properties, the lambdas act like methods and the "show" function is polymorphic. This is pretty much the whole of class-based OO, except for the syntax and that the function dictionary is stored in the __class__ property instead of explicitly in the object. Prototype-based OO is similar except we clone objects and use a __parent__ instead of a __class__.
Nothing there has anything to do with types. The "implicit self/this argument" is just a run-of-the-mill closure, the "object" itself is just a dictionary of properties, the "class" is just one of those properties which just-so-happens to contain a dictionary of functions with a custom lookup function (which defers to the class "parent" property if a key isn't found). These are all run-time values. The only type involved is "any".
Personally I prefer to throw away all of the complicated class/instance/inheritance/method mess and just pass functions straight to other functions. Much less messy, and just as polymorphic.
Code must accept an any, and then fail if the operations it needs to use cannot be applied to type of data of that object.
But from our common, static point of view it *does not* fail: it produces a perfectly reasonable "exception" value.
That doesn't make it not strongly typed, just not statically typed. Try getting a substring of a number type, for example. That works in javascript, a very weakly typed language.
It *also* works in Python:
def takeTwo(x):
return x[:2]
print 'First: ',
print takeTwo('hello')
try:
takeTwo(123)
except Exception as e:
print 'Second: ',
print e
First: he
Second: 'int' object has no attribute '__getitem__'
The first call to "takeTwo" gives back a string "he", which is a perfectly acceptable value of type "any". The second call to "takeTwo" gives back (via "raise") an exception "'int' object has no attribure '__getitem__'", which is another perfectly acceptable value of type "any".
There is no type-related failure here, in the same way that a "checkResult" function returning "False" isn't a type-related error. The substring operation in Python behaves differently to the one in Javascript, but they both accept any argument and can return any result (they're "endomorphisms"); although the "any" type in Python is slightly different to the "any" type in JS.