JSON loads 実行時の parse_float

JSON loads で文字列JSON を読込む時の、浮動小数点の少数桁は以下の動きをする。

import json
from decimal import Decimal

jstr = '{"width": 2.01285481238632125743}'

dc = json.loads(jstr)
print(dc['width'])

dc = json.loads(jstr, parse_float=Decimal)
print(dc['width'])

dc = json.loads(jstr, parse_float=lambda x:round(float(x), 6))
print(dc['width'])

結果

2.0128548123863212
2.01285481238632125743
2.012855

parse_float=None の時、decimal.Decimal と同じではなく、
float(value) と同じである。

with文

Python with 文のサンプルは、ネット検索するとたくさんあります。
クラスを用意、__enter__ と __exit__ で前処理、後処理を実行する方法

# -*- coding: UTF-8 -*-

class Foo():
    def __init__(self, msg):
        self.msg = msg
    def getMsg(self):
        return self.msg
    def __enter__(self):
        print('Foo __enter__')
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('Foo __exit__')
        if exc_type:
            print("exc_type : %s" % exc_type)
            print("exc_val  : %s" % exc_val)
            print("exc_tb   : %s" % exc_tb)

with Foo('Hello') as f:
    print('test {}'.format(f.getMsg()))
    # print('%d' % 'a')

この例では、、
 __enter__で、return self により自身のインスタンスを返して
 メソッド getMsg() が実行できるようにします。
結果

Foo __enter__
test Hello
Foo __exit__

これに、例外を発生するように実行します。

with Foo('Hello') as f:
    print('test {}'.format(f.getMsg()))
    print('%d' % 'a')

例外を発生さえた結果

Foo __enter__
Traceback (most recent call last):
test Hello
Foo __exit__
exc_type : <class 'TypeError'>
exc_val  : %d format: a number is required, not str
exc_tb   : <traceback object at 0x0362FB70>

contextmanagerを使う方法
これは実行するメソッドがインターセプト対象になり、
前処理、後処理を、yeild 前後で書くことになります。
例外は、try:~except で捕捉する処理を書きます。

from contextlib import contextmanager
from traceback import format_exception

@contextmanager
def execute(msg):
    try:
        print('# 前処理')
        yield msg.upper()
        print('# 後処理')
    except BaseException as e:
        print(''.join(format_exception(type(e), e, e.__traceback__)))
    finally:
        print('# finally')

with execute('Hello') as e:
    print('-- start')
    print(e)
    print('-- end')

yield で、値を返すこともできます。
結果

# 前処理
-- start
HELLO
-- end
# 後処理
# finally

これに、例外を発生するように実行します。

with execute('Hello') as e:
    print('-- start')
    print(e)
    print('%d' % 'a')

例外を発生さえた結果

# 前処理
-- start
HELLO
Traceback (most recent call last):
  File "C:/Users/xxx/PycharmProjects/xxxx/test.py", line 9, in execute
    yield msg.upper()
  File "C:/Users/xxx/PycharmProjects/xxx/test.py", line 18, in <module>
    print('%d' % 'a')
TypeError: %d format: a number is required, not str
# finally

例外捕捉を以下のように書きましたが、

    except BaseException as e:
        print(''.join(format_exception(type(e), e, e.__traceback__)))

次のように書いても同じです。

    except BaseException as e:
        for v in format_exception(type(e), e, e.__traceback__):
            print(v.rstrip())

この contextmanager の方法を踏まえて、過去に書いた
oboe2uran.hatenablog.com

に当てはめて書くと、、、

from contextlib import contextmanager
from traceback import format_exception
@contextmanager
def query(statement):
    try:
        import mysql.connector
        connect = mysql.connector.connect(host='localhost', port=3306, user='food', password='a1B01a', database='foodb', charset="utf8")
        cur = connect.cursor()
        cur.execute(statement)
        table = cur.fetchall()
        yield table
    except BaseException as e:
        print(''.join(format_exception(type(e), e, e.__traceback__)))
    finally:
        connect.close()

with query('SELECT * FROM users') as res:
    print(res)

で、結果 res は、1レコードはタプルでSELECT全行の結果としてリストで返ってきます。

クエリメソッドではなく、コネクション接続後のカーソルにする場合は、、

from contextlib import contextmanager
from traceback import format_exception
@contextmanager
def connect():
    try:
        import mysql.connector
        connect = mysql.connector.connect(host='localhost', port=3306, user='food', password='a1B01a', database='foodb', charset="utf8")
        cur = connect.cursor()
        yield cur
    except BaseException as e:
        print(''.join(format_exception(type(e), e, e.__traceback__)))
    finally:
        connect.close()

with connect() as cur:
    cur.execute("SELECT * FROM users")
    table = cur.fetchall()
    print(table)

トランザクションの為に書けば、、
commit roleback を差し込んで以下のように書けますね。

from contextlib import contextmanager
from traceback import format_exception
@contextmanager
def connect():
    try:
        import mysql.connector
        connect = mysql.connector.connect(host='localhost', port=3306, user='food', password='a1B01a', database='foodb', charset="utf8")
        cur = connect.cursor()
        yield cur
        connect.commit
    except BaseException as e:
        connect.rollback()
        print(''.join(format_exception(type(e), e, e.__traceback__)))
    finally:
        connect.close()

全角文字の平仮名⇔カタカナ変換

文字コード表を使用せず、計算だけで行います。
次の文字は、変換しません。

濁点、合成用    [゙    ]→ '\u3099'
半濁点、合成用 [゚    ]→ '\u309a'
濁点          [゛] → '\u309b'
半濁点        [゛] → '\u309c'
よりの合略仮名 [ゟ] → '\u309f'
中点         [・] → '\u30fb'
長音         [ー] → '\u30fc'
ワに濁点      [ヷ] → '\u30f7'
ヰに濁点      [ヸ] → '\u30f8'
ヱに濁点      [ヹ] → '\u30f9'
ヲに濁点      [ヺ] → '\u30fa'

以下の記号文字は、平仮名とカタカナで対応が存在するので変換します

繰り返し記号(平仮名)  [ゝ]→ '\u309d' 12445
繰り返し記号(平仮名)  [ゞ]→ '\u309e' 12446
繰り返し記号(カタカナ) [ヽ]→ '\u30fd' 12541
繰り返し記号(カタカナ) [ヾ]→ '\u30fe' 12542

全角平仮名→全角カタカナ変換

def hiraToKata(target):
    return ''.join([chr(n+96) if (12352 < n and n < 12439) or n==12445 or n==12446 else chr(n) for n in [ord(c) for c in target]])


全角カタカナ→全角平仮名変換

def kataToHira(target):
    return ''.join([chr(n-96) if (12448 < n and n < 12535) or n==12541 or n==12542 else chr(n) for n in [ord(c) for c in target]])

平仮名、カタカナ、それぞれ以下のように文字コードが存在します。

>>> from functools import reduce
>>> reduce(lambda r, t: r + chr(t), range(12353, 12448), '') 
'ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんヴ??\u3097\u3098??゛゜ゝゞ?'
>>> reduce(lambda r,t:r+chr(t),range(12449,12543),'')
'ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ????・ーヽヾ'


文字列、h. k に対して変換を実行します

h = "ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖ゗゘゙゚゛゜ゝゞゟ"
k = "ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽヾ"

print( hiraToKata(h) )
print( kataToHira(k) )

結果

ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ゗゘゙゚゛゜ヽヾゟ
ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖヷヸヹヺ・ーゝゞ

ASCIIコード文字の全角変換

半角英数字だけでなく、記号文字、!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ を含め
ASCII コード文字を対応する全角文字への変換、または逆(ASCIIコードへの変換)を
正規表現ではなく、計算で行う処理を Pytnon で書いてみました。
2通りの方法が考えられます。
・join の方法:変換対象文字列を Iteratable としてリスト内包表記で計算して join する方法
・redudeの方法:変換対象文字列を Iteratable として reduce のラムダでまとめる方法

テストの前準備
ASCIIコードを chr() で求める

>>> from functools import reduce
>>> reduce(lambda r,t:r+chr(t),range(32, 127), '')
' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'

全角スペースを覗いたASCIIコードの全角文字を chr() で求める

>>> from functools import reduce
>>> reduce(lambda r,t:r+chr(t),range(65281, 65375), '')
'!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'

これを元に、
s = ASCIIコード

s = reduce(lambda r,t:r+chr(t),range(32, 127), '')

z = 全角スペース+ASCIIコードの全角文字

z = ' ' + reduce(lambda r,t:r+chr(t),range(65281, 65375), '')

以下、其々の方法のメソッド
join の方法、(半角→全角)

def asciiToZenByJoin(target):
    return ''.join([chr(n+65248) if 32 < n and n < 127 else chr(12288) if n==32 else chr(n) for n in [ord(c) for c in target]])


reduce の方法、(半角→全角)

def asciiToZenByReduce(target):
    return reduce(lambda r,t:r+(chr(t+65248) if 32 < t and t < 127 else chr(12288) if t==32 else chr(t)), [ord(c) for c in target], '')


join の方法、(全角→半角)

def zenToASCIIByJoin(target):
    return ''.join([chr(n-65248) if 65280 < n and n < 65375 else chr(32) if n==12288 else chr(n) for n in [ord(c) for c in target]])


reduce の方法、(全角→半角)

def zenToASCIIByReduce(target):
    return reduce(lambda r,t:r+(chr(t-65248) if 65280 < t and t < 65375 else chr(32) if t==12288 else chr(t)), [ord(c) for c in target], '')


計測の処理:1万回の実行で計測

print( asciiToZenByJoin(s) )
t = timeit('asciiToZenByJoin(s)', globals=globals(), number=10000)
print('join   の方法、(半角→全角): {}'.format(t))

print( asciiToZenByReduce(s) )
t = timeit('asciiToZenByReduce(s)', globals=globals(), number=10000)
print('reduce の方法、(半角→全角): {}'.format(t))

print( zenToASCIIByJoin(z) )
t = timeit('zenToASCIIByJoin(s)', globals=globals(), number=10000)
print('join   の方法、(全角→半角): {}'.format(t))

print( zenToASCIIByReduce(z) )
t = timeit('zenToASCIIByReduce(s)', globals=globals(), number=10000)
print('reduce の方法、(全角→半角): {}'.format(t))

結果

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
join   の方法、(半角→全角): 0.39903364785408185
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
reduce の方法、(半角→全角): 0.532446630128369
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
join   の方法、(全角→半角): 0.28868796883428294
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
reduce の方法、(全角→半角): 0.41078418085099

計測値は、実行環境によって差がありますが、
・join の方法:変換対象文字列を Iteratable としてリスト内包表記で計算して join する方法
が速いという結果です。

直感的な見た目で reduce の記述の方がロジックとして読みやすく速いのではないかと
錯覚するのは、Java を多く書いていた感覚なのかもしれません。
すぐに、reduce は遅いと決めつけるのは良くないと思いますが。。。
いろいろやってみるべきと思っています。

string Template クラス

% による文字列のフォーマット出力を書くことが多いが、
string Templateクラスの機能も
忘れてはならない。

テンプレートは、$文字接頭辞

from string import Template

t = Template('$a is $b : $c')

substitute( テンプレートの対象名=値 、、、

from string import Template

s = t.substitute(a='Apple', b=True, c=datetime.now())
print(s)
# Apple is True : 2020-12-15 20:02:05.479734

substitute に、dict を渡して実行

data = {
    'a': 'Banana',
    'b': 180,
    'c': True
}
s = t.substitute(data)
print(s)
# Banana is 180 : True

もちろん、t.substitute(**data) と書いても結果は同じ

テンプレートに対して値が不足する時

data = {
    'a': 'Lemon',
    'b': 90
}
s = t.substitute(data)

string.py で、KeyError が発生する。

    return str(mapping[named])
KeyError: 'c'

テンプレートなので、以下のように同じ置き換え対象が2個以上あっても良い。

t = Template('$a is $b : $a')

これは、string.format() 関数で以下と同じです。

s = "{0} is {1} ; {0}".format('a','b','c')


'c’が対象にならずに、結果、

a is b ; a

になります

関数かどうかの型判定

オブジェクトが関数かどうかの判定方法は2通り。

・isinstance() で判定する場合は、types の FunctionType で判定

import types
f = lambda x:x+1
print( isinstance(f, types.FunctionType) )
# True

・callable() で判定、(呼び出せるかという文字通りの問い合わせ)

f = lambda x:x+1
print( callable(f) )
# True

dict(辞書)を正規表現で参照する

dict(辞書)の参照、dict[ 正規表現 ] で参照する
場合、dict のサブクラスで以下のようにする。

import re
class rdict(dict):
    def __getitem__(self, key):
        p = re.compile(key)
        r = [ v for k,v in self.items() if p.search(k) ]
        return r if len(r) > 0 else KeyError
    def get(self, k, d=None):
        p = re.compile(k)
        r = [v for k, v in self.items() if p.search(k)]
        if len(r)==1: return r[0]
        return r if len(r) > 1 else d

これは、search() を使ってるので、先頭からマッチせず途中からマッチでも
抽出するようになっている。
match() を使えば先頭文字からマッチである。

実行

dct = {
    'item': 'apple',
    'price': 170,
    'pin': "PIN-08334-2334",
    "pitype": "orange30"
}

rd = rdict(dct)

print(rd['price'])
print(rd['pi.*'])
# [170]
# ['PIN-08334-2334', 'orange30']

print(rd.get('price'))
print(rd.get('pi.*'))
# 170
#  ['PIN-08334-2334', 'orange30']