9
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python の nonlocal と global

Last updated at Posted at 2020-03-01

はじめに

  • global 文と nonlocal 文はいつ使うの?
    • 「関数の外側のスコープにある変数」を関数の内側のスコープで 変更 したいとき
予約語 変更対象となる変数
global 一番外側のスコープにある変数(グローバル変数)
nonlocal nonlocal 文が使われた関数の外側のスコープにある変数

1. global 文

  • global 文はいつ使うの?
    • 関数の中でグローバル変数を変更したいとき

O 正しいコード

#
# 対話モード >>> に
# コピペで実行できます。
#

間隔 = 1

def 間隔を設定する(新しい間隔):
    global 間隔
    print(間隔)
    間隔 = 新しい間隔

間隔を設定する(2)  # 1
間隔  # 2

X 誤ったコード

間隔 = 1

def 間隔を設定する(新しい間隔):
    # 1. global 文を使わずに
    # global 間隔
    
    # 2. グローバル変数を参照してから
    print(間隔)
    
    # 3. ローカル変数を定義しようとすると
    間隔 = 新しい間隔  

# 4. 例外が発生します
間隔を設定する(2)
# UnboundLocalError:
#     cannot access local variable '間隔'
#     where it is not associated with a value

間隔  # 1
長い解説
間隔 = 1

def 間隔を設定する(新しい間隔):
    # 1. global 文を使わずに
    # global 間隔
    
    # 2. 関数の外側の変数を参照してから
    print(間隔)
    
    # 3. 関数の外側の変数と同じ名前を変数を
    #    関数の内側で定義するようなコードを書くと
    間隔 = 新しい間隔  

# 4. 例外が発生します
間隔を設定する(2)  # print 関数は実行されない
# 結果
#     UnboundLocalError:
#         cannot access local variable '間隔'
#         where it is not associated with a value
#
# 直訳
#     未束縛ローカルエラー:
#         ローカル変数 '間隔' にアクセスできません、
#         ローカル変数 '間隔' が値に関連付けられていない状態で
#
# 意訳
#     代入される前のローカル変数を参照してしまったエラー:
#         ローカル変数 '間隔' に値が代入されていない状態で
#         ローカル変数 '間隔' にアクセスできません
#
# 解釈
#     エラー文を見る限り global を使わずに関数内で変数 '間隔' に代入を行うコードを書くと
#     関数内では変数 '間隔' はローカル変数として取り扱われるということでしょうか...?

間隔  # 1
  • UnboundLocalError
    • どこで例外が発生するの?
      • 関数定義文 def 間隔を設定する(新しい間隔): ではなく
      • 関数呼び出し 間隔を設定する(2) で例外が発生します
    • 関数は実行されないので print(間隔) は実行されません

2. nonlocal 文

  • nonlocal っていつ使うの?
    • わざわざクラス定義文を使うのが面倒だなってとき

かなと個人的に思っています。うまく説明できないのですが...orz

Step 1. nonlocal 文で実装

#
# 対話モード >>> に
# コピペで実行できます。
#
間隔 = 1  # クラス変数, 一番外側の変数

def 間隔を設定する(新しい間隔):
    global 間隔
    間隔 = 新しい間隔

def カウンターを作る():
    現在の値 = 0  # インスタンス変数, 外側の変数
    def カウントする():
        nonlocal 現在の値
        現在の値 = 現在の値 + 間隔
        return 現在の値
    return カウントする



カウントするA = カウンターを作る()
カウントするA()  # 1
カウントするA()  # 2
カウントするA()  # 3
間隔を設定する(2)
カウントするA()  # 5
カウントするA()  # 7
カウントするA()  # 9

カウントするB = カウンターを作る()
間隔を設定する(3)
カウントするB()  # 3
カウントするB()  # 6
カウントするB()  # 9
間隔を設定する(4)
カウントするB()  # 13
カウントするB()  # 17
カウントするB()  # 21

カウントするA()  # 13
カウントするA()  # 17
カウントするA()  # 21

Step 2. nonlocal 文で実装

ちょっとクラスっぽく実装したいな...

#
# 対話モード >>> に
# コピペで実行できます。
#
間隔 = 1  # クラス変数, 一番外側の変数

def カウンターを作る():
    現在の値 = 0  # インスタンス変数, 外側の変数
    
    def 間隔を設定する(新しい間隔):
        global 間隔
        間隔 = 新しい間隔
    
    def カウントする():
        nonlocal 現在の値
        現在の値 += 間隔
        return 現在の値
    
    return {
        'カウントする': カウントする,
        '間隔を設定する': 間隔を設定する
    }

# インスタンスを作成して使用
カウンターA = カウンターを作る()
カウンターA['カウントする']()  # 1
カウンターA['カウントする']()  # 2
カウンターA['カウントする']()  # 3

カウンターA['間隔を設定する'](2)
カウンターA['カウントする']()  # 5
カウンターA['カウントする']()  # 7
カウンターA['カウントする']()  # 9

カウンターB = カウンターを作る()
カウンターB['間隔を設定する'](3)
カウンターB['カウントする']()  # 3
カウンターB['カウントする']()  # 6
カウンターB['カウントする']()  # 9

カウンターB['間隔を設定する'](4)
カウンターB['カウントする']()  # 13
カウンターB['カウントする']()  # 17
カウンターB['カウントする']()  # 21

カウンターA['カウントする']()  # 13
カウンターA['カウントする']()  # 17
カウンターA['カウントする']()  # 21

なんやクソコードみせんなこの野郎😡って感じですが、この Step 2 の書き方は Step 3 と比較して self を使っていないことを心の片隅においておいてもよいかなと感じました... React の関数コンポーネントの書き方はこの Step 2 の書き方と似ています... 伝わるかはわからないのですがあとで React の関数コンポーネントのコードを示します...

Step 3. クラス定義文で実装

クラスで実装したいな...

#
# 対話モード >>> に
# コピペで実行できます。
#
class カウンタークラス:
    間隔 = 1  # クラス変数

    def __init__(self):
        self.現在の値 = 0  # インスタンス変数
    
    @classmethod
    def 間隔を設定する(cls, 新しい間隔):
        cls.間隔 = 新しい間隔
    
    def カウントする(self):
        self.現在の値 += カウンタークラス.間隔
        return self.現在の値

# インスタンスを作成して使用
カウンターA = カウンタークラス()
カウンターA.カウントする()  # 1
カウンターA.カウントする()  # 2
カウンターA.カウントする()  # 3

カウンターA.間隔を設定する(2)
カウンターA.カウントする()  # 5
カウンターA.カウントする()  # 7
カウンターA.カウントする()  # 9

カウンターB = カウンタークラス()
カウンターB.間隔を設定する(3)
カウンターB.カウントする()  # 3
カウンターB.カウントする()  # 6
カウンターB.カウントする()  # 9

カウンターB.間隔を設定する(4)
カウンターB.カウントする()  # 13
カウンターB.カウントする()  # 17
カウンターB.カウントする()  # 21

カウンターA.カウントする()  # 13
カウンターA.カウントする()  # 17
カウンターA.カウントする()  # 21

4. そのほかの言語 - self を書かない動向

(1) Kotlin

Kotlin はクラス定義文の中で this, Python でいうところの self が省略できるらしいです。


(2) Swift

Swift もまたクラス定義文の中で self が省略できるらしいです。そしてそれが主流らしいです。

本記事の結論はselfを書くべき、としていますが、現時点では逆の結論に至っています。それは下記の理由から:

  • closureにキャプチャされているselfを見つけやすくするため。
  • GitHubなどをみる限り、ポピュラーなほうはselfを省略するスタイル。

(3) JavaScript

React でコンポーネントという HTML の塊みたいなものを定義するときにクラスではなく関数で定義するようになりました。理由は this, Python でいうところの self を書くのが面倒になったからなのかなと思っています。

React の関数で定義されたコンポーネントは初見で見ると「?」ってなるので焦るのですが self って書くのが面倒だったのかなくらいに抑えておけばよいのかなと思っています... 小並感

React におけるコンポーネントの定義

クラスコンポーネントよりも関数コンポーネントのほうが短くなっているよねっていうのがポイントです。

クラス ... Step 3 的な書き方

this.setStatethis.state, Python でいうところのインスタンス変数を全部更新しているのがポイント。なんで一部のプロパティだけでなくすべてのプロパティを更新しているのかは知りません。

See the Pen Untitled by domodomodomo (@nico25) on CodePen.

関数 ... Step 2 的な書き方

const [現在の値, set現在の値] = useState(0); は所謂 getter と setter です。setter が起動すると値の更新を通知するようになっている はず です。

See the Pen nonlocal - 関数コンポーネント by domodomodomo (@nico25) on CodePen.


↓ useState は本当になにやっているかわからない...


↓「クラスを使わない」ことが必ずしも「オブジェクト指向ではない」ことを表しているわけではないよね、という話をされているのだと思います。


◯ まとめ

バックエンドはわかりませんがフロントエンド, UI については self を書かない流れなのかなと感じました。


おわりに

関数の中で定義する関数の使いどころとしては、他にも「デコレータ」と「カリー化, 部分的適用」があります。

9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?