Pythonは厳密に型指定された言語であるというリンクに出くわしました。
ただし、強く型付けされた言語ではこれを行うことはできないと考えました。
bob = 1
bob = "bob"
強く型付けされた言語は、実行時に型の変更を受け入れないと考えました。たぶん、強い型/弱い型の定義が間違っている(またはあまりにも単純すぎる)のです。
それで、Pythonは強くまたは弱く型付けされた言語ですか?
Pythonは、動的に型指定されています。
あなたの例は
bob = 1
bob = "bob"
これは、変数に型がないため機能します。任意のオブジェクトに名前を付けることができます。 bob=1
の後、type(bob)
はint
を返しますが、bob="bob"
の後は、str
を返します。 (type
は通常の関数なので、引数を評価し、値の型を返します。)
これとは対照的に、Cの古い方言は弱く静的に型付けされていたため、ポインターと整数はほとんど互換性がありました。 (多くの場合、最新のISO Cは変換を必要としますが、私のコンパイラはデフォルトでまだこれに寛容です。)
強いタイピングと弱いタイピングはブール型の選択というよりも連続的なものであることを付け加えなければなりません。 C++にはCよりも強い型付けがあります(より多くの変換が必要です)が、型キャストはポインターキャストを使用して破壊できます。
Pythonなどの動的言語の型システムの強さは、そのプリミティブとライブラリ関数が異なる型にどのように応答するかによって実際に決まります。たとえば、+
はオーバーロードされているため、2つの数字または2つの文字列で機能しますが、文字列と数字では機能しません。これは+
が実装されたときに行われた設計上の選択ですが、実際には言語のセマンティクスに従う必要はありません。実際、カスタム型で+
をオーバーロードすると、暗黙的に何でも数値に変換できます。
def to_number(x):
"""Try to convert x to a number."""
if x is None:
return 0
# more special cases here
else:
return float(x) # works for numbers and strings
class Foo(object):
def __add__(self, other):
other = to_number(other)
# now do the addition
(完全に厳密に型指定されている、厳密に型指定されていることを私が知っている唯一の言語はHaskellであり、型は完全にばらばらであり、型クラスによってオーバーロードの制御された形式のみが可能です。)
既存の回答のすべてが見落としていると思う重要な問題がいくつかあります。
弱いタイピングとは、基になる表現へのアクセスを許可することを意味します。 Cでは、文字へのポインターを作成し、それを整数へのポインターとして使用するようコンパイラーに指示できます。
char sz[] = "abcdefg";
int *i = (int *)sz;
32ビット整数のリトルエンディアンプラットフォームでは、これによりi
が数値0x64636261
および0x00676665
の配列になります。実際、ポインター自体を(適切なサイズの)整数にキャストすることもできます。
intptr_t i = (intptr_t)&sz;
そしてもちろん、これはシステム内のどこでもメモリを上書きできることを意味します。*
char *spam = (char *)0x12345678
spam[0] = 0;
*もちろん、最新のOSは仮想メモリとページ保護を使用しているため、自分のプロセスのメモリのみを上書きできますが、C自体にはこのような保護を提供するものはありません。
従来のLISPでは、同様の種類のハッカーが許可されていました。一部のプラットフォームでは、ダブルワードのフロートとコンスセルは同じ型であり、一方を他方を期待する関数に渡すだけで「機能する」ことができました。
今日のほとんどの言語は、CやLISPほど弱くはありませんが、多くの言語はまだやや漏れがあります。たとえば、チェックされていない「ダウンキャスト」*があるOO言語は、タイプリークです。本質的に、コンパイラに「これが安全であると判断するのに十分な情報を提供しなかったので、型システムの重要な点は、コンパイラが常に何が安全であるかを知るのに十分な情報を持っているということです。
*ダウンキャストをチェックしても、チェックがランタイムに移動するからといって、言語の型システムが弱くなることはありません。もしそうなら、サブタイプポリモーフィズム(別名、仮想関数呼び出しまたは完全動的関数呼び出し)は型システムの違反と同じになり、だれもそれを言いたくないと思います。
この意味で弱い「スクリプト」言語はほとんどありません。 PerlやTclでさえ、文字列を受け取ってそのバイトを整数として解釈することはできません。*ただし、CPython(および多くの言語の他の多くのインタープリターでも同様)に注意する価値はあります。 ctypes
を使用してlibpython
をロードし、オブジェクトのid
をPOINTER(Py_Object)
にキャストし、型システムを強制的にリークさせることができます。これにより型システムが弱くなるかどうかは、ユースケースに依存します。セキュリティを確保するために言語内で制限された実行サンドボックスを実装しようとする場合、これらの種類のエスケープに対処する必要があります。
* struct.unpack
のような関数を使用してバイトを読み取り、「Cがこれらのバイトをどのように表すか」から新しいintを構築できますが、明らかに漏れはありません。 Haskellでも可能です。
一方、暗黙的な変換は、弱い型システムや漏れやすい型システムとは実際には異なります。
Haskellを含むすべての言語には、たとえば整数を文字列または浮動小数点数に変換する機能があります。ただし、一部の言語ではこれらの変換の一部が自動的に行われます。たとえば、Cでは、float
を必要とする関数を呼び出し、int
で渡すと、変換されます。これは間違いなく、たとえば予期しないオーバーフローを伴うバグにつながる可能性がありますが、弱い型システムから得られるのと同じ種類のバグではありません。そして、Cは実際ここで弱くなっているわけではありません。 Haskellでintとfloatを追加したり、floatを文字列に連結したりすることができます。もっと明示的に行う必要があります。
そして、動的言語では、これはかなりあいまいです。 PythonやPerlには「フロートを必要とする関数」というものはありません。しかし、さまざまなタイプでさまざまなことを行うオーバーロードされた関数があり、他の何かに文字列を追加することは「文字列を必要とする関数」であるという強い直感的な感覚があります。その意味で、Perl、Tcl、およびJavaScriptは多くの暗黙的な変換を行うように見えます("a" + 1
は"a1"
を提供します)、Pythonははるかに少ない("a" + 1
は例外を発生させますが、1.0 + 1
は2.0
*を提供します) )。その意味を正式な用語にまとめるのは難しいだけです。文字列とintを受け取る+
が、インデックス作成などの他の関数が明らかにあるのに、なぜあるべきではないのでしょうか。
*実際、現代のPythonでは、isinstance(2, numbers.Real)
がtrueであるため、OOサブタイピングの観点から説明できます。 2
がPerlまたはJavaScriptの文字列型のインスタンスであるという意味はないと思います... Tclでは、everythingは文字列のインスタンスであるため、実際にはそうです。
最後に、「強い」タイピングと「弱い」タイピングの完全に直交する別の定義があります。ここで、「強い」とは強力/柔軟/表現力を意味します。
たとえば、Haskellでは、数値、文字列、この型のリスト、または文字列からこの型へのマップである型を定義できます。これは、JSONからデコードできるものを完全に表現する方法です。 Javaでそのような型を定義する方法はありません。しかし、少なくともJavaにはパラメトリック(ジェネリック)型があるため、Tのリストを受け取り、要素がT型であることを知る関数を作成できます。初期のJavaのような他の言語では、オブジェクトのリストとダウンキャストを使用する必要がありました。ただし、少なくともJavaを使用すると、独自のメソッドで新しい型を作成できます。 Cでは、構造のみを作成できます。 BCPLにはそれさえありませんでした。アセンブリに至るまで、ビット長が異なるタイプのみです。
したがって、その意味で、Haskellの型システムは現代のJavaよりも強く、以前のJavaよりも強く、Cよりも強く、BCPLよりも強くなっています。
それで、Pythonはそのスペクトルにどこに適合しますか?それは少しトリッキーです。多くの場合、アヒルのタイピングを使用すると、Haskellでできることのすべてをシミュレートできます。確かに、コンパイル時ではなく実行時にエラーがキャッチされますが、それでもキャッチされます。ただし、ダックタイピングでは不十分な場合があります。たとえば、Haskellでは、空のintリストはintのリストであることがわかります。そのため、そのリストで+
を減らすと0 *が返されます。 Pythonでは、空のリストは空のリストです。 +
を減らすことで何をすべきかを判断するのに役立つ型情報はありません。
*実際、Haskellではこれを許可していません。空のリストで開始値を受け取らないreduce関数を呼び出すと、エラーが発生します。しかし、その型システムは十分に強力であるため、couldを機能させることができますが、Pythonでは機能しません。
'strongly typed' と 'dynamically typed' を混同しています。
文字列1
を追加して'12'
の型を変更することはできませんが、変数に格納する型を選択し、プログラムの実行中に変更できます。
動的型付けの反対は静的型付けです。 変数タイプの宣言は、プログラムの存続期間中は変わりません。強い型付けの反対は弱い型付けです。 valuesのタイプは、プログラムの有効期間中に変更できます。
この wiki Python によると、記事Pythonは動的かつ強く型付けされています(説明も良い)。
おそらく、静的に型付けされたプログラムの実行中に型が変更できず、型チェックが行われる言語について考えているコンパイル時に可能なエラーを検出します。
このSOの質問は興味深いかもしれません: 動的型言語対静的型言語 およびこのウィキペディアの記事 Type Systems 詳細情報を提供します
PythonのタイピングはDynamicですので、int変数を文字列に変更できます
x = 'somestring'
x = 50
Pythonの型付けはStrongですので、型をマージすることはできません。
'x' + 3 --> TypeError: cannot concatenate 'str' and 'int' objects
弱い型付けのJavascriptではこれが起こります...
'x'+3 = 'x3'
Javaは、オブジェクト型を明示的に宣言することを強制します
int x = 50
Kotlin は推論を使用して、int
であることを認識します
x = 50
ただし、両方の言語がstatic型を使用するため、x
をint
から変更することはできません。どちらの言語もdynamicのような変更を許可しません
x = 50
x = 'now a string'
すでに何度か回答されていますが、Pythonは強く型付けされた言語です。
>>> x = 3
>>> y = '4'
>>> print(x+y)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
JavaScriptでは次のようになります。
var x = 3
var y = '4'
alert(x + y) //Produces "34"
それが弱いタイピングと強いタイピングの違いです。弱い型は、コンテキスト(Perlなど)に応じて、ある型から別の型に自動的に変換しようとします。強い型neverは暗黙的に変換します。
混乱は、Pythonが値(一般に変数と呼ばれる)に値をバインドする方法の誤解にあります。
Pythonでは、名前には型がないため、次のようなことができます。
bob = 1
bob = "bob"
bob = "An Ex-Parrot!"
また、名前は何にでもバインドできます。
>>> def spam():
... print("Spam, spam, spam, spam")
...
>>> spam_on_eggs = spam
>>> spam_on_eggs()
Spam, spam, spam, spam
さらに読むには:
https://en.wikipedia.org/wiki/Dynamic_dispatch
わずかに関連しているがより高度なもの:
「厳密な型指定」という用語には明確な定義はありません。
したがって、この用語の使用は、誰と話しているかによって異なります。
変数の型が明示的に宣言されていないか、厳密に型付けされるように静的に型付けされていない言語は考えません。
厳密な型指定は、変換を禁止するだけではありません(たとえば、整数から文字列への「自動的な」変換)。代入(つまり、変数の型の変更)を排除します。
次のコードがコンパイル(解釈)される場合、言語は厳密に型指定されていません。
Foo = 1 Foo = "1"
強く型付けされた言語では、プログラマーは型を「当てにする」ことができます。
たとえば、プログラマが宣言を見た場合、
UINT64 kZarkCount;
そして、20行後でも、kZarkCountはUINT64であることがわかります(同じブロックで発生する限り)-介在するコードを調べる必要はありません。
Python変数には、値を表すターゲットオブジェクトへの型なし参照が格納されます。
割り当て操作とは、型指定されていない参照を割り当てられたオブジェクトに割り当てることです。つまり、オブジェクトは元の参照と新しい(カウントされた)参照を介して共有されます。
値の型は、参照値ではなく、ターゲットオブジェクトにバインドされます。 (強力な)型チェックは、値を使用した操作が実行されるときに実行されます(実行時)。
言い換えると、変数には(技術的に)型がありません-正確にしたい場合、変数型の観点から考えることは意味がありません。しかし、参照は自動的に逆参照され、実際にターゲットオブジェクトのタイプの観点から考えます。
この単純な例では、強力な型付けと動的な型付けの違いを説明する必要があります。
>>> tup = ('1', 1, .1)
>>> for item in tup:
... type(item)
...
<type 'str'>
<type 'int'>
<type 'float'>
>>>
Java:
public static void main(String[] args) {
int i = 1;
i = "1"; //will be error
i = '0.1'; // will be error
}
私はちょうどそれを記憶する素晴らしい簡潔な方法を発見しました:
.