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

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

新規登録して質問してみよう
ただいま回答率
85.35%
Python 2.7

Python 2.7は2.xシリーズでは最後のメジャーバージョンです。Python3.1にある機能の多くが含まれています。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

2604閲覧

質問内コードの__setattr__と、プライベート変数について教えて下さい

ao_tombo

総合スコア7

Python 2.7

Python 2.7は2.xシリーズでは最後のメジャーバージョンです。Python3.1にある機能の多くが含まれています。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/04/09 05:46

編集2021/04/09 06:39

以下のコード中のclass GenericWrapper内のコードで分からないところがあります。

Python2.4

1#! /usr/bin/env python 2# coding=utf-8 3 4def wrap_callable(any_callable, before, after): 5 ''' あらゆる呼び出し可能型(callable)を前後にコールを入れる形でラップする ''' 6 def _wrapped(*a, **kw): 7 before() 8 try: 9 return any_callable(*a, **kw) 10 finally: 11 after() 12 _wrapped.__name__ = any_callable.__name__ 13 return _wrapped 14 15import inspect 16 17class GenericWrapper(object): 18 ''' オブジェクトのメソッド全てを前後にコールに入れる形でラップする ''' 19 def __init__(self, obj, before, after, ignore=()): 20 # __setattr__をバイパスするため__dict__に直接セットするのが必須 21 # ゆえにダブルアンダースコア名のためname-manglingの再生成が必要 22 clasname = 'GenericWrapper' 23 self.__dict__['_{}__methods'.format(clasname)] = {} 24 self.__dict__['_{}__obj'.format(clasname)] = obj 25 for name, method in inspect.getmembers(obj, inspect.ismethod): 26 if name not in ignore and method not in ignore: 27 self.__methods[name] = wrap_callable(method, before, after) 28 29 def __getattr__(self, name): 30 try: 31 return self.__methods[name] 32 except KeyError: 33 return getattr(self.__obj, name) 34 35 def __setattr__(self, name, value): 36 setattr(self.__obj, name, value) 37 38class SynchronizedObject(GenericWrapper): 39 ''' オブジェクトとその全メソッドを同期機構でラップする ''' 40 def __init__(self, obj, ignore=(), lock=None): 41 if lock is None: 42 import threading 43 lock = threading.RLock() 44 GenericWrapper.__init__(self, obj, lock.acquire, lock.release, ignore) 45 46if __name__ == '__main__': 47 import threading 48 import time 49 class Dummy(object): 50 def foo(self): 51 print("Foo") 52 time.sleep(1) 53 def bar(self): 54 print("Bar") 55 def baaz(self): 56 print("Baaz") 57 tw = SynchronizedObject(Dummy(), ignore=['baaz']) 58 threading.Thread(target=tw.foo).start() 59 time.sleep(0.1) 60 threading.Thread(target=tw.bar).start() 61 time.sleep(0.1) 62 threading.Thread(target=tw.baaz).start() 63

ここで、2点分からないことがあります。

❶以下のコメント部が何を言っているのかが分かりません。

# __setattr__をバイパスするため__dict__に直接セットするのが必須 # ゆえにダブルアンダースコア名のためname-manglingの再生成が必要

self.__methodsself.__objが何なのか分かりません。
self.__dict__['_GenericWrapper__methods']などと関連があるのでしょうか?

アドバイス頂けたらありがたいです。
宜しくお願いします。

ちなみに上記のコードが書かれている書籍には以下のようなことが書かれています。

このレシピではオブジェクト属性への直接アクセス(getやset)をラップしていない。オブジェクトのロックを考慮しつつ直接アクセスがしたい場合、ラッパーの特殊メソッド__getattr__および__setattr__でそれぞれのビルトイン関数getattrおよびsetattrをコールする周辺にtry/finallyのロッキング・イディオムを加える必要がある。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

y_waiwai

2021/04/09 06:10

それはどこから持ってきたコードなんでしょうか
ao_tombo

2021/04/09 06:16

書籍『第2版Pythonクックブック 』の『9.1 オブジェクト中の全メソッドを同期』からです
quickquip

2021/04/09 06:23 編集

> なぜかダブルアンダースコアが消えています markdownの書式指定になっているからです。単語の前後を ` で括りましょう。それで 文章中のコード で扱われます。 https://teratail.com/help#about-markdown
ao_tombo

2021/04/09 06:39

できました。ありがとうございます。
ao_tombo

2021/04/13 08:07 編集

__setattr__()は、例えばa = A()、a.x = valのような式がある場合に呼び出されるものだと認識していますが?間違っていますでしょうか?
guest

回答1

0

ベストアンサー

ひとまず①のことは忘れて、うまく動かないけれど分かりやすい途中経過を見るのが②のためにはいいと思いました。

これです。

python

1class GenericWrapper(object): 2 ''' オブジェクトのメソッド全てを前後にコールに入れる形でラップする ''' 3 def __init__(self, obj, before, after, ignore=()): 4 self.methods = {} 5 self.obj = obj 6 for name, method in inspect.getmembers(obj, inspect.ismethod): 7 if name not in ignore and method not in ignore: 8 self.methods[name] = wrap_callable(method, before, after) 9 10 def __getattr__(self, name): 11 try: 12 return self.methods[name] 13 except KeyError: 14 return getattr(self.obj, name) 15 16 def __setattr__(self, name, value): 17 setattr(self.obj, name, value)

self.objは単純に初期化子__init__に渡されたobjを参照するのに使っています。
self.methodsobjの属性の中でメソッドであるものを 名前→wrap_callableでラップした関数 という形のマッピングを保持するのに使っている辞書です。
②の答えはこれで終わりです。

GenericWrapper型のインスタンスxに対して
例えばx.some_attrと参照された時、some_attrが元のobjのメソッドだったらwrap_callableでラップされた関数を返し、そうでなければobj.some_attrの結果を返すのが__getattr__です。
例えばx.some_attr = hogeと代入されたら、obj.some_attr = hogeを実行するのが__setattr__です。

単純にそれだけです。


ここにはうまく動かない点と、あまりよくない点があります。それが①に繋がります。

うまく動かない点は__init__の1行目、self.methods = {}です。
self.methods = {}を実行すると、__setattr__呼び出されてしまうのです。
さらにその中でself.objを参照しています。この時、属性objはまだセットされていませんから属性の解決に失敗して__getattr__呼び出されてしまうのです。

つまり。
__setattr____getattr__が正しく動くためにはself.objself.methodsが必要
だが
self.objself.methodsを普通に代入すると準備が終わっていない__setattr____getattr__が呼び出されてしまう
のです。

それを回避するために
__setattr__をバイパスするため__dict__に直接セットするのが必須
だと言っているわけです。


まだあまりよくない点があります。
このままだとobj自身がmethodsobjという属性を持っていた時、それらには決してアクセスできません。GenericWrapperでセットしたmethodsobjが取り出されてしまいます。
これは普通の継承でも起きる話です。
他のクラススタイルのオブジェクト指向プログラミング言語だとprivateアクセス指定子で解決するのが多いでしょう。
Pythonではname-manglingで解決します。
ここはこれ以上解説しません。公式ドキュメントからの引用にとどめます。

https://docs.python.org/ja/3/tutorial/classes.html#private-variables

クラスのプライベートメンバについて適切なユースケース(特にサブクラスで定義された名前との衝突を避ける場合)があるので、名前マングリング (name mangling) と呼ばれる、限定されたサポート機構があります。 __spam (先頭に二個以上の下線文字、末尾に一個以下の下線文字) という形式の識別子は、 _classname__spam へとテキスト置換されるようになりました。

python

1self.__methods = {}

と書きたいのですがそれができないため"__dict__に直接セットするのが必須"なのでした。
かわりにこれと等価になるように__dict__にセットするコードを書こうとすると、そこでセットするべき名前はname-manglingの仕様で変換された後の名前でなければなりません。

ゆえにダブルアンダースコア名のためname-manglingの再生成が必要
はそういう意味です。

投稿2021/04/10 04:21

編集2021/04/10 04:26
quickquip

総合スコア11235

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ao_tombo

2021/04/13 05:10

回答、ありがとうございます。自分が思っていたよりも複雑なもので驚きました。非常にべんきょうになりました。本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問