属性をいつプライベートにするか、プロパティを使用するかどうかはわかりません。
私は最近、セッターとゲッターがPythonicではなく、プロパティデコレーターを使用する必要があることを読みました。大丈夫です。
しかし、属性がある場合は、クラスの外部から設定するのではなく、読み取ることができます(読み取り専用属性)。この属性はプライベートである必要があり、プライベートとはアンダースコアを意味します。たとえば、self._x
?はいの場合、ゲッターを使用せずにどのように読むことができますか?私が今知っている唯一の方法は書くことです
@property
def x(self):
return self._x
そうすれば、obj.x
で属性を読み取ることができますが、obj.x = 1
に設定できないので問題ありません。
しかし、設定してはならないオブジェクトの設定を本当に気にする必要がありますか?たぶんそのままにしておくべきでしょう。ただし、ユーザーにとってobj._x
の読み取りは奇妙なので、アンダースコアは使用できません。したがって、obj.x
を使用する必要がありますが、ユーザーはこの属性を設定してはならないことを知りません。
あなたの意見と実践は何ですか?
一般に、Pythonプログラムは、すべてのユーザーが成人に同意しているため、自分で物事を正しく使用する責任があるという前提で作成する必要があります。ただし、属性を設定可能にするだけの意味がない場合(派生値、静的データソースから読み取った値など)、getter-onlyプロパティが一般的に推奨されるパターンです。
ちょうど私の2セント、 Silas Ray は正しい軌道に乗っていますが、例を追加したいと思いました。 ;-)
Pythonは型安全でない言語であり、したがって、コードのユーザーが合理的な(賢明な)人のようにコードを使用することを常に信頼する必要があります。
PEP 8 :
非パブリックメソッドとインスタンス変数に対してのみ、先頭にアンダースコアを1つ使用します。
クラスに「読み取り専用」プロパティを設定するには、@property
装飾を使用します。新しいスタイルのクラスを使用するには、object
から継承する必要があります。 。
例:
>>> class A(object):
... def __init__(self, a):
... self._a = a
...
... @property
... def a(self):
... return self._a
...
>>> a = A('test')
>>> a.a
'test'
>>> a.a = 'pleh'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
これは、次の仮定を回避する方法です。
すべてのユーザーは大人に同意しているため、自分で物を正しく使用する責任があります。
@property
を使用すると、非常に冗長になります。例:
class AClassWithManyAttributes:
'''refactored to properties'''
def __init__(a, b, c, d, e ...)
self._a = a
self._b = b
self._c = c
self.d = d
self.e = e
@property
def a(self):
return self._a
@property
def b(self):
return self._b
@property
def c(self):
return self._c
# you get this ... it's long
を使用して
アンダースコアなし:パブリック変数です。
アンダースコア1つ:保護された変数です。
2つのアンダースコア:プライベート変数です。
最後のものを除いて、それは慣習です。本当に一生懸命やるなら、二重アンダースコアで変数にアクセスできます。
見よ! read_only_properties
デコレーターが助けて!
@read_only_properties('readonly', 'forbidden')
class MyClass(object):
def __init__(self, a, b, c):
self.readonly = a
self.forbidden = b
self.ok = c
m = MyClass(1, 2, 3)
m.ok = 4
# we can re-assign a value to m.ok
# read only access to m.readonly is OK
print(m.ok, m.readonly)
print("This worked...")
# this will explode, and raise AttributeError
m.forbidden = 4
あなたが尋ねる:
read_only_properties
はどこから来たのですか?
read_only_properties のソースは次のとおりです。
def read_only_properties(*attrs):
def class_rebuilder(cls):
"The class decorator"
class NewClass(cls):
"This is the overwritten class"
def __setattr__(self, name, value):
if name not in attrs:
pass
Elif name not in self.__dict__:
pass
else:
raise AttributeError("Can't modify {}".format(name))
super().__setattr__(name, value)
return NewClass
return class_rebuilder
この回答がそれほど注目されるとは思っていませんでした。驚くべきことに。これにより、使用できるパッケージを作成するようになりました。
$ pip install read-only-properties
pythonシェルで:
In [1]: from rop import read_only_properties
In [2]: @read_only_properties('a')
...: class Foo:
...: def __init__(self, a, b):
...: self.a = a
...: self.b = b
...:
In [3]: f=Foo('explodes', 'ok-to-overwrite')
In [4]: f.b = 5
In [5]: f.a = 'boom'
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-5-a5226072b3b4> in <module>()
----> 1 f.a = 'boom'
/home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value)
116 pass
117 else:
--> 118 raise AttributeError("Can't touch {}".format(name))
119
120 super().__setattr__(name, value)
AttributeError: Can't touch a
これは、読み取り専用プロパティへのわずかに異なるアプローチです。初期化する必要があるため、書き込み専用プロパティと呼ばれる可能性があります。オブジェクトのディクショナリに直接アクセスすることでプロパティを変更できることを心配している私たちの妄想のために、「極端な」名前のマングリングを導入しました。
from uuid import uuid4
class Read_Only_Property:
def __init__(self, name):
self.name = name
self.dict_name = uuid4().hex
self.initialized = False
def __get__(self, instance, cls):
if instance is None:
return self
else:
return instance.__dict__[self.dict_name]
def __set__(self, instance, value):
if self.initialized:
raise AttributeError("Attempt to modify read-only property '%s'." % self.name)
instance.__dict__[self.dict_name] = value
self.initialized = True
class Point:
x = Read_Only_Property('x')
y = Read_Only_Property('y')
def __init__(self, x, y):
self.x = x
self.y = y
if __== '__main__':
try:
p = Point(2, 3)
print(p.x, p.y)
p.x = 9
except Exception as e:
print(e)
Oz123のクラスデコレータが好きですが、明示的なクラスラッパーと、クロージャ内のクラスを返すクラスFactoryメソッドで__new__を使用する次のこともできます。
class B(object):
def __new__(cls, val):
return cls.factory(val)
@classmethod
def factory(cls, val):
private = {'var': 'test'}
class InnerB(object):
def __init__(self):
self.variable = val
pass
@property
def var(self):
return private['var']
return InnerB()
インスタンスメソッドも(クラスの)属性であり、本当におたくになりたい場合は、クラスまたはインスタンスレベルで設定できることに注意してください。または、クラス変数(クラスの属性でもあります)を設定することもできます。この場合、便利な読み取り専用プロパティはすぐに使用できません。私が言おうとしているのは、「読み取り専用属性」の問題は実際には、通常認識されているよりも一般的だということです。幸いなことに、これらの他のケースで私たちを盲目にするほど強力な従来の仕事上の期待があります(結局のところ、ほとんどすべてがpythonのある種の属性です)。
これらの期待に基づいて、最も一般的で軽量なアプローチは、「パブリック」(先行アンダースコアなし)属性が書き込み可能として明示的に文書化されている場合を除き、読み取り専用であるという規則を採用することだと思います。これは、メソッドにはパッチが適用されず、インスタンスのデフォルトを示すクラス変数は言うまでもないという通常の期待を包含しています。特別な属性について本当に妄想を感じる場合は、最後のリソース指標として読み取り専用記述子を使用してください。
最初の解決策では読み取り専用属性を削除してから__dict__をブロックせずに許可するため、読み取り専用プロパティを作成するための前の2つの答えには不満です。 2番目のソリューションは、テストで回避できます。2つに設定した値に等しい値を見つけ、最終的に変更します。
さて、コードについて。
def final(cls):
clss = cls
@classmethod
def __init_subclass__(cls, **kwargs):
raise TypeError("type '{}' is not an acceptable base type".format(clss.__name__))
cls.__init_subclass__ = __init_subclass__
return cls
def methoddefiner(cls, method_name):
for clss in cls.mro():
try:
getattr(clss, method_name)
return clss
except(AttributeError):
pass
return None
def readonlyattributes(*attrs):
"""Method to create readonly attributes in a class
Use as a decorator for a class. This function takes in unlimited
string arguments for names of readonly attributes and returns a
function to make the readonly attributes readonly.
The original class's __getattribute__, __setattr__, and __delattr__ methods
are redefined so avoid defining those methods in the decorated class
You may create setters and deleters for readonly attributes, however
if they are overwritten by the subclass, they lose access to the readonly
attributes.
Any method which sets or deletes a readonly attribute within
the class loses access if overwritten by the subclass besides the __new__
or __init__ constructors.
This decorator doesn't support subclassing of these classes
"""
def classrebuilder(cls):
def __getattribute__(self, name):
if name == '__dict__':
from types import MappingProxyType
return MappingProxyType(super(cls, self).__getattribute__('__dict__'))
return super(cls, self).__getattribute__(name)
def __setattr__(self, name, value):
if name == '__dict__' or name in attrs:
import inspect
stack = inspect.stack()
try:
the_class = stack[1][0].f_locals['self'].__class__
except(KeyError):
the_class = None
the_method = stack[1][0].f_code.co_name
if the_class != cls:
if methoddefiner(type(self), the_method) != cls:
raise AttributeError("Cannot set readonly attribute '{}'".format(name))
return super(cls, self).__setattr__(name, value)
def __delattr__(self, name):
if name == '__dict__' or name in attrs:
import inspect
stack = inspect.stack()
try:
the_class = stack[1][0].f_locals['self'].__class__
except(KeyError):
the_class = None
the_method = stack[1][0].f_code.co_name
if the_class != cls:
if methoddefiner(type(self), the_method) != cls:
raise AttributeError("Cannot delete readonly attribute '{}'".format(name))
return super(cls, self).__delattr__(name)
clss = cls
cls.__getattribute__ = __getattribute__
cls.__setattr__ = __setattr__
cls.__delattr__ = __delattr__
#This line will be moved when this algorithm will be compatible with inheritance
cls = final(cls)
return cls
return classrebuilder
def setreadonlyattributes(cls, *readonlyattrs):
return readonlyattributes(*readonlyattrs)(cls)
if __== '__main__':
#test readonlyattributes only as an indpendent module
@readonlyattributes('readonlyfield')
class ReadonlyFieldClass(object):
def __init__(self, a, b):
#Prevent initalization of the internal, unmodified PrivateFieldClass
#External PrivateFieldClass can be initalized
self.readonlyfield = a
self.publicfield = b
attr = None
def main():
global attr
pfi = ReadonlyFieldClass('forbidden', 'changable')
###---test publicfield, ensure its mutable---###
try:
#get publicfield
print(pfi.publicfield)
print('__getattribute__ works')
#set publicfield
pfi.publicfield = 'mutable'
print('__setattr__ seems to work')
#get previously set publicfield
print(pfi.publicfield)
print('__setattr__ definitely works')
#delete publicfield
del pfi.publicfield
print('__delattr__ seems to work')
#get publicfield which was supposed to be deleted therefore should raise AttributeError
print(pfi.publlicfield)
#publicfield wasn't deleted, raise RuntimeError
raise RuntimeError('__delattr__ doesn\'t work')
except(AttributeError):
print('__delattr__ works')
try:
###---test readonly, make sure its readonly---###
#get readonlyfield
print(pfi.readonlyfield)
print('__getattribute__ works')
#set readonlyfield, should raise AttributeError
pfi.readonlyfield = 'readonly'
#apparently readonlyfield was set, notify user
raise RuntimeError('__setattr__ doesn\'t work')
except(AttributeError):
print('__setattr__ seems to work')
try:
#ensure readonlyfield wasn't set
print(pfi.readonlyfield)
print('__setattr__ works')
#delete readonlyfield
del pfi.readonlyfield
#readonlyfield was deleted, raise RuntimeError
raise RuntimeError('__delattr__ doesn\'t work')
except(AttributeError):
print('__delattr__ works')
try:
print("Dict testing")
print(pfi.__dict__, type(pfi.__dict__))
attr = pfi.readonlyfield
print(attr)
print("__getattribute__ works")
if pfi.readonlyfield != 'forbidden':
print(pfi.readonlyfield)
raise RuntimeError("__getattr__ doesn't work")
try:
pfi.__dict__ = {}
raise RuntimeError("__setattr__ doesn't work")
except(AttributeError):
print("__setattr__ works")
del pfi.__dict__
raise RuntimeError("__delattr__ doesn't work")
except(AttributeError):
print(pfi.__dict__)
print("__delattr__ works")
print("Basic things work")
main()
書き込み時以外は、読み取り専用属性を作成しても意味がありません ライブラリコード、 アプリ開発などの他の目的のためのコードではなく、プログラムを強化するために使用するコードとして他者に配布されているコード。 __dict__は不変であるため、__ dict__の問題は解決されました。 types.MappingProxyType、したがって、__ dict__を介して属性を変更することはできません。 __dict__の設定または削除もブロックされます。読み取り専用プロパティを変更する唯一の方法は、クラス自体のメソッドを変更することです。
私のソリューションは前の2つよりも優れていると思いますが、改善される可能性があります。これらはこのコードの弱点です。
a)読み取り専用属性を設定または削除するサブクラスのメソッドに追加することはできません。サブクラスで定義されたメソッドは、スーパークラスバージョンのメソッドを呼び出しても、読み取り専用属性へのアクセスが自動的に禁止されます。
b)クラスの読み取り専用メソッドを変更して、読み取り専用の制限を無効にすることができます。
ただし、クラスを編集せずに読み取り専用属性を設定または削除する方法はありません。これは命名規則に依存していません。これはPythonが命名規則とそれほど一貫していないため良いです。これにより、クラス自体を編集せずに隠れた抜け穴で変更できない読み取り専用属性を作成できます。デコレータを引数として呼び出すときに読み取り専用になる属性をリストするだけで、読み取り専用になります。
Pythonの別のクラスの関数内で呼び出し元クラス名を取得する方法? の呼び出し元クラスとメソッドを取得するためのBriceの答えに感謝します。
それが私の回避策です。
@property
def language(self):
return self._language
@language.setter
def language(self, value):
# WORKAROUND to get a "getter-only" behavior
# set the value only if the attribute does not exist
try:
if self.language == value:
pass
print("WARNING: Cannot set attribute \'language\'.")
except AttributeError:
self._language = value