こんばんは。ヤマモトです。
夜更かししているわけではない!! 究極の早起きをしただけだ!!!
なんか今日は疲れ果てて21時頃に寝てしまい、起きたら深夜の1時だったんよね。。。
う~ん。中途半端。
さてさて。今回の話題。
先日、WordPressの記事投稿を行った。
ここでは投稿だけをやったのだが、python の wordpress_xmlrpc だと既存記事の一覧も取得できるとのこと。例えば投稿済みの記事を編集したいときとかに、以下の流れで作業する時とかに使える。
記事一覧取得 → 該当記事のIDを取得 → IDをキーにして記事を呼び出し → 編集
ということで、試しに使ってみよ~・・・っていう流れで掲題の通り躓いたという話である。
今日はその経緯と原因と対策についてお話しする。
以下の同じ悩みで詰まっている人は注目。
- python 3.8 以上で python-wordpress-xmlrpc を使っている人
- GetPosts() で失敗する人
- GetPosts() で ”
module 'collections' has no attribute 'Iterable'
” みたいなエラーが出ちゃう人。
記事一覧の取得失敗の経緯
記事取得のコードとエラーメッセージ
# 必要なモジュールを持ってくる
from wordpress_xmlrpc import Client, methods
from wordpress_xmlrpc.methods.posts import GetPosts
# client情報
url = 'https://arefukeblog.com/xmlrpc.php' # トップページのアドレス末尾に/xmlrpc.phpを付ける
user = 'yamatoXXXXXX' # WordPressのユーザ名
password = 'ABCDXXXXXX' # WordPressのパスワード
client = Client(url, user, password)
# 記事一覧の取得。出力はリスト型。
posts = client.call(GetPosts())
ただ、上記を実行してみると以下のエラーが出てきてしまった。
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 posts = client.call(GetPosts())
File ~\anaconda3\envs\py310\lib\site-packages\wordpress_xmlrpc\base.py:46, in Client.call(self, method)
44 else:
45 raise
---> 46 return method.process_result(raw_result)
File ~\anaconda3\envs\py310\lib\site-packages\wordpress_xmlrpc\base.py:128, in XmlrpcMethod.process_result(self, raw_result)
126 if isinstance(raw_result, dict_type):
127 return self.results_class(raw_result)
--> 128 elif isinstance(raw_result, collections.Iterable):
129 return [self.results_class(result) for result in raw_result]
131 return raw_result
AttributeError: module 'collections' has no attribute 'Iterable'
collections
モジュールに Iterable
って関数が無いよ!、ってことだと思うが。。。
あれ、でもこれってpythonに標準で装備されてるモジュールじゃね??
・・・(この後めちゃくちゃ検索した)・・・
エラー調査
エラーの箇所の調査
githubに python-wordpress-xmlrpc
の中身が公開されていたのでそちらを追ってみる。
今回の場合、GetPosts
メソッドでエラーが吐かれていたので、import した順に辿っていって、methods/posts.py
の中にある class の GetPosts
を発見。
from wordpress_xmlrpc.base import *
from wordpress_xmlrpc.wordpress import WordPressPost, WordPressPostType
class GetPosts(AuthenticatedMethod):
"""
Retrieve posts from the blog.
Parameters:
`filter`: optional `dict` of filters:
* `number`
* `offset`
* `orderby`
* `order`: 'ASC' or 'DESC'
* `post_type`: Defaults to 'post'
* `post_status`
Returns: `list` of :class:`WordPressPost` instances.
"""
method_name = 'wp.getPosts'
optional_args = ('filter', 'fields')
results_class = WordPressPost
## 以下、略 ##
ただ、こいつはさらに base.py
から AuthenticatedMethod
→ XmlrpcMethod
の順に呼び出しているようなので、そっちを追っていく。
import collections
## 中略 ##
class XmlrpcMethod(object):
## 中略 ##
def process_result(self, raw_result):
"""
Performs actions on the raw result from the XML-RPC response.
If a `results_class` is defined, the response will be converted
into one or more object instances of that class.
"""
if self.results_class and raw_result:
if isinstance(raw_result, dict_type):
return self.results_class(raw_result)
elif isinstance(raw_result, collections.Iterable): ## ここが問題の箇所 !!!
return [self.results_class(result) for result in raw_result]
return raw_result
collections
モジュール自体はコンテナデータ型と呼ばれる、list型みたいな配列処理の進化版らしい。(知らんかった)
いずれにせよ、ごく普通のモジュールらしい。現に↑の base.py
でも冒頭で普通にimportして使ってる。
エラー原因の特定
前回、参考記事として載せた以下のサイトでは、python 3.7.9 の環境で実行しているようだった。
ヤマモトの仮想環境はpython 3.10.4 なのでそれかも??
ってことで、試しにpython 3.7の環境で実行してみる。
(base) C:\Users\ヤマモト>conda list python
# packages in environment at C:\Users\ヤマモト\anaconda3:
#
# Name Version Build Channel
python 3.7.6 h60c2a47_2
## baseのpythonのバージョンが3.7なので、こいつでjupyter labを起動する。
(base) C:\Users\ヤマモト>jupyter lab
## import とかは省略 ##
posts = client.call(GetPosts())
for post in posts:
print(post.id, post.title)
250 【python】WordPressに自動投稿やってみた。
256 python投稿テスト
248 【気まま日記】今週を振り返りながら酒を飲む。
244 【python】Webスクレイピングで画像取得をやってみる。
238 【週刊少年ジャンプ】40号感想
231 トラックボールマウスを買ったった。
227 「スーパーの裏でヤニ吸うふたり」が面白かった件。
219 【python】Webスクレイピングをやってみる。
203 【python】Anacondaでpython環境を整える。
成功すんのかい 笑
ということで、バージョン違いが原因とわかったのでそれっぽい検索ワードで探してみると、以下のやり取りがヒット。
つまるところ、collections
が collections.abc
にアップデートされたため、python 3.10 環境では使えなかったという話らしい。(正確にはpython 3.8からっぽい)
たしかに、モジュールの説明ページにもそんなことが書いてある。
New in version 3.3: Formerly, this module was part of the
Python 3.10.6 ドキュメント よりcollections
module.
エラーの修正対応
残念ながら python-wordpress-xmlrpc
のモジュールは数年以上更新されておらず(2022/9 現在)、言わずもがなpython 3.10への対応はされているわけがない。
ということで自分で修正する必要があるわけだが、修正方法としては以下が思いつく。
- モジュールそのものを書き換える(恒久対応)
- スクリプト内で動的に修正を加える(暫定対応)
それぞれメリット・デメリットはあるが、1個目はモジュールそのものを書き換えるので少しリスキーではある。おそらくこの問題自体は割と最近発生したであろうものだが、使えないとみんな困ると思うので、そのうちアップデートが入るだろう。
ということで、今回は暫定対応でリスクが少ない2個目の方法でトライしてみる。
コードの修正
つまるところ、呼び出すメソッドの中身を呼び出した後に書き換えれば良い。以下のページを参考にしてトライしてみる。
今回の場合は、base.py
の中の XmlrpcMethod
クラスの process_result
を修正したいので、XmlrpcMethod
をインポートして、process_result
を自作の修正した関数に置き換える。
# 修正するモジュールをインポート
from wordpress_xmlrpc.base import XmlrpcMethod
# 修正するにあたり、必要な関連モジュールをインポート
from wordpress_xmlrpc.compat import xmlrpc_client, dict_type
import collections.abc
# 元の関数をコピペして、自作関数を作成
def my_process_result(self, raw_result):
"""
Performs actions on the raw result from the XML-RPC response.
If a `results_class` is defined, the response will be converted
into one or more object instances of that class.
"""
if self.results_class and raw_result:
# dict_type は wordpress_xmlrpc.compat からインポートしてこないといけない。
if isinstance(raw_result, dict_type):
return self.results_class(raw_result)
# collections => collections.abc に書き換え
elif isinstance(raw_result, collections.abc.Iterable): ## ここを書き換え。
return [self.results_class(result) for result in raw_result]
return raw_result
# 自作のメソッドに置き換える。
XmlrpcMethod.process_result = my_process_result
記事一覧の再取得
ということでリトライ。
# python 3.10.4 で実行
from wordpress_xmlrpc import Client, methods, WordPressPost
from wordpress_xmlrpc.methods.posts import GetPosts, NewPost
from wordpress_xmlrpc.base import XmlrpcMethod
from wordpress_xmlrpc.compat import xmlrpc_client, dict_type
import collections.abc
# 元の関数をコピペして、自作関数を作成
def my_process_result(self, raw_result):
"""
Performs actions on the raw result from the XML-RPC response.
If a `results_class` is defined, the response will be converted
into one or more object instances of that class.
"""
if self.results_class and raw_result:
if isinstance(raw_result, dict_type):
return self.results_class(raw_result)
elif isinstance(raw_result, collections.abc.Iterable):
return [self.results_class(result) for result in raw_result]
return raw_result
# 自作のメソッドに置き換える。
XmlrpcMethod.process_result = my_process_result
# client情報
url = 'https://arefukeblog.com/xmlrpc.php' # トップページのアドレス末尾に/xmlrpc.phpを付ける
user = 'yamatoXXXXXX' # WordPressのユーザ名
password = 'ABCDXXXXXX' # WordPressのパスワード
client = Client(url, user, password)
# 記事一覧の取得。
posts = client.call(GetPosts())
for post in posts:
print(post.id, post.title)
250 【python】WordPressに自動投稿やってみた。
256 python投稿テスト
248 【気まま日記】今週を振り返りながら酒を飲む。
244 【python】Webスクレイピングで画像取得をやってみる。
238 【週刊少年ジャンプ】40号感想
231 トラックボールマウスを買ったった。
227 「スーパーの裏でヤニ吸うふたり」が面白かった件。
219 【python】Webスクレイピングをやってみる。
203 【python】Anacondaでpython環境を整える。
いよっしゃ。成功。
ということでお疲れ様っす。
参考にした記事だと当たり前のように成功してて、自分だけできなくてめっちゃ焦ったw
ただ、検索してもなかなか日本語の記事が見つからず、割と最近のバグなのかな? と思った。
困っている人多そうだけどなー
はー とりあえず問題解決するとすっきり。
今日は気持ち良く寝れそう。→ もう寝ただろ。
コメント