※当サイトの記事には、広告・プロモーションが含まれます。

Pythonのクラスが何かとややこしい

gigazine.net

このコストの多くはRobloxの管理外で発生しているものです。売上の平均23%がApp Storeなどの配信プラットフォームの手数料として支払われています。基本的に配信プラットフォームは売上の30%を手数料として徴収しているにもかかわらず、平均が23%となっている理由はRobloxのPC版はサードパーティーの配信プラットフォームを介しておらずクレジットカードの手数料以外にコストが発生しないためです。さらに、売上の26%はRobloxのユーザー生成コンテンツの開発者に報酬として支払われています。

月間アクティブユーザー数が3.8億人超なのに赤字のゲーム「Roblox」は一体どんな問題を抱えているのか? - GIGAZINE

Appleが売上の30%も徴収してるのに驚きなんですが、

diamond.jp

 スマホのシェアが高いことからアップルが標的にされることも多いが、アップルの手数料が特別高いわけではないようだ。スマホ市場の占有率は、アップルとグーグルで9割超と超寡占状態ではあるものの、業界のスタンダードとして手数料は30%前後、場合によっては50%取られることもあるといっていい状況であることが確認できる。

「アップル税30%」に騒ぐ人が知らない世界の常識…アプリストア強制開放で違法コピー地獄の懸念 | DOL特別レポート | ダイヤモンド・オンライン

⇧ 業界のスタンダードの手数料が、ぼったくり過ぎな気がするんだけど...

開発を頑張っても、これでは報われませんな...

Pythonクラスの基本とか

前提として、Javaに触れてきた人間なので、Javaの「クラス」を基準にPythonの「クラス」の仕組みを考察してる点は、ご容赦ください。

Pythonの公式のドキュメントによると、

3. データモデル

3.1. オブジェクト、値、および型

Python における オブジェクト (object) とは、データを抽象的に表したものです。Python プログラムにおけるデータは全て、オブジェクトまたはオブジェクト間の関係として表されます。(ある意味では、プログラムコードもまたオブジェクトとして表されます。これはフォン・ノイマン: Von Neumann の "プログラム記憶方式コンピュータ: stored program computer" のモデルに適合します。)

https://docs.python.org/ja/3/reference/datamodel.html

⇧ 一応、Pythonは「オブジェクト指向言語OOP:Object Oriented Programming)」という認識で良いってことなんですかね?

で、Pythonにおける「クラス」はというと、

3.2.8.8. クラス

Classes are callable. These objects normally act as factories for new instances of themselves, but variations are possible for class types that override __new__(). The arguments of the call are passed to __new__() and, in the typical case, to __init__() to initialize the new instance.

https://docs.python.org/ja/3/reference/datamodel.html

⇧ とあり、早速、

  • __new()__
  • __init()__

の違いが分からんのだが...

Pythonの公式のドキュメントを読み進めると、

■__new()__

3.3.1. 基本的なカスタマイズ

object.__new__(cls[...])

クラス cls の新しいインスタンスを作るために呼び出されます。 __new__() は静的メソッドで (このメソッドは特別扱いされているので、明示的に静的メソッドと宣言する必要はありません)、インスタンスを生成するよう要求されているクラスを第一引数にとります。残りの引数はオブジェクトのコンストラクタの式 (クラスの呼び出し文) に渡されます。 __new__() の戻り値は新しいオブジェクトのインスタンス (通常は cls のインスタンス) でなければなりません。

https://docs.python.org/ja/3/reference/datamodel.html

■__init()__

object.__init__(self[...])

インスタンスが (__new__() によって) 生成された後、それが呼び出し元に返される前に呼び出されます。引数はクラスのコンストラクタ式に渡したものです。基底クラスとその派生クラスがともに __init__() メソッドを持つ場合、派生クラスの __init__() メソッドは基底クラスの __init__() メソッドを明示的に呼び出して、インスタンスの基底クラス部分が適切に初期化されること保証しなければなりません。例えば、 super().__init__([args...]) 。

https://docs.python.org/ja/3/reference/datamodel.html

⇧ とあり、

  • __new()__
    Javaで言うところのconstructor
  • __init()__
    Javaで言うところのinstance initializer

の役割を担っている模様。

と思ったのだけど、

fgshun.hatenablog.com

babaye.hatenablog.com

⇧ 上記サイト様によりますと、「__new()__」については、第一引数に「クラス」を指定してることから、インスタンス変数の初期化を想定していないっぽい。

ちなみに、Javaのstatic initializerに該当するようなものについては、

stackoverflow.com

Pythonには用意されていないっぽい。

Pythonのクラスの変数がややこしい

Javaの「クラス」に慣れた身からすると、Pythonの「クラス」はややこしいのだけど、Pythonの「クラス」における変数はというと、

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

一般的に、インスタンス変数はそれぞれのインスタンスについて固有のデータのためのもので、クラス変数はそのクラスのすべてのインスタンスによって共有される属性やメソッドのためのものです:

https://docs.python.org/ja/3/tutorial/classes.html

⇧ とあり、大きく分けて、

  • クラス変数(class variable shared by all instances)
    Javaでいうclass variable(static修飾子の付いたメンバ変数)
  • インスタンス変数(instance variable unique to each instance)
    Javaでいうinstance variable(static修飾子の付いていないメンバ変数)

の2つ。

Pythonの「インスタンス変数」は、

  • __init()__
    Javaで言うところのinstance initializer

で定義する感じになるっぽいので、このあたりが、Javaに慣れた身からすると違和感を感じてしまう。

Pythonの「インスタンス変数」と「クラス変数」はややこしく、

qiita.com

qiita.com

⇧ 被害者が続出している模様。

で、更にややこしいことに、Python 3.7で「dataclasses」というものが追加されており、この「dataclasses」を利用すると、「インスタンス変数」の定義が変わってきますと。

そして、「インスタンス変数」と「クラス変数」を区別するために、

docs.python.org

クラス変数

One of the few places where @dataclass actually inspects the type of a field is to determine if a field is a class variable as defined in PEP 526. It does this by checking if the type of the field is typing.ClassVar. If a field is a ClassVar, it is excluded from consideration as a field and is ignored by the dataclass mechanisms. Such ClassVar pseudo-fields are not returned by the module-level fields() function.

https://docs.python.org/ja/3/library/dataclasses.html

⇧ とあるように、「クラス変数」に対して「typing.ClassVar」による型を指定してあげる必要があると。

qiita.com

⇧ 上記サイト様が参考になるかと。

ちなみに、「Pydantic」という外部ライブラリを使ってデータモデルのクラスを実装することが多いらしいのですが、

stackoverflow.com

⇧ 上記サイト様によりますと、「クラス定数」としたい場合は、Python標準で用意されている「dataclasses」によるデータモデルのクラスの実装の時と同じく「typing.ClassVar」による型を指定してあげれば良いらしい。

Pythonのクラスにメソッドが4種類あるっぽいが...

Pythonで「関数」と「メソッド」の違いがいまいちよく分からんのだけど、

docs.python.org

Pythonの公式のドキュメントによると、

(メソッド) クラス本体の中で定義された関数。そのクラスのインスタンスの属性として呼び出された場合、メソッドはインスタンスオブジェクトを第一 引数 として受け取ります (この第一引数は通常 self と呼ばれます)。 関数 と ネストされたスコープ も参照してください。

とあるので、

  • メソッド
    クラス内で定義された関数
  • 関数
    クラス内以外で定義された関数

ということで、「クラス」の話であれば「メソッド」と呼称する感じで良い模様。

Javaに触れてきた人間なので、Pythonの「クラス」の仕組みがいまいちよく分からんのだけど、Pythonの「クラス」で「メソッド」が4種類あるようで、

  1. instance method
    アノテーションなし
  2. class method
    アノテーション@classmethodを付与
  3. static method
    アノテーション@staticmethodを付与
  4. abstract method
    アノテーション@abstractmethodを付与

という感じになるらしい。

そもそもとして、

www.yasuhisay.info

このようにPythonではアンダースコア一つで(紳士的に)「このメソッドやアトリビュートには触らないでね」ということを伝え、アンダースコア二つで(擬似的に)アトリビュートやメソッドに触れないようになっています。Javaが物理的に*2触らせないことを考えると、Pythonはアクセス制御に関しては限定的な機能しかもたらさないようです。

Pythonのアクセス制限と抽象クラス&インターフェイスについてのまとめ - yasuhisa's blog

⇧ 上記サイト様によりますと、Javaの「アクセス修飾子」のようなものが、Pythonには存在しないらしく、Pythonの「クラス」自体に対しては、Javaにおける「アクセス修飾子」のようなアクセス制御はできないらしい。

ただ、

qiita.com

  • 先日、ソースコードレビューを受けた際に「pythonにはアクセス修飾子の概念はないよ」と言われました。

PythonやJavaScriptにアクセス修飾子がない理由を考えてみた #Python3 - Qiita

note.com

⇧ 例の如く、Pythonの情報が錯綜している...

なかなかに辛い...

ちなみに「abstarct method」については、

docs.python.org

⇧ 「抽象基底クラス(ABC)」を継承させる必要があるらしい。

Pythonのクラスのメソッドが4種類あるっぽいが、使い分けはどうするか

で、気になるのが、「メソッド」の使い分けですかね。

ネットの情報を漁った感じでは、

medium.com

⇧ 上記サイト様のように、3種類で考えるみたいですね。

で、「instance method」については、「クラス」をインスタンス化して利用される想定というのは分かりますと。

「class method」が今いち、どういう用途を想定しているものなのか分からない...

pythonjp.ikitai.net

  1. 引数の違い: クラスメソッドはclsという引数を持ち、クラス自体を指すことが一般的です。一方、静的メソッドはクラスやインスタンスを指す引数を持ちません。

  2. 状態へのアクセス: クラスメソッドはクラス変数などのクラスの状態にアクセスできますが、静的メソッドはアクセスできません。

  3. 呼び出し方: クラスメソッドはクラスを通じて呼び出されることが一般的ですが、静的メソッドはクラスまたはインスタンスのどちらからでも呼び出すことができます。

Pythonのclassmethod()とstaticmethod(): クラスメソッドと静的メソッドの探究 - Python転職初心者向けエンジニアリングブログ

⇧ 上記サイト様によりますと、「static method」は「クラス変数」を利用できないと。

Javaに慣れてる人間からすると、「static method」で「クラス変数(static修飾子の付いたメンバ変数)」を利用できるから混乱するんよね...

Pythonの「クラス」はハマりどころ満載と言った感じですかね...

毎度モヤモヤ感が半端ない…

今回はこのへんで。