はてだBlog(仮称)

私的なブログど真ん中のつもりでしたが、気づけばWebサイト系のアプリケーション開発周りで感じたこと寄りの自分メモなどをつれづれ述べています。2020年6月現在、Elasticsearch、pandas、CMSなどに関する話題が多めです。...ですが、だんだんとより私的なプログラムのスニペット置き場になりつつあります。ブログで述べている内容は所属組織で販売している製品などに関するものではなく、また所属する組織の見解を代表するものではありません。

Pythonオレオレ相対パス/ルート相対パスの小品

はじめに

このブログは総じて自分メモなのですが、その中でも次の過去記事の応用(?)として、自分の手に馴染むルート相対/相対パス周りのオレオレミニライブラリを作成したのでそのメモです。

itdepends.hateblo.jp

itdepends.hateblo.jp

なぜこの記事なのか

ほぼ車輪の再発明なんですが、というか再発明でさえなく/ライブラリと呼べるものでもないというのが実体なんですが、実は再発明というよりは、この類のラッパーを作って、ミニDSLっぽくしたいことがあるんですよね。

つまり、プログラムの純粋な共通化やその他のプラクティスの方向というよりは、当時自分がやりたかった案件・ドメインを切り取ったような・それを体現したような関数やクラスを残しておきたいというところ。

なお、隠れたウリとして、次のPythonのメタな処理の関数を使って、走行確認テスト((テストコードとは呼べないな))をタイプ量少なめで実装してみています。

  • inspect.getmembers によるクラスのメソッド名一覧取得
  • inspect.signature による、あるメソッドのシグネチャ確認
  • getattr による、メソッド名文字列を与えて関数実行

inspect.getmembers と inspect.signature docs.python.org docs.python.org

getattr ビルトイン関数( getattr(x,'foobar')は x.foobarと等価です。というのが全てかも) docs.python.org

プログラムと実行結果

from urllib.parse import urlparse
from urllib.parse import urljoin
import os.path
import os
import re
import sys

class ResourcePath:
    def __init__(self,address):
        _a = re.sub('/index.html$','/',address)
        _a = re.sub('/index.htm$','/',address)
        self.url = _a
        self.IMG = 'jpg/jpeg/JPG/JPEG/png/PNG/gif/GIF/pdf/PDF'.split('/')
        self.CSS = 'css'.split('/')
        self.JS = 'js/json'.split('/')

    def __str__(self):
        return self.url        

    # 部分値の取得 ------------

    def scheme(self):
        return urlparse(self.url).scheme

    def netloc(self):
        return urlparse(self.url).netloc

    def dirname(self):
        return os.path.dirname(urlparse(self.url).path)

    def just_dirname(self):
        return self.dirname().split('/')[-1]

    def basename(self):
        return os.path.basename(urlparse(self.url).path)

    def extension(self):
        work = self.basename().split('.')
        return work[-1] if len(work) > 1 else ''

    # ルート相対パス関連の個別関数 ------------

    def ルート相対パス取得可能(self):
        if self.is相対パス():
            return False
        return True

    def ルート相対パス(self): 
        try:
            if self.is相対パス(): 
                raise ValueError
            return urlparse(self.url).path
        except:
            print('ERROR:相対パスに対してルート相対パス情報取得',sys.stderr)
            sys.exit()

    def set_cwd(self, cwd=None): # 相対パスの場合、指定のカレントディレクトリを結合してルート相対パス情報を格納する。
        try:
            if self.is相対パス():
                if len(cwd) > 0 and cwd.startswith('/'):
                    cwd = cwd if cwd.endswith('/') else cwd + '/'
                    self.url = urljoin(cwd, urlparse(self.url).path)
                    # 注:この使い方なら、os.path.joinでも同等の結果が得られる。
                else:
                    raise ValueError
            else:
                pass
        except:
            print('ERROR:不正なカレントディレクトリが指定',sys.stderr)
            sys.exit()
    
    # 判定関数 ------------

    def is絶対パス(self):
        if self.url.startswith('http://') or self.url.startswith('https://') or self.url.startswith('//'):
            return True
        return False

    def isルート相対パス(self):
        if ( not self.url.startswith('//') ) and self.url.startswith('/'):
            return True
        return False

    def ルート相対パス_matches(self,prefixdir): ####
        if self.ルート相対パス().startswith(prefixdir):
            return True
        return False

    def is相対パス(self):
        if self.is絶対パス():
            return False
        if self.isルート相対パス():
            return False
        return True
    
    def is_cssfile(self):
        if self.extension() in self.CSS:
            return True
        return False            

    def is_jsfile(self):
        if self.extension() in self.JS:
            return True
        return False            

    def is_imgfile(self):
        if self.extension() in self.IMG:
            return True
        return False            

    def is_htmlext(self):
        if self.extension() in ['html','htm','HTML','HTM']:
            return True
        return False            

    # 変換した値を戻す関数 ------------
    def change_scheme(self, to): 
        if self.is絶対パス():
            return re.sub('(https://|http://|//)',to,self.url)

    def change_netloc(self,to): # toはschemeから指定の文字列を指定する。また、ルート相対パス型であれば、実質「to」で指定のnetlocを追加するような動作とする。
        return to + self.ルート相対パス()

def 引数なしのメソッド走行確認(クラス, インスタンス):
    """
    リフレクション系(Pythonにおいて(でなくても)適切な用語でないかも...)のメソッドを使って、一括で走行確認
    """
    import inspect
    メソッド一覧 = inspect.getmembers(クラス, inspect.isfunction)
    # ↑はこんな↓感じ
    #In [244]: inspect.getmembers(ResourcePath,inspect.isfunction)                                                          
    #Out[244]: [('__init__', <function __main__.x.__init__(self, path)>)]

    # 1要素めにメソッド名が入っている
    アンスコ始まりでないメソッド一覧 = [m for m in メソッド一覧 if not m[0].startswith('_')]
    # 2要素めに関数定義?が入っているので、parametersでパラメータ(シグネチャ)が取れる。inspect.signatureも参照)
    runlist = [m for m in アンスコ始まりでないメソッド一覧 if len(inspect.signature(m[1]).parameters) == 1] # selfしか引数に持たないもの
    for i in runlist:
        メソッド名 = i[0]
        # getattrでメソッドを実行して結果を確認。print関数で雑な出力。
        print(インスタンス, '->', メソッド名, '->', getattr(インスタンス, メソッド名)())


a = [
    'https://example.com/a/b/c.html',
    'http://example.com/a/b/c.html',
    '//example.com/a/b/c.html',
    '/a/b/c.html',
    '/a/b/',
    '/a/b',
    '/a/b/index.html'
]

for i in a:    
    x = ResourcePath(i)
    引数なしのメソッド走行確認(ResourcePath,x)    

b = 'x/y.jpg'
c = '/XXX/YYY/'
x = ResourcePath(b)
x.set_cwd(c)
引数なしのメソッド走行確認(ResourcePath,x)    

b_dash = 'x/y.jpg'
x = ResourcePath(b)
# こちらは途中でエラーになる(期待値どおり)
引数なしのメソッド走行確認(ResourcePath,x)    

↓ 実行結果

https://example.com/a/b/c.html -> basename -> c.html
https://example.com/a/b/c.html -> dirname -> /a/b
https://example.com/a/b/c.html -> extension -> html
https://example.com/a/b/c.html -> is_cssfile -> False
https://example.com/a/b/c.html -> is_htmlext -> True
https://example.com/a/b/c.html -> is_imgfile -> False
https://example.com/a/b/c.html -> is_jsfile -> False
https://example.com/a/b/c.html -> isルート相対パス -> False
https://example.com/a/b/c.html -> is相対パス -> False
https://example.com/a/b/c.html -> is絶対パス -> True
https://example.com/a/b/c.html -> just_dirname -> b
https://example.com/a/b/c.html -> netloc -> example.com
https://example.com/a/b/c.html -> scheme -> https
https://example.com/a/b/c.html -> ルート相対パス -> /a/b/c.html
https://example.com/a/b/c.html -> ルート相対パス取得可能 -> True
http://example.com/a/b/c.html -> basename -> c.html
http://example.com/a/b/c.html -> dirname -> /a/b
http://example.com/a/b/c.html -> extension -> html
http://example.com/a/b/c.html -> is_cssfile -> False
http://example.com/a/b/c.html -> is_htmlext -> True
http://example.com/a/b/c.html -> is_imgfile -> False
http://example.com/a/b/c.html -> is_jsfile -> False
http://example.com/a/b/c.html -> isルート相対パス -> False
http://example.com/a/b/c.html -> is相対パス -> False
http://example.com/a/b/c.html -> is絶対パス -> True
http://example.com/a/b/c.html -> just_dirname -> b
http://example.com/a/b/c.html -> netloc -> example.com
http://example.com/a/b/c.html -> scheme -> http
http://example.com/a/b/c.html -> ルート相対パス -> /a/b/c.html
http://example.com/a/b/c.html -> ルート相対パス取得可能 -> True
//example.com/a/b/c.html -> basename -> c.html
//example.com/a/b/c.html -> dirname -> /a/b
//example.com/a/b/c.html -> extension -> html
//example.com/a/b/c.html -> is_cssfile -> False
//example.com/a/b/c.html -> is_htmlext -> True
//example.com/a/b/c.html -> is_imgfile -> False
//example.com/a/b/c.html -> is_jsfile -> False
//example.com/a/b/c.html -> isルート相対パス -> False
//example.com/a/b/c.html -> is相対パス -> False
//example.com/a/b/c.html -> is絶対パス -> True
//example.com/a/b/c.html -> just_dirname -> b
//example.com/a/b/c.html -> netloc -> example.com
//example.com/a/b/c.html -> scheme -> 
//example.com/a/b/c.html -> ルート相対パス -> /a/b/c.html
//example.com/a/b/c.html -> ルート相対パス取得可能 -> True
/a/b/c.html -> basename -> c.html
/a/b/c.html -> dirname -> /a/b
/a/b/c.html -> extension -> html
/a/b/c.html -> is_cssfile -> False
/a/b/c.html -> is_htmlext -> True
/a/b/c.html -> is_imgfile -> False
/a/b/c.html -> is_jsfile -> False
/a/b/c.html -> isルート相対パス -> True
/a/b/c.html -> is相対パス -> False
/a/b/c.html -> is絶対パス -> False
/a/b/c.html -> just_dirname -> b
/a/b/c.html -> netloc -> 
/a/b/c.html -> scheme -> 
/a/b/c.html -> ルート相対パス -> /a/b/c.html
/a/b/c.html -> ルート相対パス取得可能 -> True
/a/b/ -> basename -> 
/a/b/ -> dirname -> /a/b
/a/b/ -> extension -> 
/a/b/ -> is_cssfile -> False
/a/b/ -> is_htmlext -> False
/a/b/ -> is_imgfile -> False
/a/b/ -> is_jsfile -> False
/a/b/ -> isルート相対パス -> True
/a/b/ -> is相対パス -> False
/a/b/ -> is絶対パス -> False
/a/b/ -> just_dirname -> b
/a/b/ -> netloc -> 
/a/b/ -> scheme -> 
/a/b/ -> ルート相対パス -> /a/b/
/a/b/ -> ルート相対パス取得可能 -> True
/a/b -> basename -> b
/a/b -> dirname -> /a
/a/b -> extension -> 
/a/b -> is_cssfile -> False
/a/b -> is_htmlext -> False
/a/b -> is_imgfile -> False
/a/b -> is_jsfile -> False
/a/b -> isルート相対パス -> True
/a/b -> is相対パス -> False
/a/b -> is絶対パス -> False
/a/b -> just_dirname -> a
/a/b -> netloc -> 
/a/b -> scheme -> 
/a/b -> ルート相対パス -> /a/b
/a/b -> ルート相対パス取得可能 -> True
/a/b/index.html -> basename -> index.html
/a/b/index.html -> dirname -> /a/b
/a/b/index.html -> extension -> html
/a/b/index.html -> is_cssfile -> False
/a/b/index.html -> is_htmlext -> True
/a/b/index.html -> is_imgfile -> False
/a/b/index.html -> is_jsfile -> False
/a/b/index.html -> isルート相対パス -> True
/a/b/index.html -> is相対パス -> False
/a/b/index.html -> is絶対パス -> False
/a/b/index.html -> just_dirname -> b
/a/b/index.html -> netloc -> 
/a/b/index.html -> scheme -> 
/a/b/index.html -> ルート相対パス -> /a/b/index.html
/a/b/index.html -> ルート相対パス取得可能 -> True
/XXX/YYY/x/y.jpg -> basename -> y.jpg
/XXX/YYY/x/y.jpg -> dirname -> /XXX/YYY/x
/XXX/YYY/x/y.jpg -> extension -> jpg
/XXX/YYY/x/y.jpg -> is_cssfile -> False
/XXX/YYY/x/y.jpg -> is_htmlext -> False
/XXX/YYY/x/y.jpg -> is_imgfile -> True
/XXX/YYY/x/y.jpg -> is_jsfile -> False
/XXX/YYY/x/y.jpg -> isルート相対パス -> True
/XXX/YYY/x/y.jpg -> is相対パス -> False
/XXX/YYY/x/y.jpg -> is絶対パス -> False
/XXX/YYY/x/y.jpg -> just_dirname -> x
/XXX/YYY/x/y.jpg -> netloc -> 
/XXX/YYY/x/y.jpg -> scheme -> 
/XXX/YYY/x/y.jpg -> ルート相対パス -> /XXX/YYY/x/y.jpg
/XXX/YYY/x/y.jpg -> ルート相対パス取得可能 -> True
x/y.jpg -> basename -> y.jpg
x/y.jpg -> dirname -> x
x/y.jpg -> extension -> jpg
x/y.jpg -> is_cssfile -> False
x/y.jpg -> is_htmlext -> False
x/y.jpg -> is_imgfile -> True
x/y.jpg -> is_jsfile -> False
x/y.jpg -> isルート相対パス -> False
x/y.jpg -> is相対パス -> True
x/y.jpg -> is絶対パス -> False
x/y.jpg -> just_dirname -> x
x/y.jpg -> netloc -> 
x/y.jpg -> scheme -> 
ERROR:相対パスに対してルート相対パス情報取得 <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>