12A猫で学んだこと-Memoir-

...What are you learning now?

(Python/Role Play) Brother's protection -Example of Descriptor-

こんにちは。StudentSです。
最近、Fluent Pythonという本を読んでいます。

自分の中の朧げな「こんなことがしてみたい」というのを楽にできるようになりたいですね。
ありがたいことに世界の方々のおかげで技術が進歩しているけれど、自分が技術を楽しんだり、面白いことを創り出すためには、自分の能力を上げなきゃいけないんだろうなと思う5月の夕方です。

人狼に参加させて感じていることは、色々なバックグランドの人がいるから面白いということです。
その人の人狼以外の趣味が、人狼のゲーム中の思考のパターンに反映されるんだろうなと感じます。

  • 論理性の強いゲームが趣味の人は始めに、詰み進行の探索や確率論に基づく最適進行を考えるだろうし。
  • 人間観察が得意な人は始めに、発言を精査するだろうし。

様々な要素があるゲームだからこそ、色々なアプローチを見ることができるんだと思います。

不器用な私は、残念ながらプレイヤーごとの思考体系を読んで戦略を立てるのが苦手ですが、 他の参加者やGMさんの考え方に触れて刺激をもらっています。

自分ができるようになりたい、と思ったことができるようになっていけば、 また、少し違った人狼プレーもできると信じて。 このブログは自分の個人的なメモだけれど、それでも定期的に書くことには何か意味があると思うのです。

話しがすごく脱線しましたが、 今月のブログの内容です。
PythonにDescriptorという仕組みがあって、今まで勘違いしていたので会話口調でメモを作成しました。
Fluent Pythonと下記のページが元ネタです。

さて、このエントリの対象者は次の人です

  • Pythonを知っている
  • 私のRole Playを楽しんでくれる慈悲深い人である

登場人物は、Pythonの課題が出された高校生。課題を解くためにChatで友達としゃべりながら取り組んでいるようです。

とある高校生の就寝前

今日もお疲れ様! わたしはまだ、Pythonの課題が終わっていないから眠れないんだ... 課題は妹を守るっていうってふざけた問題なんだけど、 たぶん、これはインスタンス属性へのアクセスをフックすることができればいいんだ。

まずさ、Pythonの基本として クラスのみんなで決めたことよりも、自分が決めたことが優先される が基本だよね。 だから、クラスの属性よりも自分が設定した属性のほうが優先されるよね。

class Player:
    description = "Player"
    def __init__(self):
        pass

if __name__ == "__main__":
    student = Player()
    assert  "description" not in student.__dict__, "attribute does not exist in __dict__"
    assert student.description ==  Player.description == "Player"

    #student.description = "StudentS"
    setattr(student, "description", "StudentS")
    assert  "description" in student.__dict__, "attribute exists in __dict__"

    del student.description

    assert  "description" not in student.__dict__, "attribute does not exist in __dict__"
    assert student.description ==  Player.description == "Player"

何もしないと、属性を自由に書き換えられてしまうんだけど、属性へのアクセスの仕方を変えることができるのがDesciptorなんだ。 今日の課題となって渡されているプログラムはこれ。

class Teacher:
    def __call__(self, girl):
        raise RuntimeError("検閲により削除")

class You:
    """ [Implement]
    """

class Imoto:
    boy_friend = You()

    def check(self):
        if isinstance(self.boy_friend, You):
            print("やっぱり、お兄ちゃんはまだ私がいないとだめだなぁ!")
        elif isinstance(self.boy_friend, Teacher):
            print("お兄ちゃんなんてどうでもいい!")
            self.act()
        elif isinstance(self.boy_friend, You):
            print("たどり着くのは難しいって分かっている。")
            print("この穏やかな関係を1回壊して、もう1回関係を作り直さなきゃいけないから。")
            print("でも、ここが、お兄ちゃんとの理想の関係。")
            print("私たちの到達点だよ!")

    def act(self):
        self.boy_friend(self)

if __name__ == "__main__":
    """ :今日の課題:
    君の妹はとても可愛い。幼いながらも知的で純粋な目を僕に向けてくれる。
    冷めた目で僕を嘲笑する君とは大違いだよ。兄弟で全然性格が違うね。
    僕は君の妹に僕だけをみていて欲しいと強く願うんだ...

    君は可愛い妹の兄だ。
    上記は職員の新年会で酒に酔ったとある教師がつぶやいていた文章である。
    教師の妹に対するアプローチをYouクラスを実装することで制限せよ。
    1. Runtime Error を発生させないようにせよ
    2. Imotoクラスcheck関数で"ここが、お兄ちゃんとの理想の関係。"が表示されるパスを通るようにせよ。
    """
    imoto = Imoto()
    teacher = Teacher() 
    imoto.boy_friend = teacher
    imoto.check()

問題設定がひどいよね。 なんで兄限定にしてんだか。 フィクションの兄妹関係に幻想もちすぎじゃないの?
pythonのコードと同じぐらいのどーでもいい文章量...この設定不要でしょ。
しかもさ、教師として教師の売春をネタにするって自虐が強すぎるし。

まぁ、文句はあるけれど1問目から考えていこうか。

大切なところは

imoto.boy_friend = Teacher()

だね。ここでimotoの属性が書き換えられちゃてる。
他のプログラミング言語だったら、アクセス修飾子をつけることで解決できるよね。
JAVAC++privateとか。恋人とかの話は簡単にするものじゃないから、privateにしておくべきだよね。
わたしに妹はいないけれど、もしいたら、"好き"とかそういう話はきっと2人っきりのときにしかしないよ。

そうそう、Descriptorの話。

class You:
    """ [Implement]
    """
    def __get__(self, obj, type=None):
        pass

    def __set__(self, obj, value):
        pass

細かいところは、Tutorial をみるとして、 get, setを定義しておけば、インスタンスの属性のアクセスの時にget関数やset関数を呼び出してくれる。

これがDescriptorの役割だね。

問題1だけならとっても簡単。 Descriptorを使ってteacherboy_friend 属性に代入するところの処理を無視するようにすればいいだけ。

class You:
    def __set__(self, obj, value):
        pass

最低限これだけ書けば、少なくとも、妹のboy_friend属性は守ることができるね。 本当は、

class You:
    def __set__(self, obj, value):
        if isinstance(value, Teacher):
            del value

ってしたいけどね! こうすれば、ふざけた教師は消せるからね。

はい。これで1問目は終わり。 次の問題がちょっと難しいね...

if isinstance(self.boy_friend, You):
    print("やっぱり、お兄ちゃんはまだ私がいないとだめだなぁ!")
elif isinstance(self.boy_friend, Teacher):
    print("お兄ちゃんなんてどうでもいい!")
    self.act()
elif isinstance(self.boy_friend, You):
    print("たどり着くのは難しいって分かっている。")
    print("1回壊して、もう1回関係を作り直さなきゃいけないから。")
    print("でも、ここが、お兄ちゃんとの理想の関係。")
    print("私たちの到達点だよ!")

このプログラムはひどいよね。 if/elifで同じ条件を使うって、馬鹿なの?
素直にこの部分を書き直せよっていいたい。すごく言いたいなー

それにさ、この問題の作成者のImotoという概念の使い方が低俗だと思うんだよね。
ちょっとした環境の変化で家族関係が変わってしまうのは考えにくいはずだよ。
兄弟の関係性にだけ焦点をあてて、ちょっと刺激的な文章を書けばいいと考えている
作者の"甘さ"が透けるんだよ。
人間どうしの関わりを観察するとき、定型的な関係を記号的にとらえるだけじゃ駄目だよね。

あ、問題の話だったねー
そうだね、君の言う通り早く宿題を終わらせるのが大事だよね。
この"歪んだ関係"を直すためには、歪んだプログラムが必要だね。
副作用、変数にアクセスする度に結果が変わる仕組みを使うしかないね。

class You:
    flag = False
    def __get__(self, obj, type=None):
        if not You.flag:
            You.flag = True
            return None
        return self

    def __set__(self, obj, value):
        pass

クラス変数を使ってこうすればできるけどさ、これはひどいよねー
こんなのdebug用にboy_friend属性にアクセスしただけでも破綻するプログラムだもんね。

歪んだ関数自体を直すという発想からは、関数自体を書き換えるという方針がありだね。
関数自体の中のパスを通すという課題からは少しずれてしまっているかもしれないけれど。
まあ、これもひどいプログラムだよ....

class You:
    def dummy_check(self, obj):
            print("たどり着くのは難しいって分かっている。")
            print("この穏やかな関係を1回壊して、もう1回関係を作り直さなきゃいけないから。")
            print("でも、ここが、お兄ちゃんとの理想の関係。")
            print("私たちの到達点だよ!")

    def __set__(self, obj, value):
        setattr(obj, "check", lambda: self.dummy_check(obj))

なんか疲れたなー
もう夜の2時か...なすごく不毛な宿題だったような気がする。
プログラムを読むより、日本語を読んでいた時間の方が長いような気がするねー
君とぼんやりでも話ながらやらないと、精神的にきつかったよ。
ごめんね、こんな時間まで付き合わせちゃって。 お互い早く寝たいよねー
ここまで付き合ってくれてありがと。
君の宿題も終わったよね。 また明日、学校でね。

今月の反省

  • RPの主人公は幼馴染の男の子とチャットしながら宿題をやっている女の子を目指しました。 ちょっと失敗感..
  • やりたいことが不明瞭になっていますね。 RPの中途半端さと、Python Descriptorの説明もどっちも中途半端なような気がします。 問題だけに焦点を当てるならもう少し短くまとめてもよかったし...
  • 文章の論旨が非常に不明確ですね。これは呪殺対応にてこずっていた偽でしょう。多分狂人。