Closed17

Pythonテクニック

Tomoki OtaTomoki Ota

以下のPythonプログラムはすごいナンセンスである。
流石にいないと思うが。。。

class Person(object):
    def __init__(self, name):
          self.name = name
    def get_name(self):
        return self.name
    def set_name(self, name):
        self.name = name

a = Person("Taro")

print(a.get_name())
a.set_name("Jiro")
print(a.get_name())
person.set_name("Mr." + a.get_name())
print(a.get_name())

get_nameで名前を取得、set_nameで名前を設定している。

実行結果
Taro
Jiro
Mr.Jiro
Tomoki OtaTomoki Ota

普通に書くと以下のように簡潔になる。

class Person(object):
    def __init__(self, name):
        self.name = name


a = Person("Taro")
print(a.name)
a.name = "Jiro"
print(a.name)
a.name += "Yamada"
print(a.name)
Tomoki OtaTomoki Ota

属性が設定されたときに特別な振る舞いをしたい場合、@property デコレータをつけて、@xxx.setterというふうにsetter属性をマイグレートする。

class NewPerson(Person):
    def __init__(self, name):
        super().__init__(name)
    
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, name):
        self._name = name
        self.current = "Mr." + self._name

a = NewPerson("Taro")
print(a.name)
print(a.current)

上記の場合、currentを呼び出すとMrがつく。

実行結果
Taro
Mr.Taro

ちなみに、getterやsetterの中で、他の属性をsetしては絶対ダメ
変な動作をする。

Tomoki OtaTomoki Ota

以下のようにするとバリデーションをかけることもできる。

class NewPerson(Person):
    def __init__(self, name):
        super().__init__(name)

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

    @name.setter
    def name(self, name):
        if type(name) is not str:
            raise ValueError("正しく名前を入力してください。")
        self._name = name


a = NewPerson("Taro")
print(a.name)
a.name = 0
実行結果
Taro
Traceback (most recent call last):
        ・・・
ValueError: 正しく名前を入力してください。
Tomoki OtaTomoki Ota

hasattrを使うと変更禁止にすることも可能。

    @name.setter
    def name(self, name):
        if hasattr(self, 'name'):
            raise AttributeError("名前は変更できません。")
        self.__name = name
Tomoki OtaTomoki Ota

属性とは

  • プロパティ : データ (e.g. インスタンス変数)
  • メソッド : 処理 (e.g. クラスメソッド)

dir()でオブジェクトが持ってる属性を調べられる。

user = NewPerson("Taro")
print(dir(user)) 
実行結果
['_NewPerson__name', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
Tomoki OtaTomoki Ota

Everything is object

Pythonのクラスは全てobjectというクラスから継承している。
object内の関数は全てDunderである。

Dunder(ダンダー)

__init__ のようにアンダースコア2つに囲まれている関数のことをDunder(Double underscore)と呼ぶ。
Special methods、Magic methodsともいわれる。

init(self)

インスタンスの初期化。インスタンスを生成すると実行される関数。object内で 2 番目に実行される。

new(cls):

インスタンスの生成。インスタンス創造時に実行される関数で、object内で 1 番目に実行される関数。

Tomoki OtaTomoki Ota

デコレータ

def deco(func):
    def foo(*args, **kwargs):
        print("Hello Deco!")
    return foo

@deco
def a():
    print('Hello Decorator!')

a() # Hello Deco!
Tomoki OtaTomoki Ota

デコレータを複数使う

デコレータが複数ある場合は下から順番に実行される。
以下の例ではdeco1 → deco2

def deco2(func):
    def foo(*args, **kwargs):
        res = func(*args, **kwargs) + " deco2!"
        return res
    return foo

def deco1(func):
    def foo(*args, **kwargs):
        res = func(*args, **kwargs) + " deco1"
        return res
    return foo

@deco2
@deco1
def main():
    return 'Hello'

print(main()) # Hello deco1 deco2!
Tomoki OtaTomoki Ota

引数があってもOK

def deco1(func):
    def foo(*args, **kwargs):
        res = func(*args, **kwargs) + " deco1"
        return res
    return foo

@deco1
def main(str):
    return f'Hello {str}'

print(main("world")) # Hello world deco1
Tomoki OtaTomoki Ota

デコレータの引数も使える

def deco(str):
    def _deco(func):
        def foo(*args, **kwargs):
            res = func(*args, **kwargs) + " " + str
            return res
        return foo
    return _deco

@deco('deco2!')
@deco('deco1')
def hoge():
    return 'Hello'

print(hoge()) # Hello deco1 deco2!
Tomoki OtaTomoki Ota

デスクリプタ

デスクリプタとは__get__()__set__() 、あるいは __delete__() メソッドを定義したあらゆるオブジェクトのこと。set_name()も使うことができる。

データデスクリプタ

__set__()__delete__() の両方を定義しているオブジェクト

非データデスクリプタ

__get__() のみ定義されているオブジェクト

Tomoki OtaTomoki Ota
class B:
    def __get__(self, obj, objtype):
        print(self)
        print(obj)
        print(objtype)
        return 'Hello, world!'

class A:
    b = B()  # デスクリプタインスタンス

a = A()
print(a.b)
実行結果
<__main__.B object at 0xffffb8aeba50>
<__main__.A object at 0xffffb8aebb10>
<class '__main__.A'>
Hello, world!
Tomoki OtaTomoki Ota

デスクリプタ例2

objのパラメータにはAのインスタンス(a)が入る。
selfにはBのインスタンス(b)が、objectにはAのクラスが入る。

class B:
    def __get__(self, obj, objtype=None):
        print(obj.arg1)
        print(obj.arg2)
        return 'Hello, world!'


class A:
    b = B()
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

a = A("foo", "hoge")
print(a.b)
実行結果
foo
hoge
Hello, world!
このスクラップは2023/11/19にクローズされました