質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.63%

Python typeオブジェクトに__call__メソッドがないのにcallableなのは何故か?また仕様はどうなっているのか?

解決済

回答 2

投稿

  • 評価
  • クリップ 2
  • VIEW 1,864

_Victorique__

score 1258

題名の通りです。callメソッドがないのにも関わらず呼び出しが出来てしまいます。
また、その挙動がよく分かりません。実行してみた感じだとそのオブジェクトの初期値?的なものが出力されているように感じます。
引数を与えるとその値がそのまま出力されます。ここら辺の仕様が確認できるところはありますでしょうか?

import pprint

n = 1
n_type = type(n) # <class 'int'>
n_type() # 0
n_type.__call__() # 0

pprint.pprint(dir(n), compact=True)
"""
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__',
 '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__',
 '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__',
 '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__',
 '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__',
 '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__',
 '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__',
 '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__',
 '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__',
 '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__',
 '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__',
 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator',
 'real', 'to_bytes']
"""
pprint.pprint(dir(type(n)), compact=True)
"""
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__',
 '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__',
 '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__',
 '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__',
 '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__',
 '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__',
 '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__',
 '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__',
 '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__',
 '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__',
 '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__',
 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator',
 'real', 'to_bytes']
"""
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • otn

    2019/03/20 16:00

    n_type.__call__
    ⇒ <method-wrapper '__call__' of type object at ~~>
    ですね。「method-wrapper」ググったけどよくわからず。

    キャンセル

  • _Victorique__

    2019/03/20 16:05

    > otn様
    こういう見えない部分があると凄く気になってしまいます。
    何か別のものが呼ばれて最終的にcallが呼ばれているってことなんでしょうかね?
    よくわからないです。

    キャンセル

回答 2

checkベストアンサー

+2

自分も type() の返す type オブジェクトに対して、呼び出しメソッドを使ったことがないので、知らなかったのですが、流れを追ってみたら以下のようです。

callable かどうかの判定

Python に置ける callable かどうかは __call__ が定義されているかどうかではなく、厳密にはオブジェクトの tp_call ポインタが NULL かどうかで見ている。
なので、__call__ を定義していなくても、tp_call が NULL でなかったら、callable である。

int PyCallable_Check(PyObject *x)
{
    if (x == NULL)
        return 0;
    return x->ob_type->tp_call != NULL;
}


cpython/object.c at master · python/cpython

An optional pointer to a function that implements calling the object. This should be NULL if the object is not callable. The signature is the same as for PyObject_Call()

Type Objects — Python 3.7.3rc1 documentation

type オブジェクトに対して呼び出すと、なにが呼ばれるか

type() が返す type オブジェクトは組み込み型なので、tp_call がどうなっているのかと見てみたところ、NULL (0) ではなく、type_call() という関数が設定されていた。

(ternaryfunc)type_call,                     /* tp_call */


cpython/typeobject.c at 3.6 · python/cpython

この関数の中身を見てみると、

static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj;

    if (type->tp_new == NULL) {
        PyErr_Format(PyExc_TypeError,
                     "cannot create '%.100s' instances",
                     type->tp_name);
        return NULL;
    }

    obj = type->tp_new(type, args, kwds);

略

    return obj;
}

cpython/typeobject.c at 3.6 · python/cpython

tp_new() で type (ここでの type 変数は type() 引数に渡したオブジェクトのクラスのこと) のインスタンスを作成して返しているようでした。
これが type(Class) が返す type オブジェクトを呼び出すと、元の Class のインスタンスが作成されるという挙動につながっているのではないでしょうか。

実行してみた感じだとそのオブジェクトの初期値?的なものが出力されているように感じます。

class Hoge:
    pass

hoge1 = Hoge()
hoge_type = type(hoge1)
hoge2 = hoge_type()

# 同じクラス
print(type(hoge1) is type(hoge2))  # True

ちなみにint オブジェクトは引数を与えずコンストラクタを作成すると、値は0になります。

a = int()
print(a)  # 0

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/20 16:46

    詳しい回答有難う御座います!
    なんか不思議な仕様ですね。挙動は概ね予想通りでしたが何故こんな仕様なのでしょうか?
    何が嬉しいのかが理解できません。tp_callのポインタがNULLじゃないのはやっぱりそれだけの理由があるということなんでしょうね。。。

    キャンセル

  • 2019/03/20 16:56 編集

    どのライブラリにもドキュメントに明文化されていない仕様 (例えば、ドキュメントに乗っていない関数であったり、挙動であったり) というのがあります。
    それらは開発者が内部的な実装の都合等でそうしたものですが、ドキュメント化されていないということは、外部の利用する側の人にその仕様を利用してもらうことは想定していないということです。
    上記の挙動も、CPython の開発者でないのでわかりませんが、内部的になんらかの都合があり、そのようにしているのだと思います。しかし、ドキュメントに載っていないということは、Python を使う側が利用することは想定していないと思うので、その挙動 (隠し機能のようなもの) はコードを書く際に使わないほうがよいと思います。(将来的に言及なしに急に変わったりする可能性もあるため)

    普通に Python を使っていれば、type() は型を確認するのに使うぐらいで、その返り値の type オブジェクトを呼び出したいというケースはないと思います。

    キャンセル

  • 2019/03/20 17:02

    確かにそうですね。深追いはしないことにします。
    偶発的に呼び出してしまって疑問に思ったため質問させていただきました。
    有難う御座いました!

    キャンセル

0

Pythonの組み込み関数のドキュメントが手掛かりでしょうね。

type

引数が1つだけの場合、object の型を返します。返り値は型オブジェクトで、一般に object.class によって返されるのと同じオブジェクトです。

とあるので、最初にtypeを使うとintクラスが返されます。

callable

object 引数が呼び出し可能オブジェクトであれば True を、そうでなければ False を返します。この関数が真を返しても、呼び出しは失敗する可能性がありますが、偽であれば、 object の呼び出しは決して成功しません。なお、クラスは呼び出し可能 (クラスを呼び出すと新しいインスタンスを返します) です。また、インスタンスはクラスが call() メソッドを持つなら呼び出し可能です。

クラスなので呼び出し可能です。この場合に呼び出されてるのは__call__ではなく__init__ですね。

In [36]: class A:
    ...:     def __init__(self):
    ...:         print("__init__ of class A")
    ...:     def __call__(self):
    ...:         print("__call__ of class A")
    ...:
    ...:

In [37]: a = A()
__init__ of class A

In [38]: b = type(a)

In [39]: b()
__init__ of class A
Out[39]: <__main__.A at 0x10aed1710>

In [40]: b()()
__init__ of class A
__call__ of class A

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.63%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • Pythonに関する質問
  • Python typeオブジェクトに__call__メソッドがないのにcallableなのは何故か?また仕様はどうなっているのか?