くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

プログラム

MVCでトランザクションをどこに持たせるか

今更MVCの話。

結局MVCって役割の分離って事じゃないですか。

WebアプリでMVC(厳密に言うと原書のMVCではないけども)っぽく作る場合。

色んなサンプルを見てるとMVCのCにトランザクションのBeginとEndがあったりする。

システムのアーキテクチャ上、コントローラでトランザクション制御するっていうモノももちろんあると思うけど、

それしかないのもどうかなと思うのと、微妙な気がしてるので少し書いておく。

まず、Contorlerとは何をするべきなのかって事。

Webアプリだと、ココはやっぱり何のビジネスロジックを呼び出すかっていう部分と、

認証周りの処理、セッション周りの処理、あとはリクエストの内容を解析して、

ビジネスロジックに渡すイベントオブジェクト的なものを作る処理辺りだと思うのです。

で、良くあるトランザクションの処理で言われてるところが、

AっていうDBを更新する処理があって、その後にBっていう他のデータを更新する処理があって、

その2つの処理を関連付けて一つのトランザクションとして扱いたい

みたいなものがある。

その制御をするためにはモデルを呼ぶコントローラでやらなきゃ!

って感じなのかな~と。

そこに違和感を感じるわけです。

処理を関連付けるとか処理する順番に依存してるとかとか。

それに意味があるなら、それはビジネスロジックだよね?

という事でトランザクションの制御はコントローラではやらない方がいいと思うのです。

Contorlerに入れた時点で、HTTPのリクエスト渡さないと他から使えないじゃない?

モデル側でファサード的なクラスを用意しておけばいいように思うわけです。

MVCってModelViewContorlerに分ける「方向性」で組むと思うんですが、

分割の仕方は3つである必要はないと思うんですよ。

大事なのは役割の分割であって、3つという数に意味はないように思うのです。

なので、Controlerに持たせるのは何か微妙に思う。

あと、結構致命的だと思ってるのは、テスト書き辛くない?

Webアプリのコントローラって認証とセッションにほぼ結合してると思うので、

その辺の調整をテストコード側でやらないと呼ぶの無理じゃん?

テスト書きヅラ!!みたいな。

という事で、MVCを元に構成したWebアプリでトランザクション処理する時は

コントローラでやるべきではないと思うのでした。。。

Python typeでクラス定義を書き換える

なんのこっちゃって感じ。rest_frameworkのソース読んでて、

あー、これはこういう事に使うのかってピンと来たので残しておく。

DjangoのModelもそうなんだけど、シリアライザを作る時に

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

こんな感じで、「クラス変数」にモデルの定義を入れてる。

で、クラス変数ってstaticだから、インスタンス化して使おうが、

結局全インスタンスで共有されてる変数。

でも、DjangoもRestFrameworkもちゃんとインスタンス変数になってる。

なんでかなーって不思議だったんですが、ソース読んでると、

metaclassの定義使って、クラス定義読み込まれる時に、クラス変数を潰して、

インスタンス変数作ってるんですね。

RestFrameworkのソースだと↓の部分。

class SerializerMetaclass(type):
    """
    This metaclass sets a dictionary named `_declared_fields` on the class.
    Any instances of `Field` included as attributes on either the class
    or on any of its superclasses will be include in the
    `_declared_fields` dictionary.
    """

    @classmethod
    def _get_declared_fields(cls, bases, attrs):
        fields = [(field_name, attrs.pop(field_name))
                  for field_name, obj in list(attrs.items())
                  if isinstance(obj, Field)]
        fields.sort(key=lambda x: x[1]._creation_counter)

        # If this class is subclassing another Serializer, add that Serializer's
        # fields.  Note that we loop over the bases in *reverse*. This is necessary
        # in order to maintain the correct order of fields.
        for base in reversed(bases):
            if hasattr(base, '_declared_fields'):
                fields = [
                    (field_name, obj) for field_name, obj
                    in base._declared_fields.items()
                    if field_name not in attrs
                ] + fields

        return OrderedDict(fields)

    def __new__(cls, name, bases, attrs):
        attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
        return super().__new__(cls, name, bases, attrs)

で、Serializerの定義が↓

class Serializer(BaseSerializer, metaclass=SerializerMetaclass):

SerializerMetaclassの↓のところ

    @classmethod
    def _get_declared_fields(cls, bases, attrs):
        fields = [(field_name, attrs.pop(field_name))
                  for field_name, obj in list(attrs.items())
                  if isinstance(obj, Field)]
        fields.sort(key=lambda x: x[1]._creation_counter)

attrsにクラス定義でのクラス変数とか入ってて、これが最終的にクラス定義内に

展開されるっぽい。なので、attrs.popでクラス変数として消してる。

消すタイミングで、fieldsって変数に定義を退避してる感じ。

で、typeを継承してて、最後に「return super().__new__(cls, name, bases, attrs)」してる

という感じ。

なるほどー。ってなんか納得した。

pythonでメタプログラミングするならtype継承してクラス定義が作られてるって

知っとけみたいな話見てて「は?」って思ってたんだけど、

こう具体的なtypeの使い方見ると納得できますね。。。

で、↑のが何で便利なのかというと、MVCとMVVMとかで作ってる時って、

View層とのやり取りは専用のViewModel作ると思うんですよ。

バリデーションとか統一しやすいし。

で、ViewModelの定義する時のベースにこれがすごく使える。

一つ一つのフィールドのデータ型を個別に作れるので。

一つ賢くなりましたというお話。。。

clocでgitのブランチ間の修正行数とかをカウントする

gitで管理してるリポジトリのコード行数とか色々集計したくなった。

コミットの追加行数とか修正行数は見ればわかるんだけど、

色々集計しようとしたりすると、gitのみだと割と使えない。

なのでclocっていうツールを使ってやる。

って話が割と見るんだけど、結構大変だったのと、clocってgitのリポジトリ集計するとき

だいぶイケてないように思うのでちょっと書いておく。

あ、Windowsでやります。

まず、インストール。現状の最新は1.82。

clocのgithubから

で、ググって出てくるサイトが最初はコッチ

古い方のサイトなので、注意。GitHubのreleaseから取る。

DLしてとりあえず動かそうとすると「unzipが無い」みたいなエラーが出る。

なので、unzipをDL。

unzipの公式

このunzipのインストールが必要ってclocの公式に書いて無いような気がするんだけども。。。

で、clocとunzipともpathを通しておかないとまずいっぽいので、path通す。

そしたら、対象のリポジトリのディレクトリに行って下の感じ。

git rev-list --max-parents=0 HEAD
>> ae5d586ed47385ded893d95c3ac66724e41259c4

cloc --diff ae5d586ed47385ded893d95c3ac66724e41259c4 HEAD

これで集計はされるんですが、割と↓のエラーが出るんでないかと。

fatal: pathspec ''Program.cs'' did not match any files
Failed to create tarfile of files from git. at script/cloc line 4584.

何でこれが出るのか全然分からなくて、ソース見てみたんですが、

gitのリポジトリからやる場合、下記の流れてやってるっぽい。

それぞれのハッシュから変更のあったファイル名を

git ls-tree --name-only

で持ってきて、

git archive

にファイルの一覧を渡してZIPなんかにして、

そのZIPファイルをclocで集計するみたいな動きになってる。

で、どうも比較するハッシュのどっちかに無いファイルがあると、

archiveが失敗してるって事みたい。

ダメじゃん。。。

cloc自体、スター結構ついてるし使ってる人いっぱいいると思うのですが、

どうみてもコレ出来ないんですよ。

どーなんだろ、みんな困ってないのかしら?

cloc自体、生のファイルかZIPとかのアーカイブからじゃないと集計できないって事なのね。

そしたら、自分でgit の zipアーカイブをいい感じに作るしかないですね。

とりあえず、ブランチ間の差分をとるなら下の感じでやれる。

git archive test --output=test.zip

git archive master --output=master.zip

cloc --diff test.zip master.zip

変更されたファイルのみやりたい場合は、なんかスクリプト組むか

プログラム作るしかなさそうですね。。。

C#でちょっと「ほや?」って思った事

大した話じゃないんですが、下記ソース。

public void hoge()
{
    //プリミティブ
    int t = 1;
    int tt = t;
    Console.WriteLine("ref={0}", Object.ReferenceEquals(t, tt));//⇒False
    //オブジェクト
    Object o = new object();
    Object oo = o;
    Console.WriteLine("ref={0}", Object.ReferenceEquals(o, oo));//⇒True
    //string
    string s = "s";
    string ss = s;
    Console.WriteLine("ref={0}", Object.ReferenceEquals(s, ss));//⇒True
    //オブジェクトの中のプリミティブ
    SampleValue x = new SampleValue();
    SampleValue xx = x;
    Console.WriteLine("ref={0}", Object.ReferenceEquals(x, xx));//⇒True
    Console.WriteLine("ref={0}", Object.ReferenceEquals(x.v, xx.v));//⇒False!!!
}
public class SampleValue
{
    public int v = 1;
}

オブジェクトは参照渡しだから、ReferenceEqualsがTrueになるのは分かるんだけど、

中のプリミティブな奴ってFalseになるんですね。

そうなのかーーーと思って。

なんか、すんませんでした。。。

DjangoとCeleryのジョブキューについて

Pythonでジョブキュー的に処理を外に投げたい場合に

Django+Celeryが良く見るやつ。

公式の通りにやれば特に問題なく動く。

で、一つ気になったところ。

これって、Djangoのアプリの中で下の感じで処理を投げるじゃないですか。

def aaction(request):
    res = some_task.delay()
    return HttpResponse(f"Delay Task!!!")

そしたら、Workerサーバというかサービスが↑でジョブキューに入った

処理依頼を処理する流れじゃないですか。

で、その時のWorkerプロセスの動きが、どう見ても、毎回Djangoアプリを初期起動してるんだよね。

そういうもん?バッチ処理的に使うなら、起動時間は全体の処理時間からみて

問題にならないって事なんかな?

2~3秒かかる程度の処理は投げない方が良い感じ?

それなりに大きいアプリだと起動処理もそこそこオーバーヘッドあると思うんだけど。。。

多分中で、django.setup()してると思うんだけど、

どうなんでしょ。何となく、起動時にsetup()はやって使いまわして欲しいのですが。。。

つか、それやったら、Djnagoの普通のWebサーバだろみたいな感じかしらね。

何とも釈然としないモヤモヤ感。

何かいいやり方ないかなー。もうちょい検討。。。

Python Djangoをローカルのスクリプトから読み込んで使う

Djangoのアプリの中でゴニョゴニョするんじゃなく、

別のスクリプトからDjangoアプリを呼び出して操作したい場合。

テストとかで。

公式的にはこの辺。

で、現状ちゃんと動くのは↓

import os
import sys
import django

#sys.path.append('/home/hogeuser/sandbox/mysite')
sys.path.append(os.getcwd())
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
django.setup()

#↑の後ならモデルとか読み込んで使える
from myapp import models

a = SampleTable()
a.save()

なんだけど、環境変数とか使うのどうにかならないかなーと思い、試してみた。

#公式に乗ってるやつ ⇒ これは無理
import django
from django.conf import settings
import mysite.settings as mysettings
settings.configure(default_settings=mysettings, DEBUG=True)
django.setup()

# 下のエラーが出る
# AttributeError: module 'mysite.settings' has no attribute 'LOGGING_CONFIG'

どうもsettingsを入れるだけだと、djangoのデフォルトのsettingsを丸ごと上書きしてるらしく、

必要な定義が消えちゃうっぽい。

やるなら、自分のsettingsにDjangoのデフォルトのsettingsを全部書かないとダメっぽ。

そして、それがかなり量があるのでしんどい。

つか、公式でもデフォルトはdjangoでやってるから原則変えるんじゃねー

みたいな感じっぽい。

で、次に試したのは↓

#これは動くけど、自分のアプリが読み込まれてない
import django
from django.conf import settings
from django.conf import global_settings
settings.configure(default_settings=global_settings, DEBUG=True)
django.setup()

Djangoのデフォルトを読み込ませてみたらいいんじゃないか的な。

これだとうまくいくけど、自分のINSTALL_APPSが読み込まれてないので、色々ダメ。

まー当たり前か。

という事で、デフォルトは上書きせずにマージするようなやり方がないか調べてたけど、

良く分かりませんでした。。。

どうも環境変数に入れる以外に良い方法が出回ってなさそうですね。

つか、manage.pyの中でこの方法でやってるからこれしかないのかしらね。

誰かいい方法知ってたら教えてほしい。

Python ディスクリプタについて

ディスクリプタの使い方を色々考えてて、挙動が面白かったので書いておこう。

てか、ディスクリプタって何で使えばいいんだろうか。

何かに使えそうな感じはするんだけど、具体的に何使うかは何とも・・・。

オブジェクトに値入れる時のバリデーションを共通化とかかな?

とりあえず、公式はこの辺

で、公式に

クラスへ束縛すると、A.x は呼び出し A.__dict__['x'].__get__(None, A) に変換されます。

って書いてあったので、試しに、、、

class DescriptorA:
    def __set_name__(self,owner,name):
        self._name = name

    def __get__(self, instance, owner):
        return owner.a

class MyClassA:
    a = DescriptorA()

ってやってみたら、案の定、無限ループして落ちた。つか、Pythonのエンジン側で再帰の制限かかってたのね。初めて知った。

  [Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded

ふむふむ。アクセスメソッド的に呼ばれるインターフェースが決まってて、それを定義したクラスがディスクリプタって感じでしょか。

で、公式に乗ってるサンプルは下記なんだけど、

class RevealAccess(object):
    """A data descriptor that sets and returns values
       normally and prints a message logging their access.
    """

    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        print('Retrieving', self.name)
        return self.val

    def __set__(self, obj, val):
        print('Updating', self.name)
        self.val = val

class MyClass(object):
...     x = RevealAccess(10, 'var "x"')
...     y = 5

これって、値の本体はMyClassじゃなくて、RevealAccess側のインスタンスだよね?

なんかMyClassのクラス変数でもインスタンス変数でも無いように思うのですが、こういう使い方が良いの?

てか、むしろこれやると、


class DescriptorB:
    def __init__(self,value):
        self.value = value

    def __set_name__(self,owner,name):
        self._name = name

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = value

class MyClassA:
    a = DescriptorA(10)
    b = 100

if __name__ == "__main__":
    x = MyClassB()
    y = MyClassB()

    print(f"before x.a : {x.a}")
    print(f"before y.a : {y.a}")
    y.a = 20
    print("set : y.a = 20")
    print(f"after x.a : {x.a}!!!!")
    print(f"after y.a : {y.a}")

    print(f"before x.b : {x.b}")
    print(f"before y.b : {y.b}")
    y.b = 200
    print("set : y.b = 200")
    print(f"after x.b : {x.b}")
    print(f"after y.b : {y.b}")

↓↓↓↓↓↓↓↓

before x.a : 10
before y.a : 10
set : y.a = 20
after x.a : 20!!!!
after y.a : 20

before x.b : 100
before y.b : 100
set : y.b = 200
after x.b : 100
after y.b : 200

って感じで、なんちゃってシングルトンのような超グローバル変数的な何かになる。

意図せずやっちゃったら相当ヤバイけど、意図的に何かに使えそうな気もする。

で、あと、色々見てると↓の感じでインスタンス変数扱いにしてるのを見る。

class DescriptorC:

    def __set_name__(self,owner,name):
        self._name = name

    def __get__(self, instance, owner):
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        instance.__dict__[self._name] = value

class MyClassC:
    a = DescriptorC()

if __name__ == "__main__":
    x = MyClassC()
    x.a = 10
    print(x.a)

↓↓↓↓↓

10

で、インスタンス変数へのアクセスっぽいんだけど、これって、値の代入するまでインスタンスの__dict__に対象の名前いないから、代入する前にアクセスすると落ちるよね?

if __name__ == "__main__":
    x = MyClassC()
    print(x.a)#これは落る
    x.a = 10
    print(x.a)#こっちは落ちない
↓↓↓↓↓
return instance.__dict__[self._name]でkeyerror

あと、公式だと↓で書いてあるので、

オブジェクトインスタンスへ束縛すると、a.x は呼び出し type(a).__dict__['x'].__get__(a, type(a))

クラスへ束縛すると、A.x は呼び出しA.__dict__['x'].__get__(None, A)

クラス変数として呼ぶと

if __name__ == "__main__":
    x = MyClassC()
    x.a = 10
    print(x.a)
    print(MyClassC.a)#これでも__get__呼び出されてキーエラー

んー、扱いがムズイ。

基本的にはインスタンス変数として扱いたいんだと思うので、↓の感じがいいのかしら。

class DescriptorD:

    def __set_name__(self,owner,name):
        self._name = name

    def __get__(self, instance, owner):
        if instance is None:
            return "申し訳ないがClassはNG"

        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        instance.__dict__[self._name] = value

class MyClassD:
    a = DescriptorD()
    def __init__(self):
        self.a = ""

if __name__ == "__main__":
    x = MyClassD()
    print(x.a)
    x.a = "init"
    print(x.a)
    print(MyClassD.a)

どうなんでしょ。個人的にはこれがいいと思うんだけど。それか、__get__の中でkeyが無かったら何かしらの初期値入れて、初期化するように組むか。

あと、公式の引数がそうなってるからだと思うのですが、色々見るサンプルって、下の感じ。

object.__get__(self, instance, owner)

このownerってクラスなんだけど、分りにくくない?

実際、Djangoのmodelsのソースでもディスクリプタ使われてるんですけど、

下の名前で使ってる。

def __get__(self, instance, cls=None):

clsの方が分かりやすいと思うんだけどなー。

Python Listのソートまとめ

list.sort()と sorted(list)の 2 つ。

まずは → を読むべし。https://docs.python.org/ja/3/howto/sorting.html

オブジェクトの配列をソートするとかする場合はkey関数を使う。

性能とか

import random
import time

time_sta = time.perf_counter()
for i in range(10000):
    numl = [random.randint(1, 1000) for i in range(100)]
    numl.sort()
time_end = time.perf_counter()
tspan = time_end- time_sta
print(tspan)

time_sta = time.perf_counter()
for i in range(10000):
    numl = [random.randint(1, 1000) for i in range(100)]
    sorted(numl)
time_end = time.perf_counter()
tspan = time_end- time_sta
print(tspan)

# どっちも大差無
3.690590940999982
3.6823944669999946

昇順/降順

numl = [random.randint(1, 1000) for i in range(100)]
print(numl.sort(reverse=True))
print(sorted(numl, reverse=True))

両者の違い

numl = [random.randint(1, 1000) for i in range(10)]
print(numl)
print(numl.sort())
print(numl)

[746, 897, 3, 989, 2, 216, 666, 852, 387, 409]
None
[2, 3, 216, 387, 409, 666, 746, 852, 897, 989]

numl = [random.randint(1, 1000) for i in range(10)]
print(numl)
print(sorted(numl))
print(numl)

[931, 793, 220, 503, 827, 961, 114, 530, 30, 528]
[30, 114, 220, 503, 528, 530, 793, 827, 931, 961]
[931, 793, 220, 503, 827, 961, 114, 530, 30, 528]

`list.sort()`は破壊的

`sorted(list)`は非破壊的

で、基本的には`sorted()`を使った方が良さそう。

Key 関数の使い方と性能

使い方
import random,string,time
from operator import itemgetter, attrgetter

dicl = [{"key": randomname(5),"val":random.randint(1, 1000)} for i in range(5)]

print( sorted(dicl,key=lambda x:x["key"]) )
print( sorted(dicl,key=lambda x:x["val"]) )

print( sorted(dicl,key=itemgetter("key")) )
print( sorted(dicl,key=itemgetter("val")) )
性能
import random,string,time
from operator import itemgetter, attrgetter

dicl = [{"key": randomname(5),"val":random.randint(1, 1000)} for i in range(1000)]
time_sta = time.perf_counter()
for i in range(10000):
    sorted(dicl,key=lambda x:x["key"])
time_end = time.perf_counter()
tspan = time_end- time_sta
print(tspan)

time_sta = time.perf_counter()
for i in range(10000):
    sorted(dicl,key=itemgetter("key"))
time_end = time.perf_counter()
tspan = time_end- time_sta
print(tspan)

7.373934460000328
6.795032753000214

`itemgetter` 使いましょう。

色んなデータ構造でソート

# 単純なList
numl = [random.randint(1, 1000) for i in range(5)]
print(sorted(numl))
print(sorted(numl,reverse=True))

# 辞書
dicl = [{"key": randomname(5),"val":random.randint(1, 1000)} for i in range(5)]
print(sorted(dicl,key=itemgetter("key")))
print(sorted(dicl,key=itemgetter("val"),reverse=True))
print(sorted(dicl,key=itemgetter("val","key")))# 複数キーのソート

# タプル
tupl = [(randomname(5),random.randint(1, 100), random.randint(200, 300) ,) for i in range(5)]
print(sorted(tupl,key=itemgetter(0)))
print(sorted(tupl,key=itemgetter(2),reverse=True))
print(sorted(tupl,key=itemgetter(2,0)))# 複数キーのソート

# オブジェクト
objl = [SampleClass(randomname(5),random.randint(1, 100)) for i in range(5)]
print(sorted(objl,key=attrgetter("name")))
print(sorted(objl,key=attrgetter("val"),reverse=True))
print(sorted(objl,key=attrgetter("val","name")))# 複数キーのソート

class SampleClass:
    def __init__(self,name,val):
        self.name = name
        self.val = val

犬は吠えない

ずいぶんと久しぶりに書く。

つか、独り言。後でもう少しちゃんと書こう。

オブジェクト指向の話。

とある例え話を見た。

なんかしっくりきたので、残しておきたい。

「動物に触ることができるか」っていう話なんだけど、

「動物とのふれあい」とかよくいうじゃない?

動物に触るって聞いたときに、

イメージしてるのって具体的な犬とか猫とかだと思うんです。

例えば、ワンコを飼ってる人だったら、自分の飼い犬を想像したり。

で、そのワンコには名前がついてるはず、例えばポチとしましょう。

ポチは「触れる」んですよね。でも動物は触れない。

動物は概念だから。存在しないから。

それがクラスとインスタンスなんだと。

なんかしっくりきた。

よく、クラスはオブジェクトの設計図っていうけど、

違う気がした。クラスは概念なんだと。

で、ちょっと「継承」って事を考える。

「継承が無いとオブジェクト指向じゃない」みたいな話もみたことある。

例えば、さっきの例だとポチのクラスは「犬」で、親クラスは「動物」みたいな話。

で、「動物」に例えば「吠える」っていうメソッドがあったとする。

「犬」にも吠えるってメソッドが継承されて、

「ポチ」に吠えるってメソッドがつく。

でも、「動物」は吠えないし、「犬」も吠えない。

吠えるのは「ポチ」だよね。

って考えると、やっぱり親クラスのメソッドは「インターフェース」なんだろうなと。

継承すると親クラスのメソッドとかフィールドとか使えて便利~♪みたいになるのは

やっぱ違うんじゃないかと思った。

「インターフェース」が定義されてるって考えるとなんかしっくりくる。

継承ってただの共通機能の寄せ集めになってる事が稀によくあるので、

それってきっと本来のオブジェクト指向の継承っていう事じゃないよね。

継承されるべきはインターフェースなんだろうなとふと思った次第。

継承されてきたフィールドとかメソッドって

一種のグローバルだと思うんです。

グローバルな変数とかメソッドって麻薬みたいなもんだと最近思う。

使ったその時はすごく便利。どこからでも呼べるし、

どこで定義してどこで使ってるかその時の「自分」は知ってるから。

でも時間が過ぎたり、色んな他の人が修正したりする中で、

どんどんメチャクチャになっていく。

で、そうなるともっとグローバルなものが必要になっていって・・・。

みたいな。

オブジェクト指向とかSOLIDとかとか、

今までに考えられて来たものってきっと、機能(役割)の分割(分担)の手法なんだろう。

機能の分割とみんな戦ってるんだな。

機能の構造化をするために、色んな手法があるんだな。

で、分割した機能は疎じゃないと。

原点回帰でそれって分割統治法ってことですよね。

昔の人もやっぱり凄いね。

Python3.6+CentOSでMeCabを使う

CentOS上でPythonからMeCabを使いたくなった。Pythonは3.6

で、一筋縄ではいかなかったので記録。

mecab本体のインストール

まず、git と Cのコンパイラ入ってない場合は入れる。

yum -y install git gcc-c++

そしたら、githubからソースを取得。作業用のディレクトリを決めてから。

cd sandbox
git clone https://github.com/taku910/mecab.git

ビルドする。

#本体のビルド
cd mecab/mecab
./configure  --enable-utf8-only
make
make check
sudo make install

#辞書のビルド
cd ../mecab-ipadic
./configure --with-charset=utf8
make
sudo make install

↑の内容はcloneした下記のhtmlファイルに記載されてるインストール手順。

firefox ~/sandbox/mecab/index.html &

そしたら動作確認。mecabでインタラクティブな実行になるので解析したい文字列を適当に入れてみる。

mecab
すもももももももものうち

ただ、この状態だと、「libmecab.so.2」っていうのがリンクしてもらえてなくて、リンクしてもらえるように下記の設定を行う。

#ファイル名は適当だけど拡張子だけは.conf
sudo vi /etc/ld.so.conf.d/mecab.conf
###############↓の一行だけ
/usr/local/lib
###############

sudo ldconfig

本体のインストールは以上。

Pythonから呼べるようにする

※ここから先は仮想環境に切り替えてからやる。

source ~/pyenv/bin/activate

swigのインストール

sudo yum install -y swig

Python用のビルドなんだけど、そのままやると動かないので、setup.pyの一部を書き換える。

cd ~/sandbox/mecab/mecab/python
vi setup.py
######################
#return string.split (cmd1(str))ってなっているところを
#return cmd1(str).split()にする。
######################

python setup.py build
python setup.py install

pip install mecab-python3

mecab-python3のPyPiのページ的にはUbuntuだとapt-getだけでイケルっぽいけど、CentOS用のは用意されてないんですかね。。。

しゃーなし。

ここまででPythonからimportして動くはず。

python
import MeCab
m = MeCab.Tagger ("-Ochasen")
print(m.parse("すもももももももものうち"))

長かった。。。

もうちょっと楽に行けるかと思ってたんだけども。。。

出来るまでに見たエラーは↓

pip install mecab-python3 で。

「unable to execute 'swig': No such file or directory」

import MeCab で。

「ImportError: libmecab.so.2: cannot open shared object file: No such file or directory」

問合せ