🐍

Pythonを始めたらまず覚えておくヤツら

に公開

新しく入ってきた人にオンボーディング担当としてつくことがあるんですが、
Pythonの経験がない人もちょいちょいいて、Pythonだとこういう風に書くとええよーというもので割と指摘することが多かったのをまとめておきます。

Pythonについての基本的な言語仕様や文法を理解していることが前提です。

他にも思い出したら追記していきます。

内包表記

PythonでIterableな(反復処理可能な)データ構造を扱うときの基本的な記法です。
Pythonで反復処理する場合、まず内包表記で書くことを意識します。
for文を使うのはネストが深くなりそうだったり(while文も一応ありますが・・・)、条件が複雑だったりするときで、出来るだけ内包表記で書きます。
これは基本的には内包表記で処理する方が処理速度が速いケースが多いのと、単純に見やすいからです(慣れれば)。
なのでこの見やすさを損なうような処理を書かざるを得ないときはfor文にします。
これは例えばネストしてしまう内包表記などです。個人的には二重になるだけでもかなり見にくいというか非直感的に感じるため、速度が求められるような処理でなければfor文で書いてしまうことが多いです。

# list内包表記
l = [i for i in range(10)]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 辞書内包表記
animals = [("cat", "ねこ"), ("dog", "いぬ"),("mouse","ねずみ")]
d = {e:j for e, j in animals}
# {'cat': 'ねこ', 'dog': 'いぬ', 'mouse': 'ねずみ'}


filter, map処理

ちなみによくJS界隈からやってきた人はまずmapとかfilterを探して使いがちなんですが、これもほぼ内包表記で事足りるためPythonではほとんど見ません(・・・偏見?)。

# filter
e = [i for i in range(10) if i%2==0]
# [0, 2, 4, 6, 8]

というのもmapもfilterもジェネレータが返ってくるので組み込み型として扱うには一旦それぞれの型に変換してやる必要がありますし、複雑な条件を与えるならそもそも普通にforで書いたほうが見やすいことが多いです。 
また仮にジェネレータで欲しい場合もジェネレータ内包表記で書けるので、内包表記とあえてかき分けたりfilter関数の方を使ってる人は周りでは見たことがないですね・・・。

型ヒント

Pythonは動的型付け言語なので厳密には型を宣言出来ません。
が、最近は型ヒントが導入されているため、注釈として書くことでIDEが解釈して警告してくれたり、型チェックツールを導入することで静的型付けっぽく出来ます。
もちろんランタイムでチェックされるわけではないので実行時エラーになる可能性は残りますが、この機能のおかげできちんと型を書けばPythonでも割と平和です。


l: list[int] = [i for i in range(10)]

d: dict[str,int] = {"test": 111111}

class Test:
    pass

t: Test = Test()

デコレータ

デコレータは既存の関数の前後に処理を追加するための仕組みです。
内部的にはデコレートした関数をデコレータとして定義された関数でラップして返します。
Pythonでは組み込みの便利なデコレータも多いので目にする機会も多いかと思います。

class User:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

user = User("test")

user.name
# "test"

https://docs.python.org/ja/3/glossary.html#term-decorator

抽象クラス

抽象クラスはabcモジュールを利用します。
またabstractmethodデコレータを付けた場合は、そのメソッドを継承したクラスでの実装を強制することが可能です。
たまにabstractmethodではなくNotImplementedErrorをraiseしているケースがありますが、チェックされるタイミング異なるため基本的にはabstractmethodを付けたほうが良いと思います。
(実装がない場合、abstractmethodはインスタンス生成時にエラーになりますが、NotImplementedErrorはメソッドが呼ばれたタイミングでエラーになります)

from abc import ABC, abstractmethod

# abstractmethodを利用する場合
class User(ABC):
    def __init__(self,name):
        self.name = name

    @abstractmethod
    def is_authenticated(self):
        ...

# NotImplementedErrorを利用する場合
class User(ABC):
    def __init__(self,name):
        self.name = name

    def is_authenticated(self):
        raise NotImplementedError

多重継承

ちなみにPythonでは多重継承が可能ですが、親クラスが盛り盛りになったり複雑な継承関係を築き上げると実装を追ったり実行や初期化順を把握するのが辛いのであまりオススメしません・・・。
多重継承時の詳しい挙動はMROで検索すると色々出てくるかと思います。

Protocol

他言語でのインターフェースに近い機能で、Pythonではダックタイピングで型ヒントを活用するために利用されます。
抽象クラスと似た使い方になりますが、こちらはtypingモジュールなのであくまで型ヒントによる制約です。そのため型チェックツールを利用せずに実際にエラーとしたい場合はabstractmethod等と組み合わせて使う必要があります。

また必ずしも継承する必要はありません。
ただ継承している場合IDE上で実装時にチェックしてくれるので基本的には継承しておいたほうが安全かなとは思います。

from typing import Protocol

class User(Protocol):
    def is_authenticated(self):
        ...

class Administorator(User):
    def is_authenticated(self):
        return True

class AnonymouseUser(User):
    def is_authenticated(self):
        return False

    
def can_access_page(user: User):
    return user.is_authenticated()

# OK
can_read_page(Administorator())

pass

passは一時的に何らかの処理として置いておく場合によく使います。
例えば本格的な実装の前にメソッドだけ定義しておいたりするときなんかですね。
通常メソッドだけ定義して内容が空だとPythonのランタイムでエラーになりますが、passは文なのでこれをとりあえず置いておくことでエラーを回避することが出来ます。

def is_num(x: Any):
    pass

https://docs.python.org/ja/3/tutorial/controlflow.html#pass-statements

ellipsis

ellipsisは少し難しいのですが、こちらもpassと似たような使い方が多いです。
正確にはEllipsisオブジェクトで、主に省略記号(...)で書きます。

https://docs.python.org/ja/3/library/constants.html#Ellipsis

from typing import Protocol

class Logger(Protocol):
    def debug(self, a:str):
        ...  ←これ

使い分けとしては、passは実装の途中やデバッグなど一時的な動作の代わり、ellipsisは主に抽象クラスのメソッドなどで使われることが多いです。

Discussion