🐥

私がPythonで詰まるところとか抜けるとこアレコレその1

2022/01/02に公開

はじめに

就職先でプロダクトに触れつつある中、やはり未経験故のスキルの無さに直面したのと本格的に Python と向き合う大義名分も得たので、定期的に基本からそれ以降のことまで個人的に忘れがちだったりすることを PU していけたらと思います。

サクッと仮想環境化


python -m venv myenv
source myenv/bin/activate # windows以外
source myenv/Scripts/activate # windows

deactivate # 仮想環境を閉じる

スクリプト実行はディレクトリ合わせてpython ファイル名.py

f-string

'...'.format()の略式、プロダクトのソースコードによく出てきてたやつ。


x = 20
y = 50

s1 = f'The Value of x is {x}' # The Value of x is 20
s2 = f'The Value of xy is {x*y}' # The Value of xy is 1000


型ヒント

変数の型を定義するというのは TypeScript に触れるようになってから意識するようになりましたが、Python でも関数の引数及び返り値に対して変数の型を指定できるのを業務でプロダクトのソースコードに触れるようになって知りました。
独学とかだとなかなか思い至らないですが、プロダクトではバグ対応、エラーチェック、テスト……等々行う上でどこに問題があるのかを出来得る限りはっきりさせておかないと、仮に問題が起きた場合対処が大変なのでこういうところもしっかりしておく必要があるんだなと。
あとまあ、型ヒントあると見慣れないうちは戸惑いますが、慣れてくると逆に引数と返り値に何が入るのかわかりやすくなるので。


# 引数
def type_hint(x: str, y: int):
    print (f' {X} number is {y}')

# 返り値
def type_hint_return(x: int) -> int:
    return x + 1

比較


def check1(x, y, data):
    # and
    if 0 <= x < 10:
        return x * 2
    # or
    elif x > 0 or y > 0:
        return x * y
    # in
    elif x < y:
        if y in data:
            print(f'{data} contains {y}')

# オブジェクトが等価か評価
def check2(x, y):
    if x is y:
        print('x is y == True')

def main():
    # x,yは以下の場合保持している値は等価だが、オブジェクト(インスタンス)としては等価ではない
    x = [1,2,3]
    y = [1,2,3]
    # つまり以下のような実行結果となる
    z = x
    v = y
    check2(y,z) # False
    check2(x,z) # True
    check2(x,y) # False
    check2(v, y) # True
    check2(v, z) # False

*args、**kwargs

引数が不特定多数になる場合に使う。
*argsはタプルとして、**kwargsは辞書として引数を受け取る。


def my_sum(*args):
    print('args: ', args)
    print('type: ', type(args))
    print('sum: ', sum(args))


print(my_sum(1, 2, 4, 6))
# args:  (1, 2, 4, 6)
# type:  <class 'tuple'>
# sum:  13

def my_sum2(x, y, *args):
    print('args: ', args)
    print('type: ', type(args))
    print('sum: ', sum(args))
    print(x * y * sum(args))


print(my_sum(1, 2, 4, 6, 9))
# args:  (4, 6, 9)
# type:  <class 'tuple'>
# sum:  19
# 38


def func_kwargs(**kwargs):
    print('kwargs: ', kwargs)
    print('type :', type(kwargs))


func_kwargs(x=1, y=2, z=10)
# kwargs:  {'x': 1, 'y': 2, 'z': 10}
# type : <class 'dict'>

def func_kwargs(a, b, **kwargs):
    print(a)
    print(b)
    print('Unit: ', kwargs)
    print('type :', type(kwargs))


func_kwargs(a='Gundam Development Project', b='ThiS is Forbidden Documents', key1='ZEPHYRANTHES', key2='PHYSALIS',
            key3='STAMEN', key4='GERBERA')
# Gundam Development Project
# ThiS is Forbidden Documents
# Unit:  {'key1': 'ZEPHYRANTHES', 'key2': 'PHYSALIS', 'key3': 'STAMEN', 'key4': 'GERBERA'}
# type : <class 'dict'>

集合


x = {0,1,2,3,4,5,6,7,8,9,10}

# リスト・タプルから集合を作る
x = set([1,1,2,2,3,4,5,5,6,6,77]) # {1, 2, 3, 4, 5, 6, 77}
y = set(([1,2,4])) # {1, 2, 4}
# ページには以下の例で紹介されてたけどTypeError: unhashable type: 'list'なのでダメ
y = set(('x', 'x', 3.14, [1, 2], [1, 2]))  # TypeError: unhashable type: 'list'

モジュール周り


__all__ = ["モジュール名1", "モジュール名2", "モジュール名3", ....]


つまり


greet
│--__init__.py
│--hello.py
│--goodbye.py

となっていた場合


from greet import *

としたときに import されるのはhello.pyということになる。

リスト内包表記

プロジェクトで頻出なのとそうでなくてもほぼこの書き方は多いのに個人的には苦手な部分。


# ループの基本かつよく使う用法
x = []
for i in range(50):
    x.append(i * 2)

# リスト内包表記
x = [i * 2 for i in range(50)]


順序がこんがらかってわからなくなるのが私の常ですが、つまり

iterableに対してループで適用したい処理 for ループ変数 in iterable

と覚えればいい。
また、単純にリストや辞書を作るだけじゃなくて別の型をキャストしてリストや辞書を新しく作るという用法もよくある。


x = {'a': 10, 'b': 20, 'c': 30}
y = [(key, value) for key, val in x.items()] # [('a', 10), ('b', 20), ('c', 30)]

DF だとこんな感じで使われたりする


import pandas as pd

# DF生成
df_origin = [["taro", "Tokyo", "12"], ["karen",
                                       "Kanagawa", "22"], ["Yamato", "Nagoya", "33"]]
df = pd.DataFrame(df_origin, index=['row1', 'row2', 'row3'], columns=[
                  'name', 'live', 'age'])

print(df)

"""     name      live age
row1    taro     Tokyo  12
row2   karen  Kanagawa  22
row3  Yamato    Nagoya  33 """

# 首都圏に住んでいるかどうか判定
def judge_syutoken(col):
    syutoken = ["Tokyo", "Kanagawa", "Chiba", "Saitama"]
    if col in syutoken:
        return "lived"
    else:
        return "Never"

# リスト内包表記で新しいカラムを作る
df["live_syutoken"] = [judge_syutoken(col) for col in df['live']]

print(df)

#         name      live age live_syutoken
# row1    taro     Tokyo  12         lived
# row2   karen  Kanagawa  22         lived
# row3  Yamato    Nagoya  33         Never

ラムダ式

いつまでも見慣れないのでこの機会にちゃんと覚える。


# これは
lambda x: x * x

# 以下の関数と同義

def sample(x):
    return x * x

つまり単純に引数: 引数を使って行う処理という図式で覚えればいい。


a = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * x, a))
print(result)  # [1, 4, 9, 16, 25]
result = list(map(lambda x: x * x * 4, a))
print(result)  # [4, 16, 36, 64, 100]
result = list(map(lambda x: (x, x * x), a))
print(result)  # [(1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]


スコープ

アクセス指定子はない。
命名規則で表現する。
メソッドでも変数でも規則は同じでアンダースコアをいくつつけるかで表現する。

  • public…… method()
  • protected(子クラスからアクセスはできるが、生成したオブジェクトからアクセスができない)…… _method()
  • private(クラス内のみでしかアクセスできない)…… __method()

あれってなるやつ

init()

前述の通り、オブジェクト(インスタンス)生成時に最初に呼び出されるメソッド。


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

Object = Point(20,30) # self.x = 20, self.y = 30

getattribute

クラスのメンバやメソッドを参照してるよというお知らせ

getattr

クラスのメンバの参照に失敗しているよというお知らせ。
大抵未定義のものにアクセスするとこれが出てくる。

setattr

メンバへの代入が行われましたよというお知らせ。
当然、クラス変数が Protected 以上だった場合、これは行えない。


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

Object = Point(20,30) # self.x = 20, self.y = 30
Object.x = 30 # self.x = 30, self.y = 30

str

クラスオブジェクト(インスタンス)に対して文字列キャストが行われたり、それが必要と判断された場合と呼び出される。
書式が適切ではないので自分で定義するほうがよい。


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f'<Point(x={self.x}, y={self.y})>'


Object = Point(20, 30)  # self.x = 20, self.y = 30
print(Object) # < Point(x=20, y=30) >

プロパティ


import math

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @property
    def distance(self):
        return math.sqrt(self.x * self.x + self.y * self.y)

@property のデコレータは関数を変数のように参照するデコレータ。
これがつくと以下の挙動になる。


# これが正常
point = Point(10, 20)
point.distance  # 22.360679775

# こっちはエラーになる、つまりクラスメソッド扱いにならない
Point.distance()

もっと役割がわかるように書くと以下の通り。


import math


class Point:
    def __init__(self, x, y):
        # _ をつけて隠蔽していることを示す
        self._x = x
        self._y = y

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

    @property
    def distance(self):
        return math.sqrt(self.x * self.x + self.y * self.y)

def __init__ は初期化のためのメソッドなので内部のクラス変数にはアクセスさせたくない。
そこで_xとして private にする。
しかし、参照はしたいという場面もある。
そこで、self._x等を返す関数を作成してその関数に対して@propertyのデコレータを適用すればクラス変数への代入を防ぎつつ、参照が可能となる。


point = Point(10, 20)
print(point.x, point.y)     # _x, _y の参照は可能。x = 10, y =20
point.x = 30                # _x, _y への代入はできない error

この場合どうしてもクラス変数への代入を試みたい場合は以下のようにする。


@property
def x(self):
    return self._x      # getter

@x.setter
def x(self, value):
    self._x = value     # setter

point.x = 30    # OK

with()って何?

参考: Python 3.9 の with 文

with のスコープ内だけでオブジェクトを使用したい、あるいはファイルの読み書きを行うが事が済んだら元に戻したいといった場合に使う。
具体的にはopen()でファイルを読み込んだ場合はclose()を行ってファイルを閉じないといけないが、最初からwith()を使っていれば閉じ忘れもなくなるといった感じで使われる。


# これは
with open("hello.txt") as hello:
    print(hello.read())

# これに同じ
hello =-open("hello.txt")
try:
    print(hello.read())
finally:
    print(hello.close())

ファイルが複数の場合は以下の通り。


with open("file1") as file1:
    with open("file2") as file2:
        with open("file3") as file3:
            print(file1.read(), file2.read(), file3.read())

# ネストは嫌だ

with open("file1") as file1, \
     open("file2") as file2, \
     open("file3") as file3:

    print(file1.read(), file2.read(), file3.read())

# python3.3~

from contextlib import ExitStack

with ExitStack() as stack:
    file1 = stack.enter_context(open("file1"))
    file2 = stack.enter_context(open("file2"))
    file3 = stack.enter_context(open("file3"))

# python3.9~

with (open("file1") as file1,
      open("file2") as file2,
      open("file3") as file3):

    print(file1.read(), file2.read(), file3.read())

参考

ゼロから学ぶ Python
Python.jp

Discussion