Otsuhachi’s diary

プログラミングのことや覚書などその他いろいろ

当ブログの内容を使用したり参考にした場合に生じた問題の責任は負いかねます。

Python組み込み関数6 -bool・callable・hasattr編-

Pythonおさらい第6段
制約として公式ドキュメントのみ参照*1として、読む癖をつける訓練も兼ねる。
今回はbool関数, callable関数, hasattr関数の3種類について。
isinstance, issubclassboolを返すという点では似ているが、次回に回す。

読み込むドキュメントはこちら[bool, callable, hasattr]

構成

今回の関数は以下のようになっている。

bool([x])

callable(object)

hasattr(object, name)

ドキュメントによると、それぞれ以下の項目を調べ、bool型を返す。

関数 引数 判定する内容
bool __bool__()を持つクラスおよびそのインスタンス クラス定義の真偽値
callable オブジェクト オブジェクトが呼び出し可能かどうか
hasattr オブジェクト, 名前 オブジェクトがその名前の属性を持つかどうか

主にif文などで使用し、例外を投げたり処理を分岐させる際に活用する関数とみて間違いないだろう。

hasattr(x)は以下のようなコードとほぼ等価らしい。

def hasattr(object, name):
    try:
        getattr(object, name)
        return True
    except AttributeError:
        return False

使ってみる

使ってみる-bool

  1. int型-28, -1, 0, 1, 28をそれぞれ判定してみる
  2. str型' ', '', '\n', '\t', 'Otsuhachi'をそれぞれ判定してみる
  3. 自作クラスPersonを定義して判定してみる*2

実行するコードは以下の通り。

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def __repr__(self) -> str:
        me = f'{self.name}({self.age})'
        return me

    def __bool__(self) -> bool:
        return self.age >= 20


def check(data: list):
    if not data:
        msg = f'引数を1つ以上与える必要があります。'
        raise ValueError(msg)
    if len(set(type(x) for x in data)) != 1:
        msg = 'dataは同じ型で統一されている必要があります'
        raise TypeError(msg)
    t = type(data[0]).__name__
    print(t)
    print('-' * 30)
    for d in data:
        print(f'"{d}": {bool(d)}')


data = [
    [-28, -1, 0, 1, 28],
    [' ', '', '\n', '\t', 'Otsuhachi'],
    [Person('Otsuhachi', 28), Person('Yamaki', 19)],
]
for d in data:
    check(d)
    print()
int
------------------------------
"-28": True
"-1": True
"0": False
"1": True
"28": True

str
------------------------------
" ": True
"": False
"
": True
"       ": True
"Otsuhachi": True

Person
------------------------------
"Otsuhachi(28)": True
"Yamaki(19)": False

int型0Falseでそれ以外はTrueを返すようだ。
str型は空文字がFalseでそれ以外はエスケープシーケンスであっても空白であってもTrueを返すようだ。
自作クラスPersonでは未成年をFalseとし、成人をTrueとする定義通りの結果が返っていることがわかる。

使ってみる-callable

  1. int, 0, str, ''をそれぞれ判定してみる
  2. 自作クラスPersonのクラス、インスタンスをそれぞれ判定してみる
  3. Personを継承し、__call__()を定義したPerson2のクラス、インスタンスをそれぞれ判定してみる

実行するコードは以下の通り。

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def __repr__(self) -> str:
        me = f'{self.name}({self.age})'
        return me


class Person2(Person):
    def __call__(self, other):
        text = f'{repr(self)}: {"{}"}'
        if isinstance(other, Person):
            text = text.format(f'こんにちは、{other.name}')
        else:
            text = text.format(f'{type(other).__name__}型の{other}か')
        print(text)


def check(data: list):
    if not data:
        msg = f'引数を1つ以上与える必要があります。'
        raise ValueError(msg)
    for d in data:
        print(f'{d}<{type(d).__name__}>: {callable(d)}')


p1 = Person('Otsuhachi', 28)
p2 = Person2('Yamaki', 19)
data = [int, 0, str, '', Person, p1, Person2, p2]
check(data)
<class 'int'><type>: True
0<int>: False
<class 'str'><type>: True
<str>: False
<class '__main__.Person'><type>: True
Otsuhachi(28)<Person>: False
<class '__main__.Person2'><type>: True
Yamaki(19)<Person2>: True

結果をまとめると以下の通りになっている。

オブジェクトの種類 結果
クラスオブジェクト True
インスタンス(__call__()あり) True
インスタンス(__call__()なし) False

callable(x)Trueを返すようなxは、x(*args, **kwargs)という式を実行可能であるということがわかる。

使い方-hasattr

  1. 自作クラスPersonのクラス、インスタンスに対し、以下の属性名を確認する
    • name
    • age
    • birthday
    • gender

実行するコードは以下の通り。

class Person:
    name: str
    age: int = 0

    def __init__(self, name: str, age: int, birthday: str):
        self.name = name
        self.age = age
        self.birthday = birthday

    def __repr__(self) -> str:
        me = f'{self.name}({self.age})'
        return me


p = Person('Otsuhachi', 28, '1995/06/04')
t = Person.__name__
for attr in ('name', 'age', 'birthday', 'gender'):
    print(f'{attr}')
    print('-' * 30)
    print(f'{t}: {hasattr(Person,attr)}')
    print(f'{p}: {hasattr(p,attr)}')
    print()
name
------------------------------
Person: False
Otsuhachi(28): True

age
------------------------------
Person: True
Otsuhachi(28): True

birthday
------------------------------
Person: False
Otsuhachi(28): True

gender
------------------------------
Person: False
Otsuhachi(28): False

Personに対し、hasattrした結果、型ヒントだけ与えた属性nameFalseで、代入を終えた属性ageTrueを返した。

また、Personから生成したインスタンスではname, ageに加え、クラス属性では触れていないbirthdayに関してもTrueを返すようになった。
このことから、__init__()内のself.birthday = birthdayの時点で、属性が追加されているのがわかる。

当然、クラス定義段階でもインスタンス生成段階でも触れなかった属性genderについては常にFalseが返った。

個人的にはこう書きたい

Pythonのバージョンが進み、:=演算子が使えるようになった今、
hasattrで属性を確認したあと、その属性に用があるのなら以下のように書き換えてもいいかもしれない。

書き換え前

name = 'otsuhachi'
attr_name = 'capitalize'
if hasattr(name, attr_name):
    aft_name = getattr(name, attr_name)()
    print(name, '->', aft_name)

書き換え後

name = 'otsuhachi'
attr_name = 'capitalize'
if (attr := getattr(name, attr_name, None)) is not None:
    aft_name = attr()
    print(name, '->', aft_name)

結果はどちらもotsuhachi -> Otsuhachiとなる。

もちろん、特定の属性を持つか持たないかがわかればいいだけなら、hasattrを使うべきだ。

締め

今回、真偽値を判定する関数3種類を紹介した。

引数のチェックが必要だったり、特定の属性を持つクラスのみの挙動を設定したりと使いどころは多いはずなので、なるべく早く慣れてしまいたい。

*1:わかりやすく解説している他サイトを見ない

*2:all・any編で定義したものとほぼ同じ