48522


"パーフェクトpython"を見て、Echoアプリケーションを作っていましたが、「python3
client.py」と実行すると、以下のエラーが発生します。どういう意味でしょうか？もちろん、server側のプログラムも立ち上げています。  
なお、client側とserver側のコードも示しています。（教材に書いているコードそのままなんですけど。。。）  
解決策も教えていただけませんでしょうか？

【エラー内容】

[code]

    Traceback (most recent call last):
      File "client.py", line 76, in <module>
        main()
      File "client.py", line 68, in main
        root.after(200, functools.partial(idle_task, root))
      File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 752, in after
        callit.__name__ = func.__name__
    AttributeError: 'functools.partial' object has no attribute '__name__'
    
[/code]

client側コード

[code]

    # -*- coding::utf-8 -*-
    import tkinter
    import asyncore
    import functools
    import sys
    class EchoView(tkinter.Frame):
        """ Echo User Interface """
        def __init__(self, master):
            super(EchoView, self).__init__(master)
            self.listcontainer = tkinter.Frame(self)
            self.listbox = tkinter.Listbox(self.listcontainer)
            self.yscroll = tkinter.Scrollbar(self.listcontainer)
            self.listbox.pack(side=tkinter.LEFT, expand=True, fill=tkinter.BOTH)
            self.yscroll.pack(side=tkinter.LEFT, expand=True, fill=tkinter.Y)
            self.listcontainer.pack(expand=True, fill=tkinter.BOTH)
            self.entry = tkinter.Entry(self)
            self.entry.pack(side=tkinter.BOTTOM, expand=True, fill=tkinter.X)
        def get_submit_messegae(self):
            data = self.entry.get()
            self.entry.delete(0, tkinter.END)
            return data
        def show_message(self, message):
            self.listbox.insert(tkinter.END, message)
            self.listbox.see(tkinter.END)
    class EchoClient(asyncore.dispatcher_with_send):
        def __init__(self, view):
            super(EchoClient, self).__init__()
            self.create_socket()
            self.buffers = []
            self.view = view
            self.bind_all()
        def bind_all(self):
            self.view.entry.bind('<Return>', self.on_submit)
        def on_submit(self, event):
            message = self.view.get_submit_messegae()
            self.buffers.append(message.encode('utf-8'))
        def handle_write(self):
            if not self.buffers:
                return
            buffer, self.buffers = self.buffers[0],self.buffers[1:]
            self.send(buffer)
        def writable(self):
            return self.buffers
        def handle_read(self):
            message = self.recv(8192)
            self.view.show_message(message.decode('utf-8'))
    def idle_task(root):
        try:
            asyncore.loop(count=1, timeout=1)
        finally:
            root.after(200, functools.partial(idle_task, root))
    def main():
            root = tkinter.Tk()
            root.after(200, functools.partial(idle_task, root))
            view = EchoView(root)
            view.pack(expand=True, fill=tkinter.BOTH)
            client = EchoClient(view)
            client.connect(('localhost', 8080))
            root.mainloop()
    if __name__ == '__main__':
            main()
    
[/code]

server側コード

[code]

    import asyncore
    class EchoHandler(asyncore.dispatcher_with_send):
        def __init__(self, socket, parent):
            super(EchoHandler, self).__init__(socket)
            self.parent = parent
        def handle_read(self):
            data = self.recv(8192)
            if data:
                self.parent.send(data)
    class EchoServer(asyncore.dispatcher):
        def __init__(self, host, port):
            asyncore.dispatcher.__init__(self)
            self.handlers = []
            self.create_socket()
            self.set_reuse_addr()
            self.bind((host, port))
            self.listen(5)
        def handle_accepted(self, sock, addr):
            print('Incoming connection from %s' % repr(addr))
            handler = EchoHandler(sock)
            self.handlers.append(handler)
        def send(self, data):
            for handler in self.handlers:
                handler.send(data)
    server = EchoServer('localhost', 8080)
    syncore.loop()
    
[/code]

※lambda関数を使用した際のエラーメッセージ

> Incoming connection from ('127.0.0.1', 51076)  
>  error: uncaptured python exception, closing channel  
>  < **main**.EchoServer listening localhost:8080 at 0x107e18f28> (: **init**
> () missing 1 required positional argument: 'parent'  
>
> [/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncore.py|read|83]  
>  [/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncore.p  
>  y|handle_read_event|416]  
>  [/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncore.p  
>  y|handle_accept|493] [server.py|handle_accepted|27])


CC BY-SA 4.0
2018-09-18T15:05:53.703
48514
2018-09-20T20:50:32.007
2018-09-20T20:50:32.007
28416
28416
question
python
pythonのtkinterを使ったEchoアプリでエラーがでる。（AttributeError 'functors.partial' obj has no attribute '__name__')


idle_task関数とmain関数の`root.after`から始まる行を下記のように書き換えて動作を確認してみてください。

修正前: `root.after(200, functools.partial(idle_task, root))`  
修正後: `root.after(200, lambda:idle_task(root))`

@metropolis さんの引用の通り、tkinterのバージョンアップで存在しない`__name__`属性を取りに行くことがエラーの原因です。

この回答は別サイトでの類似質問の[ベストアンサー](https://teratail.com/questions/54921#reply-88044)と同一です。  
リンク先のコメントも引用します。

> 実際の Python のプログラムでは、コールバックを引数に渡すようなところでは、  
>  partial の利用が推奨されてます。例えば、asyncio というモジュールのコールバックの説明では  
>  <http://docs.python.jp/3.5/library/asyncio-eventloop.html#calls>
>

>> 注釈 lambda 関数よりも functools.partial() を使用しましょう。 asyncio はデバッグモードで引数を表示するよう
functools.partial() オブジェクトを精査することが出来ますが、lambda 関数の表現は貧弱です。

※server側コードの`syncore.loop()`を`asyncore.loop()`に書き直して動作確認しました。


CC BY-SA 4.0
2018-09-19T00:40:34.667
48522
2018-09-19T00:40:34.667
9820
48514
answer