検索
ホームページバックエンド開発Python チュートリアルPython 関数のローカル変数はどのように実行されますか? Python 関数変数の適用の簡単な分析

この記事では、Python 関数のローカル変数を実行する方法について説明します。 Python 関数の変数の適用に関する簡単な分析は、一定の参考値を持っています。必要な友人はそれを参照することができます。

まえがき

この 2 日間、CodeReview にいたとき、次のコードを見つけました。

# 伪代码
import somelib
class A(object):
    def load_project(self):
        self.project_code_to_name = {}
        for project in somelib.get_all_projects():
            self.project_code_to_name[project] = project
        ...

目的は非常に単純です。つまり、somelib.get_all_projects 取得したプロジェクトを self.project_code_to_name

に挿入しましたが、最適化の余地があると感じたので、調整案 ## を提案しました。 #

import somelib
class A(object):
    def load_project(self):
        project_code_to_name = {}
        for project in somelib.get_all_projects():
            project_code_to_name[project] = project
        self.project_code_to_name = project_code_to_name
        ...
計画は非常に簡単で、最初にローカル変数

project_code_to_name を定義し、操作の完了後にそれを self.project_code_to_name に割り当てます。

その後のテストでは、この方が良いことがわかりました。結果がわかったので、次はその理由を探っていきたいと思います。

ローカル変数

実際、インターネット上のさまざまな場所や多くの書籍でさえ言及されている観点があります。

ローカル変数へのアクセスは非常に困難です。 fast 、一見意味がありそうに見えますが、下にたくさんのテスト データが掲載されています。何のことかわかりませんが、非常に素晴らしいものです。覚えておいてください。心配しないでください。

しかし実際には、この見解にはまだ一定の制限があり、普遍的に適用できるわけではありません。それでは、まずこの文と、なぜ誰もがこの文を好んで言うのかを理解しましょう。

まず、ローカル変数とは何かを理解するためにコードを見てください:

#coding: utf8
a = 1
def test(b):
    c = 'test'    
    print a   # 全局变量
    print b   # 局部变量
    print c   # 局部变量

test(3)
# 输出
1
3
test
简单来说,局部变量就是只作用于所在的函数域,超过作用域就被回收
ローカル変数とは何かを理解するには、Python 関数とローカル変数の間の好き嫌いについて話す必要があります。これを知らないと速度を感じるのが難しいです;

退屈しないように上記のコードで説明しましょう、ちなみに

test 関数を付けておきます実行 dis の分析:

# CALL_FUNCTION

  5           0 LOAD_CONST               1 ('test')
              3 STORE_FAST               1 (c)

  6           6 LOAD_GLOBAL              0 (a)
              9 PRINT_ITEM
             10 PRINT_NEWLINE

  7          11 LOAD_FAST                0 (b)
             14 PRINT_ITEM
             15 PRINT_NEWLINE

  8          16 LOAD_FAST                1 (c)
             19 PRINT_ITEM
             20 PRINT_NEWLINE
             21 LOAD_CONST               0 (None)
             24 RETURN_VALUE
上の図では、a、b、c に対応する命令ブロックがはっきりとわかります。各ブロックの最初の行は

LOAD_XXX です。 . 名前が示すように、これらの変数は Explain から取得されます。

LOAD_GLOBAL グローバルであることは間違いありませんが、LOAD_FAST とは一体何なのでしょうか? LOAD_LOCAL という名前にする必要があるようですね。

しかし、事実は非常に魔法です。実際には

LOAD_FAST と呼ばれます。ローカル変数は fastlocals という配列から読み取られるため、名前は次のようになります。と叫んだ(たぶん)。

それでは主人公が登場します。これを理解することに集中する必要があります。これは確かに非常に興味深いものだからです。

Python 関数の実行

Python 関数の構築と操作は、複雑とも言えますが、簡単とも言い切れません。たとえば、関数とメソッドは、パラメーターの有無、パラメーターの種類、可変長パラメーターの有無、およびキー パラメーターの有無によってさらに区別されます。

すべてを詳細に説明することは不可能ですが、一般的なプロセスを簡単に説明します (パラメーター変更の詳細は無視します):

Python 関数のローカル変数はどのように実行されますか? Python 関数変数の適用の簡単な分析

fast_function まで進みます。ここでの呼び出しは:

// ceval.c -> call_function

x = fast_function(func, pp_stack, n, na, nk);
パラメータの説明:

  1. func: Pass Enter

    test;

  2. ##pp_stack: コールスタック (py モード) のおおよその理解;
  3. na: 位置パラメータ数値;
  4. ##nk: キーワードの数;
  5. n = na 2 * nk;
  6. それでは、何を見てみましょう
  7. fast_function
が次に実行します。

wave の初期化

テスト オブジェクトに
    func_code を格納するために co を定義します
  1. 定義globals
  2. func_globals
  3. (辞書) をテスト オブジェクトに格納するには

    argdefs を定義して、
  4. func_defaults
  5. (関数構築時のキーワード) を格納しますテストオブジェクト パラメータのデフォルト値)

  6. argdefsが空なら判定してみましょう
&&

渡される位置パラメータの数 == 位置パラメータ関数が定義されているとき Number && キーワードパラメータが渡されない場合はthen

use
    現在のスレッドステータス
  1. ,

    co, globals 新しいスタック オブジェクトを作成しますf;

    define
  2. fastlocals
  3. (fastlocals = f- >f_localsplus; );

    渡されたすべてのパラメータを入力します
  4. fastlocals
  5. 次に、どのように接続するかという質問が生じます。どのゴースト パラメータが渡されるかを確認する方法: この質問には
  6. dis
のみが回答できます:

このステップは CALL_FUNCTION

で実行されることがわかっているため、パラメータを接続するアクションはこの前にある必要があります。したがって、

 12          27 LOAD_NAME                2 (test)
             30 LOAD_CONST               4 (3)
             33 CALL_FUNCTION            1
             36 POP_TOP
             37 LOAD_CONST               1 (None)
             40 RETURN_VALUE
CALL_FUNCTION で

30 LOAD_CONST を確認できます。興味のある方は、さらにいくつかのパラメータを渡してみてください。渡されたパラメータは LOAD_CONST を通じて順次ロードされることがわかり、パラメータを見つける方法の問題が明らかになります。

// fast_function 函数

fastlocals = f->f_localsplus;
stack = (*pp_stack) - n;

 for (i = 0; i <p>这里出现的 n 还记得怎么来的吗?回顾上面有个 <code>n = na + 2 * nk;</code> ,能想起什么吗?</p><p>其实这个地方就是简单的通过将 <code>pp_stack</code> 偏移 n 字节 找到一开始塞入参数的位置。</p><p>那么问题来了,如果 n 是 位置参数个数 + 关键字参数,那么 2 * nk 是什么意思?其实这答案很简单,那就是 关键字参数字节码 是属于带参数字节码, 是占 2字节。</p><p>到了这里,栈对象 <code>f</code> 的 <code>f_localsplus</code> 也登上历史舞台了,只是此时的它,还只是一个未经人事的少年,还需历练。</p><p>做好这些动作,终于来到真正执行函数的地方了: <code>PyEval_EvalFrameEx</code>,在这里,需要先交代下,有个和 <code>PyEval_EvalFrameEx</code> 很像的,叫 <code>PyEval_EvalCodeEx</code>,虽然长得像,但是人家干得活更多了。</p><p>请看回前面的 <code>fast_function</code> 开始那会有个判断,我们上面说得是判断成立的,也就是最简单的函数执行情况。如果函数传入多了关键字参数或者其他情况,那就复杂很多了,此时就需要由 <code>PyEval_EvalCodeEx</code> 处理一波,再执行 <code>PyEval_EvalFrameEx</code>。</p><p><code>PyEval_EvalFrameEx</code>  主要的工作就是解析字节码,像刚才的那些 <code>CALL_FUNCTION</code>,<code>LOAD_FAST</code> 等等,都是由它解析和处理的,它的本质就是一个死循环,然后里面有一堆 <code>swith - case</code>,这基本也就是 Python 的运行本质了。</p><h4 id="f-localsplus-存-和-取">f_localsplus 存 和 取</h4><p>讲了这么长的一堆,算是把 Python 最基本的 函数调用过程简单扫了个盲,现在才开始探索主题。。</p><p>为了简单阐述,直接引用名词:<code>fastlocals</code>,  其中 <code>fastlocals = f->f_localsplus</code></p><p>刚才只是简单看到了,Python 会把传入的参数,以此塞入 <code>fastlocals</code> 里面去,那么毋庸置疑,传入的位置参数,必然属于局部变量了,那么关键字参数呢?那肯定也是局部变量,因为它们都被特殊对待了嘛。</p><p>那么除了函数参数之外,必然还有函数内部的赋值咯? 这块字节码也一早在上面给出了:</p><pre class="brush:php;toolbar:false"># CALL_FUNCTION
  5           0 LOAD_CONST               1 ('test')
              3 STORE_FAST               1 (c)

这里出现了新的字节码 STORE_FAST,一起来看看实现把:

# PyEval_EvalFrameEx 庞大 switch-case 的其中一个分支:

        PREDICTED_WITH_ARG(STORE_FAST);
        TARGET(STORE_FAST)
        {
            v = POP();
            SETLOCAL(oparg, v);
            FAST_DISPATCH();
        }

# 因为有涉及到宏,就顺便给出:
#define GETLOCAL(i)     (fastlocals[i])
#define SETLOCAL(i, value)      do { PyObject *tmp = GETLOCAL(i); \
                                     GETLOCAL(i) = value; \
                                     Py_XDECREF(tmp); } while (0)

简单解释就是,将 POP() 获得的值 v,塞到 fastlocals 的  oparg 位置上。此处,v 是 "test", oparg 就是 1。用图表示就是:

Python 関数のローカル変数はどのように実行されますか? Python 関数変数の適用の簡単な分析

有童鞋可能会突然懵了,为什么突然来了个 b ?我们又需要回到上面看 test 函数是怎样定义的:

// 我感觉往回看的概率超低的,直接给出算了

def test(b):
    c = 'test'    
    print b   # 局部变量
    print c   # 局部变量

看到函数定义其实都应该知道了,因为 b 是传的参数啊,老早就塞进去了~

那存储知道了,那么怎么取呢?同样也是这段代码的字节码:

22 LOAD_FAST                1 (c)

虽然这个用脚趾头想想都知道原理是啥,但公平起见还是给出相应的代码:

# PyEval_EvalFrameEx 庞大 switch-case 的其中一个分支:
TARGET(LOAD_FAST)
{
    x = GETLOCAL(oparg);
    if (x != NULL) {
        Py_INCREF(x);
        PUSH(x);
        FAST_DISPATCH();
    }
    format_exc_check_arg(PyExc_UnboundLocalError,
        UNBOUNDLOCAL_ERROR_MSG,
        PyTuple_GetItem(co->co_varnames, oparg));
    break;
}

直接用 GETLOCAL 通过索引在数组里取值了。

到了这里,应该也算是把 f_localsplus  讲明白了。这个地方不难,其实一般而言是不会被提及到这个,因为一般来说忽略即可了,但是如果说想在性能方面讲究点,那么这个小知识就不得忽视了。

变量使用姿势

因为是面向对象,所以我们都习惯了通过 class 的方式,对于下面的使用方式,也是随手就来:

class SS(object):
    def __init__(self):
        self.fuck = {}

    def test(self):
        print self.fuck

这种方式一般是没什么问题的,也很规范。到那时如果是下面的操作,那就有问题了:

class SS(object):
    def __init__(self):
        self.fuck = {}

    def test(self):
        num = 10
        for i in range(num):
            self.fuck[i] = i

这段代码的性能损耗,会随着 num 的值增大而增大, 如果下面循环中还要涉及到更多类属性的读取、修改等等,那影响就更大了

这个类属性如果换成 全局变量,也会存在类似的问题,只是说在操作类属性会比操作全局变量要频繁得多。

我们直接看看两者的差距有多大把?

import timeit
class SS(object):
    def test(self):
        num = 100
        self.fuck = {}        # 为了公平,每次执行都同样初始化新的 {}
        for i in range(num):
            self.fuck[i] = i

    def test_local(self):
        num = 100
        fuck = {}             # 为了公平,每次执行都同样初始化新的 {}
        for i in range(num):
            fuck[i] = i
        self.fuck = fuck

s = SS()
print timeit.timeit(stmt=s.test_local)
print timeit.timeit(stmt=s.test)

Python 関数のローカル変数はどのように実行されますか? Python 関数変数の適用の簡単な分析

通过上图可以看出,随着 num 的值越大,for 循环的次数就越多,那么两者的差距也就越大了。

那么为什么会这样,也是在字节码可以看出写端倪:

// s.test
        >>   28 FOR_ITER                19 (to 50)
             31 STORE_FAST               2 (i)

  8          34 LOAD_FAST                2 (i)
             37 LOAD_FAST                0 (self)
             40 LOAD_ATTR                0 (hehe)
             43 LOAD_FAST                2 (i)
             46 STORE_SUBSCR
             47 JUMP_ABSOLUTE           28
        >>   50 POP_BLOCK

// s.test_local
        >>   25 FOR_ITER                16 (to 44)
             28 STORE_FAST               3 (i)

 14          31 LOAD_FAST                3 (i)
             34 LOAD_FAST                2 (hehe)
             37 LOAD_FAST                3 (i)
             40 STORE_SUBSCR
             41 JUMP_ABSOLUTE           25
        >>   44 POP_BLOCK

 15     >>   45 LOAD_FAST                2 (hehe)
             48 LOAD_FAST                0 (self)
             51 STORE_ATTR               1 (hehe)

上面两段就是两个方法的 for block 内容,大家对比下就会知道,  s.test 相比于 s.test_local,  多了个 LOAD_ATTR 放在 FOR_ITERPOP_BLOCK 之间。

这说明什么呢? 这说明,在每次循环时,s.test 都需要 LOAD_ATTR,很自然的,我们需要看看这个是干什么的:

TARGET(LOAD_ATTR)
{
     w = GETITEM(names, oparg);
     v = TOP();
     x = PyObject_GetAttr(v, w);
     Py_DECREF(v);
     SET_TOP(x);
     if (x != NULL) DISPATCH();
     break;
 }

# 相关宏定义
#define GETITEM(v, i) PyTuple_GetItem((v), (i))

这里出现了一个陌生的变量 name, 这是什么?其实这个就是每个 codeobject 所维护的一个 名字数组,基本上每个块所使用到的字符串,都会在这里面存着,同样也是有序的:

// PyCodeObject 结构体成员
PyObject *co_names;        /* list of strings (names used) */

那么 LOAD_ATTR 的任务就很清晰了:先从名字列表里面取出字符串,结果就是 "hehe", 然后通过 PyObject_GetAttr 去查找,在这里就是在 s 实例中去查找。

且不说查找效率如何,光多了这一步,都能失之毫厘差之千里了,当然这是在频繁操作次数比较多的情况下。

所以我们在一些会频繁操作 类/实例属性 的情况下,应该是先把 属性 取出来存到 局部变量,然后用 局部变量 来完成操作。最后视情况把变动更新到属性上。

最后

其实相比变量,在函数和方法的使用上面更有学问,更值得探索,因为那个原理和表面看起来差别更大,下次有机会再探讨。平时工作多注意下,才能使得我们的 PY 能够稍微快点点点点点。

相关推荐:

理解python的全局变量和局部变量

python函数局部变量用法实例分析

详解Python的局部变量和全局变量使用难点

以上がPython 関数のローカル変数はどのように実行されますか? Python 関数変数の適用の簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
Python vs. C:重要な違​​いを理解しますPython vs. C:重要な違​​いを理解しますApr 21, 2025 am 12:18 AM

PythonとCにはそれぞれ独自の利点があり、選択はプロジェクトの要件に基づいている必要があります。 1)Pythonは、簡潔な構文と動的タイピングのため、迅速な開発とデータ処理に適しています。 2)Cは、静的なタイピングと手動メモリ管理により、高性能およびシステムプログラミングに適しています。

Python vs. C:プロジェクトのためにどの言語を選択しますか?Python vs. C:プロジェクトのためにどの言語を選択しますか?Apr 21, 2025 am 12:17 AM

PythonまたはCの選択は、プロジェクトの要件に依存します。1)迅速な開発、データ処理、およびプロトタイプ設計が必要な場合は、Pythonを選択します。 2)高性能、低レイテンシ、および緊密なハードウェアコントロールが必要な場合は、Cを選択します。

Pythonの目標に到達する:毎日2時間のパワーPythonの目標に到達する:毎日2時間のパワーApr 20, 2025 am 12:21 AM

毎日2時間のPython学習を投資することで、プログラミングスキルを効果的に改善できます。 1.新しい知識を学ぶ:ドキュメントを読むか、チュートリアルを見る。 2。練習:コードと完全な演習を書きます。 3。レビュー:学んだコンテンツを統合します。 4。プロジェクトの実践:実際のプロジェクトで学んだことを適用します。このような構造化された学習計画は、Pythonを体系的にマスターし、キャリア目標を達成するのに役立ちます。

2時間の最大化:効果的なPython学習戦略2時間の最大化:効果的なPython学習戦略Apr 20, 2025 am 12:20 AM

2時間以内にPythonを効率的に学習する方法は次のとおりです。1。基本的な知識を確認し、Pythonのインストールと基本的な構文に精通していることを確認します。 2。変数、リスト、関数など、Pythonのコア概念を理解します。 3.例を使用して、基本的および高度な使用をマスターします。 4.一般的なエラーとデバッグテクニックを学習します。 5.リストの概念を使用したり、PEP8スタイルガイドに従ったりするなど、パフォーマンスの最適化とベストプラクティスを適用します。

PythonとCのどちらかを選択:あなたに適した言語PythonとCのどちらかを選択:あなたに適した言語Apr 20, 2025 am 12:20 AM

Pythonは初心者やデータサイエンスに適しており、Cはシステムプログラミングとゲーム開発に適しています。 1. Pythonはシンプルで使いやすく、データサイエンスやWeb開発に適しています。 2.Cは、ゲーム開発とシステムプログラミングに適した、高性能と制御を提供します。選択は、プロジェクトのニーズと個人的な関心に基づいている必要があります。

Python vs. C:プログラミング言語の比較分析Python vs. C:プログラミング言語の比較分析Apr 20, 2025 am 12:14 AM

Pythonはデータサイエンスと迅速な発展により適していますが、Cは高性能およびシステムプログラミングにより適しています。 1. Python構文は簡潔で学習しやすく、データ処理と科学的コンピューティングに適しています。 2.Cには複雑な構文がありますが、優れたパフォーマンスがあり、ゲーム開発とシステムプログラミングでよく使用されます。

1日2時間:Python学習の可能性1日2時間:Python学習の可能性Apr 20, 2025 am 12:14 AM

Pythonを学ぶために1日2時間投資することは可能です。 1.新しい知識を学ぶ:リストや辞書など、1時間で新しい概念を学びます。 2。練習と練習:1時間を使用して、小さなプログラムを書くなどのプログラミング演習を実行します。合理的な計画と忍耐力を通じて、Pythonのコアコンセプトを短時間で習得できます。

Python vs. C:曲線と使いやすさの学習Python vs. C:曲線と使いやすさの学習Apr 19, 2025 am 12:20 AM

Pythonは学習と使用が簡単ですが、Cはより強力ですが複雑です。 1。Python構文は簡潔で初心者に適しています。動的なタイピングと自動メモリ管理により、使いやすくなりますが、ランタイムエラーを引き起こす可能性があります。 2.Cは、高性能アプリケーションに適した低レベルの制御と高度な機能を提供しますが、学習しきい値が高く、手動メモリとタイプの安全管理が必要です。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

VSCode Windows 64 ビットのダウンロード

VSCode Windows 64 ビットのダウンロード

Microsoft によって発売された無料で強力な IDE エディター

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

mPDF

mPDF

mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!