かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Python3 クロージャやってみた

次のような、年が変わった時だけ年数が入っている日付のデータを datetime でフォーマットしたかった。

data = [
  "2012年10月8日",
  "10月15日",
  # … 
  "12月24日",
  "2013年1月7日",
  "1月14日",
  # …
  "12月26日",
  "2014年1月9日",
  "1月16日",
  # …  
]

年数を global 変数に置いておいて \d+年 が出現する度に変数を更新して使えば済むのだけど、global の変数を更新していくのはイケてない感があります。年数内部で保持できるクロージャにできるとスッキリしそうです。

クロージャーを使ってフォーマットする

from re import search

def froamt(_year):
  year = _year

  def _format(date):
    nonlocal year

    match = search(r'\d+年', date)
    if match:
      year = match.group()
      ymd = date
    else:
      ymd = f'{year}{date}'

    return ymd

  return _format

START_YEAR = '2012年'
f = froamt(START_YEAR)
format_data = [f(v) for v in data]

これで全て YYYY年M月D日 という形式に変換することができました。
Python のスコープはまだよく解ってないのですが、クロージャーの内側の関数の中で外の関数で定義された変数を更新するには nonlocal キーワードでアクセスできるように宣言してあげる必要がありました。

datetime で変換する

データが全て YYYY年M月D日 と言う形にできたので、これを datetime.date でオブジェクトにして strftime でフォーマットしてあげれば良さそうです。
フォーマットする関数を追加します。

from re import search, split
from datetime import date as dt

def format_date(date_str):
  date = dt(*[int(n) for n in split('[年月日]', date_str) if n])
  return date.strftime('%Y-%m-%d (%a)')

def froamt(_year):
  year = _year

  def _format(date):
    nonlocal year

    match = search(r'\d+年', date)
    if match:
      year = match.group()
      ymd = date
    else:
      ymd = f'{year}{date}'
    
    # datetime でフォーマットしたものを返す
    return format_date(ymd)

  return _format

START_YEAR = '2012年'
f = froamt(START_YEAR)
format_data = [f(v) for v in data]

フォーマット関数内のフォーマット方法を変えれば好きな形にフォーマットできます。
これで YYYY-MM-DD (What day of the week) の形のデータに整形することができました!

date オブジェクト化するのにYYYY年M月D日 な文字列を配列にして datetime.date(year, month, date) でオブジェクト化しているのですが、もっとスマートな変換方法がありそうな気がしています。

所感

Python でも 関数を返す関数を作成できることが判ったので良かったです。(個人的に PythonJavaScript 似てるな〜って感じてます。)
クロージャーの内部の関数から外部の変数にアクセスするのに nonlocal 宣言が必要で、このあたりのスコープまわりは理解が足りてないので引き続き調べて理解しておきたいです。


[参考]

ゼロから作るDeep Learning ❷ ―自然言語処理編

ゼロから作るDeep Learning ❷ ―自然言語処理編

  • 作者:斎藤 康毅
  • 発売日: 2018/07/21
  • メディア: 単行本(ソフトカバー)

アイカツ!1stシーズン Blu-ray BOX1

アイカツ!1stシーズン Blu-ray BOX1

  • 発売日: 2014/11/05
  • メディア: Blu-ray

日付でお察しの方が居るかもですがアイカツ! 放送日のリストでした

Python3 Class インスタンス変数とクラス変数・インスタンスメソッドとクラスメソット。それからスタティックメソッド

インスタンス変数とクラス変数

  • インスタンス変数 … self.variable の形で定義する
  • クラス変数 … クラスブロック直下に書かれた変数はクラス変数になる
class User(object):
  # クラス変数
  name = 'User'
  
  def __init__(self, name):
    # インスタンス変数
    self.name = name

User.name # => User
ichigo = User('Ichigo')
ichigo.name # => Ichigo

クラス変数とインスタンス変数を同じ名前で作成してしまうと、一見するとインスタンスの初期化で name を上書きしたように見えてしまう。
__class__インスタンスのクラスにアクセスできるようなので、これを使って name をみてみるとクラスブロック直下の name がクラス変数だと理解できた。

ichigo = User('Ichigo')
ichigo.name # => Ichigo

print(ichigo.__class__)
# => # => <class '__main__.User'>
ichigo.__class__.name # => User

User.name = 'Idol'
ichigo.__class__.name # => Idol
ichigo.name = Ichigo

同じ変数名でもクラス変数とインスタンス変数は別物として扱われている。
ただし、同じ名前の変数を作ってしまうとインスタンスからクラス変数を使いづらいので変数名は被らないようにしたほうが良さそう。

インスタンスメソッドとクラスメソッド

  • インスタンスメソッド … クラスブロック内に通常に作成された関数。第一引数にインスタンス自身が渡される
  • クラスメソッド … クラスブロック内に @classmethod デコレーター付きで作成された関数。第一引数にクラスが渡される
class User(object):
  name = 'User'

  def __init__(self, name, age=None):
    self.name = name
    self.age = age

  def hi(self):
    print(f'Hi, {self.name} ({self.age})')

  @classmethod
  def info(cls):
    print(f'This class is {cls.name}')

# クラスメソッド
User.info() # => This class is User

ichigo = User('Ichigo', 16)
# インスタンスメソッド
ichigo.hi() # => Hi, Ichigo (16)
# クラスメソッドはインスタンスからも呼び出せる
ichigo.info() # => This class is User

クラスからもインスタンスメソッドが呼び出せるには呼び出せるっぽい
多分ダメな使い方と思うけど、第一引数にインスタンスを渡してあげるとエラーにならず実行できた…

ichigo = User('Ichigo', 16)
User.hi(ichigo) # => Hi, Ichigo (16)

クラスメソットとスタティックメソッド

  • クラスメソッド … 第一引数にクラス自身が渡される
  • スタティックメソッド … クラス自身が渡されない
class User(object):
  name = 'User'

  @classmethod
  def info1(cls, message):
    # 第一引数でクラス自身が渡される
    print(f'{cls.name} {message}')

  @staticmethod
  def info2(message):
    # 純粋にメソッドに渡される引数だけが渡される
    print(f'{User.name} {message}')

# クラスメソッド
User.info1('info1')  # => User info1
# インスタンスメソッド
User.info2('info2')  # => User info2

# User を継承したクラス
class Idol(User):
  name = 'Idol'

# 親クラスのクラスメソッドの cls は Idol クラスが渡されるようになる
Idol.info1('Idol.info1')  # => Idol Idol.info1
# スタティックメソッドは動的に渡される変数がないので出力は変わらない
Idol.info2('Idol.info2')  # => User Idol.info2

クラスメソッドの第一引数に渡されるクラスは継承されたクラスでは、継承されたクラス自身が渡されるので、ある意味動的とも言えるのかな。

所感

クラスブロック直下に置かれた変数がクラス変数になるのは、今まで通ってきた言語ではインスタンス変数になることが多かったのでチョットびっくりしました。
後はクラスからインススタンスメソッドが呼び出せたり、同じファイル内でクラス再定義できたり、Pythonって自由な言語なんだな〜という印象です。


[参考]

ジョゼ虎映画とても楽しみなのです。

Python3 class の getter / setter のメモ

また Python を少し触っています。

private 変数 のおさらい

__ 始まりで作成された変数はインスタンスからしかアクセスできない private 変数になる

class User(object):
  def __init__(self, name):
    self.__name = name
  
  def hi(self):
    print(f'Hi, {self.__name}')

ichigo = User('Ichigo')
ichigo.hi()
# => Hi, Ichigo

# private 変数にアクセスしようとしたらエラー
ichigo.__name
# => AttributeError: 'Idol' object has no attribute '__name'

# 存在しない変数にアクセスしようとしてもエラー
ichigo.name
# => AttributeError: 'Idol' object has no attribute 'name'

# private 変数に値を代入
# エラーにはならないが無視される (更新されない)
ichigo.__name = 'Aoi'

ichigo.hi()
# => Hi, Ichigo

インスタンスからしかアクセスできないのチョット使い勝手が悪いので、他の言語にもあるような getter / setter を作成したい。

1. @property デコレーターを使う方法

  • getter: @property デコレーターを使ってメソッドを定義する
  • setter: @{property_name}.setter デコレーターを使ってメソッドを定義する
class User(object):
  def __init__(self, name):
    self.__name = name
  
  def hi(self):
    print(f'Hi, {self.__name}')
  
  # name という getter / setter を作成する
  @property
  def name(self):
    return self.__name

  @name.setter
  def name(self, name):
    self.__name = name

api = User('Aoi')
print(aoi.name)  # => 'Aoi'
aoi.name = 'Kiriya'
print(aoi.name)  # => 'Kiriya'
api.hi() # => Hi, Kiriya

2. property(getter_method, setter_method) 関数を使う方法

property_name = property(getter_method, setter_method) で getter / setter を作成できる

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

  def hi(self):
    print(f'Hi, {self.__name}')

  def get_name(self):
    return self.__name

  def set_name(self, name):
    self.__name = name

  # getter で get_name, setter で set_name を呼び出す
  name = property(get_name, set_name)

ran = User('Ran')
print(ran.name)  # => 'Ran'
ran.name = 'Shibuki'
print(ran.name)  # => 'Shibuki'
ran.hi()  # => 'Hi, Shibuki'

₍ ᐢ. ̫ .ᐢ ₎ デキタ!


[参考]