前の関連記事:linuxBean14.04(169)Pythonでデザインパターン:Strategy
Pythonの「属性」と「プロパティ」
HTMLの要素では「属性」、JavaScriptでは「プロパティ」、というものがPythonでは「プロパティ」(property)と「属性」(attribute)があって、「プロパティ」は「属性」の一種らしいです(What's the difference between a Python "property" and "attribute"? - Stack Overflow)。
(2017.8.26追記。Python Cookbookの8.6. Creating Managed Attributesにはプロパティは遅いので、必然性があるとき、、、値を実行時に生成する必要があるとき、のみの使用に限定するように書いてあります。)
2. 組み込み関数 — Python 3.5.3 ドキュメント
組み込み関数にpropertyクラスがありました。
ということでPythonについてはjavaScriptの「プロパティ」に相当するものは「属性」と呼ぶことにします。
(2017.8.26追記。LibreOfficeではインターフェイスがもつ値がアトリビュート、サービス(old-serviceのみ)がもつ値がプロパティとなっています。アトリビュート(つまりインターフェイスがもつ値)は属性名でのアクセスができるのに対して、プロパティ(つまりサービスがもつ値)はgetPropertyValue()/setPropertyValue()といったメソッドを使ってアクセスします(Objects, Interfaces, and Services - Apache OpenOffice Wiki)。だけどAPIリファレンスをみるとプロパティもAttributeと書いてあるます。)
属性に代入した関数
linuxBean14.04(169)Pythonでデザインパターン:StrategyのModelDuckのインスタンス化部分だけ抽出します。
In [1]:
def flyNoWay():
print("I can't fly")
def flyRocketPowered():
print("I'm flying with a rocket")
class Duck:
def swim(self):
print("All ducks float, even decoys!")
class ModelDuck(Duck):
def __init__(self):
self.performFly = flyNoWay
ModelDuckクラスをインスタンス化してpeformFly属性に代入した関数を実行してみます。
In [2]:
model = ModelDuck()
model.performFly()
In [3]:
model.performFly
Out[3]:
これはただの関数です。
modelインスタンスのpeformFly属性の関数を入れ替えます。
In [4]:
model.performFly = flyRocketPowered
model.performFly()
ここまでは前回までにやったことです。
このpeformFly()は属性に代入した関数を実行しているだけなので、ModelDuckクラスのメソッドが持つはずの引数selfは持っていません。
このpeformFly()は属性に代入した関数を実行しているだけなので、ModelDuckクラスのメソッドが持つはずの引数selfは持っていません。
属性に代入した関数にメソッドと同様にselfを渡すにはtypes.MethodType()を使う
In [5]:
def flyNoWayWithName(self):
print(self.name + " can't fly")
class ModelDuck2(Duck):
def __init__(self,name):
self.performFly = flyNoWayWithName
self.name = name
属性に代入する関数に、メソッドと同様にselfを引数にして代入しました。
In [6]:
carue = ModelDuck2("Carue")
carue.performFly()
これはpeformFly()の引数が与えられていないと言われて実行できません。
属性に代入した関数にはメソッドと違ってselfが暗黙的には渡されません。
属性に代入した関数にはメソッドと違ってselfが暗黙的には渡されません。
In [7]:
carue.performFly
Out[7]:
これは標準の関数です。
In [8]:
import types
class ModelDuck2(Duck):
def __init__(self,name):
self.performFly = types.MethodType(flyNoWayWithName,self)
self.name = name
そこでtypes.MethodType()の登場です。
performFly属性に代入するときにtypes.MethodType()を使って関数flyNoWayWithNameの引数にselfを与えます。
performFly属性に代入するときにtypes.MethodType()を使って関数flyNoWayWithNameの引数にselfを与えます。
In [9]:
carue = ModelDuck2("Carue")
carue.performFly()
これはうまくいきました。
ちゃんとselfがflyNoWayWithNameの引数に渡されています。
ちゃんとselfがflyNoWayWithNameの引数に渡されています。
In [10]:
def flyRocketPoweredWithName(self):
print(self.name + " is flying with a rocket")
最初の例と同様にpeformFly属性の関数をこのflyRocketPoweredWithNameに入れ替えることにします。
flyRocketPoweredWithNameもselfを引数に持っています。
flyRocketPoweredWithNameもselfを引数に持っています。
In [11]:
carue.performFly = types.MethodType(flyRocketPoweredWithName,carue)
carue.performFly()
これもtypes.MethodType()を使うとうまくいきましたが、第2引数に与えるのはselfではなく、インスタンスそのものをいれます。
selfに入れてもスコープにないものなのでエラーがでます。
引数に渡したのはインスタンスなので、performFly属性が変更になるのはこのインスタンスに対してだけです。
これも属性に普通に関数を代入した時と同じです。
selfに入れてもスコープにないものなのでエラーがでます。
引数に渡したのはインスタンスなので、performFly属性が変更になるのはこのインスタンスに対してだけです。
これも属性に普通に関数を代入した時と同じです。
In [12]:
carue2 = ModelDuck2("Carue2")
carue2.performFly()
確かに他のインスタンスのperformFly属性は変更されていません。
クラスに対してメソッドを追加する
他のインスタンスの属性にも追加したいときはクラスの属性に対して関数を代入します。
クラスの属性に関数を代入するときはselfが暗黙的に引数に与えられるのでtypes.MethodType()を使う必要がありません。
クラスの属性に関数を代入するときはselfが暗黙的に引数に与えられるのでtypes.MethodType()を使う必要がありません。
In [13]:
ModelDuck2.performFly2 = flyRocketPoweredWithName
In [14]:
carue2.performFly2()
ところがこのように新しい関数をクラスに追加できるのに、すでに存在する属性の入れ替えはうまくいきません。
In [15]:
ModelDuck2.performFly = flyRocketPoweredWithName
carue2.performFly()
In [16]:
carue3 = ModelDuck2("Carue3")
carue3.performFly()
よく考えたらこれができるのなら、クラスの継承しなくてもよくなってしまいますね。
標準の関数、メソッド(method)、静的メソッド(staticmethod)、クラスメソッド(classmethod)
Pythonでの関数は使われ方によって、標準の関数(regular function)、メソッド(method)、静的メソッド(staticmethod)、クラスメソッド(classmethod)に分類されることがわかりました。
標準の関数(regular function)とはオブジェクトと紐付けられていない単独の関数です。
関数がオブジェクトと紐付けられて、属性に入れられると、それはメソッド(method)か静的メソッド(staticmethod)かクラスメソッド(classmethod)になります。
で、これらの違いが何なのか調べてみると、その呼び出され方が違うことがわかりました。
属性はデスクリプタプロトコルで呼び出され方が定義されています。
あるオブジェクトが __get__() と __set__() の両方を定義していたら、それはデータデスクリプタとみなされます。 __get__() だけを定義しているデスクリプタは、非データデスクリプタと呼ばれます
デスクリプタ HowTo ガイド — Python 3.6.1 ドキュメント
要は、呼び出しと設定の両方をしているときはデータデスクリプタ、呼び出しだけで設定をしていないときは、非データデスクリプタということです。
データデスクリプタ
インスタンスの辞書
非データデスクリプタ
インスタンスの辞書
非データデスクリプタ
属性の呼び出しはこの順に優先されるので、 get()とset() の両方を定義している属性(データデスクリプタ)のあとにget()だけを定義している属性(非データデスクリプタ)を代入しても、あとから代入した属性は無視されます(python data and non-data descriptors - Stack Overflow)。
上記のIn[15]でperformFly属性への代入が無視されたのは、データデスクリプタに非データデスクリプタを代入しようとしたから、と納得しましたが確証はありません。
In [17]:
carue2.performFly
Out[17]:
In [18]:
ModelDuck2.performFly ="flyRocketPoweredWithName"
carue2.performFly
Out[18]:
文字列も代入できませんね。
boundしているから?
boundしているから?
staticmethod()、@staticmethodを使う
まずstaticmethod()を使ってみます。
staticmethod()は第一引数にインスタンスを持たない関数を引数にします。
staticmethod()は第一引数にインスタンスを持たない関数を引数にします。
In [19]:
class ModelDuck3(Duck):
def __init__(self,name):
self.name = name
performFly = staticmethod(flyNoWay)
In [20]:
carue3 = ModelDuck3("Carue3")
carue3.performFly()
In [21]:
carue3.performFly
Out[21]:
でも、別にこれはstaticmethod()を使わなくても同じ結果です。
In [22]:
class ModelDuck4(Duck):
def __init__(self,name):
self.name = name
performFly = flyNoWay
In [23]:
ModelDuck4.performFly()
いまのところ私が思いつくstaticmethod()使う目的は、標準の関数の記載をやめて、Javaのようにすべてをクラスの中に書く、ということぐらいしかありません。
In [24]:
class Fly:
@staticmethod
def flyNoWay():
print("I can't fly")
@staticmethod
def flyRocketPowered():
print("I'm flying with a rocket")
class Duck5:
def swim(self):
print("All ducks float, even decoys!")
class ModelDuck5(Duck):
def __init__(self):
self.performFly = Fly.flyNoWay
In [25]:
model5 = ModelDuck5()
model5.performFly()
FlyクラスのflyNoWay()メソッドはself(=インスタンス)を引数に必要としないので、インスタンス化しなくてもFlyクラスの属性として呼び出しています。
@staticmethodは書かなくても動くので、これも単にJavaのアノテーションのイミテーションなのかと思ってしまいます。
あとで気が付きましたが、PyDevでは@staticmethodを使わないとselfがないと言われます。
アノテーションは文法チェックに有効ですね。
あとで気が付きましたが、PyDevでは@staticmethodを使わないとselfがないと言われます。
アノテーションは文法チェックに有効ですね。
classmethod()、@classmethodを使う
classmethod()はインスタンスではなくクラスを引数とする関数を引数にする関数です。
Python's Class Development Toolkit - YouTube(これのスライドPython's Class Development Toolkit by Raymond Hettinger // Speaker Deck)では、classmethod()をalternative constructorとしての使用例が挙げられています。
In [26]:
import types
def flyNoWayWithName(self):
print(self.name + " can't fly")
def flyRocketPoweredWithName(self):
print(self.name + " is flying with a rocket")
class Duck:
def swim(self):
print("All ducks float, even decoys!")
class ModelDuck6(Duck):
def __init__(self,name):
self.performFly = types.MethodType(flyNoWayWithName,self)
self.name = name
@classmethod
def altInit(cls,name): # alternative constructor
name = "Duck " + name
return cls(name)
In [27]:
carue6 = ModelDuck6.altInit("Carue6")
carue6.performFly()
classmethod()は使わないと第一引数にクラスが与えられないのでエラーになります。
クラスの多重継承を使ってtypes.MethodType()を使わない方法
In [28]:
class Fly2:
def flyNoWayWithName(self):
print(self.name + " can't fly")
def flyRocketPoweredWithName(self):
print(self.name + " is flying with a rocket")
class Duck:
def swim(self):
print("All ducks float, even decoys!")
class ModelDuck7(Duck,Fly2):
def __init__(self,name):
self.performFly = self.flyNoWayWithName
self.name = name
In [29]:
carue7 = ModelDuck7("Carue7")
carue7.performFly()
標準の関数をクラスにいれてそれを継承すれば、うまくいきました。
8.9. types — 動的な型生成と組み込み型に対する名前 — Python 3.6.1 ドキュメントにはtypes.MethodType()の使い方例が解説されていないことを考えると、Javaのインターフェイスを置換する正攻法はクラスに置換してそのクラスを継承する方法だと思いました。
参考にしたサイト
What's the difference between a Python "property" and "attribute"? - Stack Overflow
Pythonではproperty(プロパティ)はattribute(属性)の一種のようです。
2. 組み込み関数 — Python 3.5.3 ドキュメント
property()、staticmethod()、classmethod()について学習しました。
デスクリプタ HowTo ガイド — Python 3.6.1 ドキュメント
属性が作動する仕組み。
python data and non-data descriptors - Stack Overflow
データデスクリプタと非データデスクリプタでは優先順位が異なります。
Decorators and Functional Python
デコレーターの使い方例がたくさんあります。クラスのデコレーターの例もあります。
(1) Python's Class Development Toolkit - YouTube
classmethod()とかproperty()使い方の実践例が解説されています。
Python's Class Development Toolkit by Raymond Hettinger // Speaker Deck
上のビデオのスライド。
8.9. types — 動的な型生成と組み込み型に対する名前 — Python 3.6.1 ドキュメント
Pythonのドキュメントを読んでもtypes.MethodType()の使い方はわかりませんでした。
0 件のコメント:
コメントを投稿