Pythonのブロックスコープ


93

他の言語でコーディングする場合、次のようにブロックスコープを作成することがあります。

statement
...
statement
{
    statement
    ...
    statement
}
statement
...
statement

(多くの)1つの目的は、コードを読みやすくすることです。特定のステートメントが論理ユニットを形成すること、または特定のローカル変数がそのブロックでのみ使用されることを示すことです。

Pythonで同じことをする慣用的な方法はありますか?


2
One purpose (of many) is to improve code readability-正しく記述された(つまり、zen of pythonに従って)Pythonコードは、そのような飾りが読みやすくなる必要はありません。実際、それは私がPythonについて気に入っている(多くの)ことの1つです。
Burhan Khalid

私は__exit__withステートメントを変更して遊んでみましたglobals()が、失敗しました。
Ruggero Turra 2015

1
リソースの取得に関連する可変のライフタイムを定義することは非常に有用です
Ruggero

24
@BurhanKhalid:それは真実ではありません。Pythonのzenは、一時的な変数でローカルスコープを汚染することを妨げません。あなたが有効にした場合、すべての使用直後に呼ばれているネストされた関数を定義する例に、単一の一時変数のを、パイソンの禅は、いずれかの幸せではありません。明示的に変数の範囲を限定すること、それが直接答えるので、読みやすさを改善するためのツール「これらの識別子は以下で使用しています?」-最もエレガントなPythonコードを読んでも発生する可能性のある質問。
bluenote10 2016年

17
@BurhanKhalid機能がなくても問題ありません。しかし、それを「禅」と呼ぶのは嫌です。
Phil

回答:


80

いいえ、ブロックスコープを作成するための言語サポートはありません。

次の構成はスコープを作成します。

  • モジュール
  • クラス
  • 関数(ラムダを含む)
  • ジェネレータ式
  • 内包表記(dict、set、list(Python 3.xの場合))

37

Pythonの慣用的な方法は、関数を短くすることです。これが必要だと思われる場合は、コードをリファクタリングしてください!:)

Pythonは、各モジュール、クラス、関数、ジェネレータ式、dict内包、セット内包、およびPython 3.xで各リスト内包に対して新しいスコープを作成します。これらを除いて、関数内にネストされたスコープはありません。


12
「プログラミングで最も重要なことは、何かに名前を付けることができることです。次に重要なことは、何かに名前を付ける必要がないことです。」ほとんどの場合、Pythonではスコープ(変数など)に名前を付ける必要があります。この点で、Python変数は2番目に重要なテストです。
Krazy Glew 2016年

19
プログラミングで最も重要なことは、アプリケーションの依存関係を管理し、コードブロックのスコープを管理する機能です。匿名ブロックを使用すると、コールバックの存続期間を制限できます。それ以外の場合、コールバックは1回しか使用されず、プログラムの存続期間は存続します。これにより、グローバルスコープが乱雑になり、コードの可読性が損なわれます。
Dmitry

変数がディクテーション/セット内包に対してローカルでもあることに気づきました。Python 2.7と3.3を試しましたが、バージョンに依存しているかどうかはわかりません。
wjandrea

1
@wjandrea正解です–リストに追加されました。これらのPythonバージョンには違いはありません。
Sven Marnach

3
関数内に関数を非常に作成できるため、最後の文を書き直します。したがって、関数内にネストされたスコープがあります。
ThomasH

17

関数内で関数を宣言し、すぐにそれを呼び出すことにより、PythonのC ++ブロックスコープと同様のことができます。例えば:

def my_func():
    shared_variable = calculate_thing()

    def do_first_thing():
        ... = shared_variable
    do_first_thing()

    def do_second_thing():
        foo(shared_variable)
        ...
    do_second_thing()

なぜこれをしたいのかわからない場合は、このビデオで納得できるでしょう。

基本的な原則は、「ガベージ」(追加の型/関数)を絶対に必要なよりも広いスコープに導入せずに、すべてを可能な限り厳密にスコープすることです- do_first_thing()たとえば、メソッドを使用する必要がないため、スコープ外ではスコープしないでください。関数の呼び出し。


これは、Google開発者がTensorFlowチュートリアルで使用している方法でもあります。たとえば、こちら
Nino Filiu

13

ブロックスコープがないことに同意します。しかし、Python 3の1つの場所では、ブロックスコープがあるかのようにSEEMになります。

何が起こったのですか?これはpython 2では適切に機能していましたが、python 3で変数リークを停止させるために、このトリックを実行しました。この変更により、ブロックスコープがあるかのように見えます。

説明させてください。


スコープの概念に従って、同じスコープ内に同じ名前の変数を導入する場合、その値を変更する必要があります。

これはPython 2で起こっていることです

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'

しかし、Python 3では、同じ名前の変数が導入されてもオーバーライドされませんが、リスト内包表記は何らかの理由でサンドボックスのように機能し、その中に新しいスコープを作成するように見えます。

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'

この回答は回答者の@Thomasのステートメントに反します。スコープを作成する唯一の方法は、新しいスコープを作成するもう1つの場所のように見えるため、関数、クラス、またはモジュールです。


0

モジュール(およびパッケージ)は、プログラムを個別の名前空間に分割するための優れたPythonの方法です。これは、この質問の暗黙の目標のようです。確かに、私はPythonの基本を学んでいたので、ブロックスコープ機能がないことに不満を感じました。しかし、Pythonモジュールを理解すると、ブロックスコープを必要とせずに以前の目標をよりエレガントに実現できました。

動機として、そして人々を正しい方向に向けるために、私はいくつかのPythonのスコープ構成の明示的な例を提供することは有用だと思います。最初に、Pythonクラスを使用してブロックスコープを実装する試みの失敗について説明します。次に、Pythonモジュールを使用して、さらに便利な方法を実現した方法を説明します。最後に、データのロードとフィルタリングへのパッケージの実用的なアプリケーションについて概説します。

クラスでブロックスコープを試す

しばらくの間、クラス宣言内にコードを貼り付けることでブロックスコープを達成したと思いました。

x = 5
class BlockScopeAttempt:
    x = 10
    print(x) # Output: 10
print(x) # Output: 5

残念ながら、これは関数が定義されている場合に機能しなくなります。

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(x) 
    printx2() # Output: 5!!!

これは、クラス内で定義された関数がグローバルスコープを使用するためです。これを修正する最も簡単な(唯一ではありませんが)方法は、クラスを明示的に指定することです。

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(BlockScopeAttempt.x)  # Added class name
    printx2() # Output: 10

クラスに含まれているかどうかに応じて関数を異なる方法で記述する必要があるため、これはそれほどエレガントではありません。

Pythonモジュールでより良い結果

モジュールは静的クラスに非常に似ていますが、モジュールは私の経験でははるかにきれいです。モジュールで同じことを行うにはmy_module.py、現在の作業ディレクトリに次の内容で呼び出されるファイルを作成します。

x = 10
print(x) # (A)

def printx():
    global x
    print(x) # (B)

次に、メインファイルまたはインタラクティブ(例:Jupyter)セッションで、

x = 5
import my_module # Output: 10 from (A)
my_module.printx() # Output: 10 from (B)
print(x) # Output: 5

説明として、各Pythonファイルは独自のグローバル名前空間を持つモジュールを定義します。モジュールをインポートすると、この名前空間の変数に.構文でアクセスできます。

インタラクティブセッションでモジュールを使用している場合は、最初にこれらの2行を実行できます。

%load_ext autoreload
%autoreload 2

モジュールは、対応するファイルが変更されると自動的に再ロードされます。

データのロードとフィルタリングのためのパッケージ

パッケージの概念は、モジュールの概念を少し拡張したものです。パッケージは、__init__.pyインポート時に実行される(おそらく空白の)ファイルを含むディレクトリです。このディレクトリ内のモジュール/パッケージには、.構文でアクセスできます。

データ分析では、大きなデータファイルを読み取ってから、さまざまなフィルターをインタラクティブに適用する必要があります。ファイルの読み取りには数分かかるので、1回だけ行いたいと思います。私はオブジェクト指向プログラミングについて学校で学んだことを基に、以前はクラスのメソッドとしてフィルタリングとロードのコードを書くべきだと信じていました。このアプローチの主な欠点は、フィルターを再定義すると、クラスの定義が変更されるため、データを含むクラス全体を再ロードする必要があることです。

最近のPythonでは、およびmy_dataという名前のサブモジュールを含むというパッケージを定義しています。内部では相対インポートを行うことができます:loadfilterfilter.py

from .load import raw_data

私が変更した場合filter.py、そのautoreload変更を検出します。再読み込みしないload.pyので、データを再読み込みする必要はありません。このようにして、Jupyterノートブックでフィルタリングコードのプロトタイプを作成し、関数としてラップして、ノートブックから直接に貼り付けfilter.pyます。これを理解することは私のワークフローに革命をもたらし、私を懐疑論者から「Pythonの禅」への信者へと変えました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.