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

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

ただいまの
回答率

87.49%

f.__name__ = unbound_method.__name__のような記述は何を意味しているのでしょうか?

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 289

score 6

オライリーのPythonクックブック第二版の中で以下のようなPython2.3や2.4で書かれた古いコードがあります。

class Proxy(object):
    """ すべてのプロキシクラスのベースクラス """
    def __init__(self, obj):
        super(Proxy, self).__init__()
        self._obj = obj
    def __getattr__(self, attrib):
        return getattr(self._obj, attrib)
def make_binder(unbound_method):
    def f(self, *a, **k):
        return unbound_method(self._obj, *a, **k)
    # Python2.4以降ではこうする
    f.__name__ = unbound_method.__name__
    return f
known_proxy_classes = {}
def proxy(obj, *specials):
    """ 特殊メソッドを移譲できるプロキシを作るファクトリ関数 """
    # 適当なカスタムクラスがあるのでは?
    obj_cls = obj.__class__
    key = obj_cls, specials
    cls = known_proxy_classes.get(key)
    if cls is None:
        # 既存のプロキシクラスがないので自動生成する
        cls = type("%sProxy" % obj_cls.__name__, (Proxy,), {})
        for name in specials:
            name = '__%s__' % name
            unbound_method = getattr(obj_cls, name)
            setattr(cls, name, make_binder(unbound_method))
        # 次に呼び出された時のためにキャッシュする
        known_proxy_classes[key] = cls
    # 必要なプロキシをインスタンス化して返す
    return cls(obj)
def empty_copy(obj):
    class Empty(obj.__class__):
        def __init__(self): pass
    newcopy = Empty()
    newcopy.__class__ = obj.__class__
    return newcopy

class YourClass(object):
    def __init__(self):
        ## assume there's a lot of work here
    def __copy__(self):
        newcopy = empty_copy(self)
        ## copy some relevant subset of self's attributes to newcopy
        return newcopy

if __name__ == '__main__':
    import copy
    y = YourClass()    # This, of course, does run __init__
    print(y)
    z = copy.copy(y)   # ...but this doesn't
    print(z)

これらの中で、

# Python2.4以降ではこうする
f.__name__ = unbound_method.__name__


newcopy.__class__ = obj.__class__


この部分が何のためにこのような処理を書き加えているのか教えて頂けないでしょうか?

また、現在のPython3でも上記コードは動きますが、Python3.x現在でも上記のようなコードを書き加えるものなのでしょうか?

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • yuki23

    2021/03/17 14:49

    その本には何と書いてありますか?

    キャンセル

  • ao_tombo

    2021/03/17 15:19

    質問ありがとうございます。以下のように書かれています。
    上のコードの説明が、
    「プロキシやメソッドの自動委譲は、Pythonでは__getattr__メソッドがあるおかげで楽に書くことが出来る。Pythonは属性の参照を行って上手くいかなかった場合、__getattr__メソッドを自動的に呼び出す。従来のオブジェクトモデルでは、__getattr__は特殊メソッドに対しても通常通りに適用された。(2007年)現在、新規に書くコードについては新しいスタイルのオブジェクトのみを使うことが推奨されている。いつの日か、これから何年か後にはPython3.0が従来のオブジェクトモデルを排除することになっている。新しいスタイルのオブジェクトモデルでは、Pythonは実行時に特殊メソッドの検索を行わず、クラスオブジェクトに用意された『スロット』のみを参照する。スロットはクラスオブジェクトが作成または修正された時のみ更新される。このため、ラッピングされたオブジェクトに特殊メソッドを委譲したいプロキシオブジェクトは、それ専用に仕立て上げられたクラスに属している必要がある。幸いにもレシピで示した通り、クラスを実行時に生成してインスタンス化するのはPythonではとても簡単である。~~~」

    下のコードの説明が、
    「__init__メソッドがハイコストなため、__copy__メソッドで当該のクラス(つまりself)の空インスタンスをまず作ることでこれをバイパスしたい場合も多い。もっともシンプルで汎用的な方法は、Pythonがインスタンスのクラスを動的に変更できることを利用するものである。つまりこのレシピのように、空のインスタンスを作るためのクラスをローカルに用意してそのクラスで新しいオブジェクトを作り、その__class__属性を差し替えてしまう方法である。空のインスタンスを作るEmptyクラスが、コピーしようとするオブジェクトのクラスobj.__class__から継承する部分は無害ではあるものの古いスタイルのクラスにおいては冗長な書き方かもしれない。しかしこの継承によって、このレシピは新旧両方ののクラススタイルで動作することが可能となっている。obj.__class__の継承を行う場合、Emptyクラスの__init__メソッドはオーバーライドする必要があり、そうしなければこのレシピは意味が無くなってしまう。こうして目的のクラスの空オブジェクトを作ったら、たいていはコピー元からselfへと、属性の一部をコピーする必要がある。~~~」
    、となっています。分かったような分からないような...という感じです。

    キャンセル

  • t_obara

    2021/03/17 16:09

    desriptorあたりを調べれば理解が進みそうですね

    キャンセル

  • ao_tombo

    2021/03/18 17:02

    アドバイスありがとうございます。デスクリプタも学習してみます。

    キャンセル

回答 2

checkベストアンサー

0

f.name = unbound_method.name について

A. 関数内で一時的にクラスや関数を動的に生成してるので、必要な情報のコピーしてます。

def make_binder(unbound_method):
    # unbound_method の実態は、各メソッドの関数で
    # 引数に インスタンス(self._obj) を加えて呼び出す新たな関数を
    # この関数内部で作ってます。

    def f(self, *a, **k):
        return unbound_method(self._obj, *a, **k)

    # この時点で f.__name__ は関数名の "f"

    # make_binder で内部生成された関数は全て "f" という名前になり
    # 区別がつかなくなってしまうので、元の関数名をコピーします。

    f.__name__ = unbound_method.__name__


    return f

スタックトレース情報のログに現れたり、
対話環境で、メソッドの情報を調べたりする時に用いたりします。

__name__ については、関数名・メソッド名の表示情報だけなので
そのレシピの本題である「委譲」自体の実行には支障ありません。

def funcA():
    "function A"
    pass

# 関数の情報を得られる
print(funcA.__name__) # => "funcA"
print(funcA.__doc__)  # => "function A"

def bind_nothing(func):
    def f(*a, **kw):
        return func(*a, **kw)
    return f

# 内部で生成された f() の情報が得られる
funcB = bind_nothing(funcA)
print(funcB.__name__) # => "f"
print(funcB.__doc__) # => None


def bind_with_name(func):
    def f(*a, **kw):
        return func(*a, **kw)
    f.__name__ = func.__name__ # 元の関数の情報をラップした関数へコピー
    return f

funcC = bind_with_name(funcA)
print(funcC.__name__) # => "funcA" <-- 元の関数名・メソッド名が得られる

このような内部関数で、関数名以外の情報をコピーするには、標準ライブラリの functools.wrapsを使って下さい。

他の __xxx__ の情報については、inspect 活動中のオブジェクトの情報を取得する モジュール辺りを参照。
内部情報的なもので、主にデバッガ等で用いられます。


現在のPython3でも上記コードは動きますが、Python3.x現在でも上記のようなコードを書き加えるものなのでしょうか?

__name__ や __class__ については、2.4以降 なので、共通です。

他の部分、クラスの定義の違いは、3.x 現在では区別不要ですが、2.x -> 3.x への移行期は
2.x と互換性のあるコードというのを明示する為に object 継承を記述したりしました。

# python 2.7

class YourClass: # old-style class

class YourClass(object): # new-style class
# python 3.x

class YourClass: # new-style class

class YourClass(object): # new-style class

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2021/03/18 17:05

    ありがとうございます!説明のコードが大変分かりやすかく、ようやく理解出来ました。ありがとうございました。

    キャンセル

0

下の方は以下のコードの実行結果を考えてください。

>>> class A:
...     def a(self):
...         print('Aです')

>>> class B: pass

>>> o = B()

>>> o.__class__ = A

>>> o.a()
Aです

o.aが参照された時、属性aに関して、まずインスタンス変数を探索します。なかったなら次はクラスから探索します。
__class__を差し替えることでそこに干渉して、探索先をBからAにしています。
クラスAaという名前を関数オブジェクトが束縛しているので、属性a探し出されたことになって、結果メソッドとして実行されています。

newcopy.__class__ = obj.__class__によって、インスタンス変数から見つからなかった属性の探索先がEmptyからobj.__class__に差し替わるので、クラス変数やメソッドへの参照がobj.__class__に対しておこなわれるようになります。


上の方は、これを無くしたとしても実行時に問題は起きることはないんじゃないでしょうか。(やっておくに越したことはないにせよ)
無いときの弊害はちょっと思いつきません。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2021/03/18 17:07 編集

    なるほど、そのような挙動が行われていたのですね。大変参考になりました。教えて頂きありがとうございました。

    キャンセル

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

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

関連した質問

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