Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

[python] 皆さんはどのようにして参照の概念を理解しましたか?

Pythonを教えているのですが、参照の概念を理解するのに苦戦している方がいます。

例えば、以下のコードです。

x = ["a", [1, 2, 3], "b"]
y = x[1]
z = y

y = 1

print(x) # => ["a", [1, 2, 3], "b"]
print(y) # => 1
print(z) # => [1, 2, 3]

このようなオブジェクトの参照関係がうまく掴めず、特に

  • 多次元リスト(リストの中のリストなど)
  • 辞書内のリスト(その中に辞書があるなど)

といった複雑なデータ構造になると、変数がどこを参照しているのか追えなくなるようです。

関数にリストを渡したときに、「どのような操作をしたら元の変数に影響が出るのか?出ないのか?」という点も、まだ直感的に理解できていないようで、良い教材や解説方法を探しています。

いくつかYouTubeの動画も参考にしてもらいましたが、参照について細かく解説しているものは少なく、なかなか「これだ!」というものが見つかりませんでした。

参考に見てもらったyoutube

How variables work in Python | Explained with Animations [See description/first comment]
https://www.youtube.com/watch?v=0Om2gYU6clE

WHY IS THE STACK SO FAST?
https://www.youtube.com/watch?v=N3o5yHYLviQ

WHY IS THE HEAP SO SLOW?
https://www.youtube.com/watch?v=ioJkA7Mw2-U

Understand the weirdness. How Python Passes Arguments.
https://www.youtube.com/watch?v=D3IffJOwDY0

MIT 6.100L Introduction to CS and Programming using Python, Fall 2022
https://www.youtube.com/playlist?list=PLUl4u3cNGP62A-ynp6v6-LGBCzeH3VAQB

【Pythonプログラミング入門】ミュータブル・イミュータブルを解説!〜VTuberと学習〜 【初心者向け】
https://youtu.be/PQjdqQbjnLU?si=cW3E1RyjXyVF3vbe&t=234
日本でよく売れてる本の人でもこの数分程度しか参照の事は説明してない

  • 3:54 参照とは
  • 4:55 関数の引数は参照渡し
  • 6:58 エンディング

お尋ねですが

皆さんは、参照の概念をどのように理解・習得しましたか?
また、初心者が分かりやすい教材や説明方法があれば教えていただけると助かります。

0

そもそも例示されたソースはPythonってクソだわ案件なのでここから教えこもうとするのは勘弁してあげて欲しいと思います.
型アノテーションすらない言語で参照を理解すること自体無謀.

概念ごと理解させてあげるならCで教えるのがいいんでは.
実用上ポインタを使わざるを得ませんからね.
いうて配列が絶対に値型にならないのは変わらないんですが…

元の配列に影響が出る云々に関しては今日日引数を直接操作するようなコードを書くなということで1つ.

0Like

皆さんはどのようにして参照の概念を理解しましたか?

質問者さんはどうなんでしょうか?
私自身はもう覚えていませんし、あまり一つ一つに拘らない方が良いかと思います。

良い教材や解説方法を探しています。

何事も経験だと思います。トライ&エラーの精神で、サンプルコードをいじくり回して動きを理解していくしかないです。
”魔法の教科書”のようなものを探し求めるのは時間の無駄かもしれません。

複雑なデータ構造になると、変数がどこを参照しているのか追えなくなるようです。

経験者でも普通にあると思います。デバッグして確認すれば良い話です。
むしろ分かりにくいデータ構造はバグの原因になるのではないでしょうか。

初心者の方の分からないことについて考えすぎると「プログラミング」そのものを嫌いになってしまわないか心配です。勉強を進めていく内に自然に分かるようになることもあるので楽しくプログラミング覚えていただければ良いなと思います。

1Like

自分はC言語の後にPythonを使い始めたのでポインタの値を渡してるという説明でなんとなく理解できました。
でもPythonしかやってない人にポインタやれと言われても余計に混乱しますよねえ…
最後の動画と同じような感じですがid()で実際のアドレスを見てもらいながら実行すると理解しやすい…かも…?

2Like

参照というよりポインタの部分も含まれていてゴチャゴチャしてますね

y または最終的にはzですが に値を入れている部分はJSONのデータを渡して
ここから必要な部分を取り出す課題を与えればすぐにわかるでしょう
データ取り出し、ポインタの概念に近い部分です

参照はpythonが特殊すぎて他で役に立たないというか混乱まであるので
他の素直な言語でまず学ばれた方が良いです

高級言語なので参照の学習には不向きです
ExcelVBAで参照を学習する人はいないかと思います

1Like

複雑なデータ構造になると、変数がどこを参照しているのか追えなくなるようです。

なんでもかんでも適当に変数定義してるのが原因かと見ます。

私自身Pythonは近年始めたばかりなので、説明材料に成るかは解りませんが、classの基本的な書き方を覚えて、クラスに変数定義させて変数を整理させるクセをつけさたほうが良いかもしれません。

たとえばx = ["a", [1, 2, 3], "b"]のようなデータとしては必要、しかしlistに規則性のないものを詰め込ませず、クラスを作って次のように変数に名前を付けさせるような感覚で整理させます。

class x:
  foo: str = "a"
  bar: list = [1, 2, 3]
  baz: str = "b"

print(x.foo) # a
print(x.bar) # [1, 2, 3]
print(x.baz) # b
x.baz = "c"
print(x.baz) # c

Pythonは不思議なことにstaticへ変えなくてもクラス内の変数を直接扱えます。
データ構造がかなり複雑化するケースは私個人こんな感じでクラスを使ったりします。

無論こんなことしなくてもnamedtuple@dataclass使うのもアリかと思います。

「初心者が分かりやすい」というより、その当人が理解出来る、どこまで変数を整理させられるかどうかなので、相手の苦にならず時間をかけてならしていけばと思います。

1Like

自分がどのように理解したかと言われると、だいぶ昔のことだし、たぶんS式の理解が基本になっているという、一般的でないルートで理解したと思います。

pythonに限らず、変数と値の関係については、以下のようなことが理解できれば、わかるようになると思っています。
・変数を入れ物ととらえるのではなく、値(オブジェクト)を指していると理解する。
・配列などのコンテナオブジェクトの値も入れ物ではなく、他のオブジェクト(値)を指している。
・オブジェクトにはミュータブルなものとイミュータブルなものがある。

以下のポストはDeep CopyとShallow Copyの違いを説明するために書いたものですが、変数と値の関係についての理解の助けにはなると思います。
https://qiita.com/ypsilon-takai/items/97ffcd92d9c60b62b3c6

1Like

Pythonにはidというインスタンスアドレスを返す関数があります
参照とはすなはちアドレスを持つことなので、サンプルコードにId関数を挟みながら動作を追えば理解の足掛かりになるでしょう
またPythonのオブジェクトには参照が共有されるものとされないものがあります
プリミティブな値に関しては後者に当たります
「数値と文字列の参照はどんな場面でも共有されない」と捉えておくと(厳密には完全な同一値を有する場合のみ共有しますが)分かりやすいでしょう
つまりはidで取得されるアドレスの値が同一か否かで参照の共有非共有は把握できます
Pythonの場合は使用する構文によっても参照か複製かが変わりうるので、まずアドレスというものの実体を掴むために、idで確認する癖をつけてもらうのが先決でしょう

1Like
  • (a) 参照を理解する
  • (b) 変数が、複雑なデータ構造のどこを指しているのか追える

……の2つは、別の問題だと思う。

(a)が出来なければ(b)が出来ないのは当然だけど、
(a)が出来ていても慣れの問題で(b)が出来ないことはありうる。
数独のルールを理解することと、数独の問題をスラスラ解けるようになるのは別の話、みたいな。

まず生徒さんが躓いている場所は、本当はどちらなのか確認してみては?
(a)で躓いてるなら単純な例で説明すべきだし、(b)で躓いてるなら簡単なパターンから順に多くの問題を解いて慣れてもらうべき。

0Like

Pythonの値はすべてオブジェクトで、変数やオブジェクト内要素はオブジェクトIDを保持して値(オブジェクト)を参照する、と理解したので苦戦はしなかったです。
数値もオブジェクト生成するのは非効率だなとは思いましたけど。

y = 1 は、まず 1 という int型オブジェクトを生成し、そのオブジェクトの識別値である「オブジェクトID」を変数yに代入して、変数からオブジェクトを参照します。
リストや辞書の要素も変数同様にオブジェクトIDを保持します。
変数に代入しているのか(影響なし)、変数から参照したオブジェクトの中に代入しているのか(影響あり)の違いを把握する必要があります。
図にしてみると理解が早まるかと思います。

image.png

数値もオブジェクトで属性やメソッドを持っている
>>> dir(1)  # 属性やメソッド
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
>>> 3 .__class__  # 小数点に解釈されないようにドットの前に空白
<class'int'>
>>> 3 .real  # 実数部の値
3
>>> 3 .imag  # 虚数部の値
0
>>> 3 .bit_length()
2
整数値1のオブジェクトIDはどれも同じ
>>> x = ["a", [1, 2, 3], "b"]
>>> y = x[1]
>>> z = y
>>> y = 1
>>> vars()  # 変数辞書の内容確認、globals()やlocals()でも確認できる
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class'_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
 'x': ['a', [1, 2, 3], 'b'],
 'y': 1,
 'z': [1, 2, 3]}
>>> id(1)
139900680454384
>>> id(y)
139900680454384
>>> id(z[0])
139900680454384
>>> id(x[1][0])
139900680454384
2Like

タグに「参照渡し」を指定されていますが、Pythonは参照渡しできない言語なのでご注意くださいませ。

  • 値渡し: 変数が保持している値(直値やポインタや参照値)を渡す、ポインタや参照値の場合は値(オブジェクト)を共有する
  • 参照渡し: 変数への参照を渡す、変数を共有する(別名変数、エイリアス変数)
  • 参照渡しできる言語: C++, C#, VB.NET, PHP, etc.
  • 参照渡しできない言語: C, Java, JavaScript, Python, Ruby, etc.

PythonプログラミングFAQ: 出力引数のある関数 (参照渡し) はどのように書きますか?

Python では引数は代入によって渡されます。代入はオブジェクトへの参照を作るだけなので、呼び出し元と呼び出し先にある引数名の間にエイリアスはありませんし、参照渡しそれ自体はありません。

変数には値型変数と参照型変数があり、それぞれ値渡しと参照渡しができます。

  1. 値型変数の値渡し
  2. 値型変数の参照渡し
  3. 参照型変数の値渡し
  4. 参照型変数の参照渡し

Pythonは 3 のみ、
CやJavaは 1 と 3 のみ、
C++, C#, VB.NET, PHP は全部できます。

1Like

int, float, str, tupleはイミュータブルで、それ以外のオブジェクトは基本ミュータブルなのでオブジェクトが所持するデータを変更すると元の変数に影響が出る、と教えればよいのではないでしょうか。
「オブジェクトが所持するデータを変更する」に該当するのは以下です。

  • foo.bar = 1のように、明示的にデータを変更する
  • foo.set_bar(2)のようにデータを変更する関数を実行する
  • foo[3] = 4のようにListやDictの所持する値を明示的に変更する
0Like

foo[3] = 4 は内部的に foo.__setitem__(3, 4) のメソッド呼び出しが行われていることを知るのも理解の助けになるかもしれませんね。

1Like

参照の概念は段階的な理解でしたかね。
C言語から学んで、オセロをCUIで作っていましたが、普通の変数はコピーとして関数に渡しますけども、配列ではこれができませんでした。
そのころポインタと言うやつも似たような概念らしいという話もありましたが、必要に駆られなかったのと良い先生(教材)が私の心の中にも外的にも存在しなかったし興味もありませんでした。
ポインタの基本的な概念を理解出来たのはBrainfuckという必要最低限の命令でチューリング完全という言語のおかげでした。

これらのおかげで参照という概念は比較的簡単に理解できたような気もしますが、参照型と参照渡しについてはJavaの特性をなにかの記事で読んだ際に初めて理解しました。
ただ、これらは必要に駆られて理解すればなんの問題もない気がします。

質問の内容ですとPythonとそのコードを例にあげていますが、Pythonの参照というか内部処理を初心者に教えるのはあまりにも鬼畜の所業だと思います。極端な言葉遣いなら申し訳ありませんが。

単に数値ですらヒープに確保しリテラルはその参照を返すという言語仕様はかなり独特かと思いますし、それをPythonで教えたくなる動機が一切理解出来ません。

Pythonで説明出来ることもあるとは思いますが、内部処理を教えるならステップバイステップで教えなければならず、当初教えたかったことよりハードルが高くなるか、もしくは重要な基礎的な部分は省かなければならない事態になるのが容易に想像できます。

Pythonにだって向いてることと向いてないことがあるわけですね。

で、投稿者はおそらく参照型の話と参照渡し(ポインタへの代入)の区別がついてないと思います。
私の感覚の話ですが、参照と言うと文脈的には参照渡し(ポインタへの代入)のことを話しているかのように振舞って、実際には参照型の話をしていることが多いです。
当然初心者はこれに混乱しますし、調べても理解不能に陥る気がします。

初心者がプログラミングのことが分からなく理由としてはこの用語の混乱が説明者自体に起きているケースがほとんどに思えます。
オブジェクト指向が初心者に難しいと言われるのも、用語の混乱や解説の混乱を整理すれば簡単かと思うのですよね。

1Like

Your answer might help someone💌