118

属性をプライベートにする必要があるのはいつか、使用する必要があるのか​​どうかはわかりませんproperty

最近読んだところによると、セッターとゲッターはpythonicではありませんが、propertyデコレーターを使用しても問題ありません。

しかし、属性がある場合は、クラスの外部から設定することはできませんが、読み取ることができます(読み取り専用属性)。この属性はプライベートである必要があります。プライベートとは、そのようなアンダースコアを意味しますself._xか?はいの場合、ゲッターを使用せずにそれを読むにはどうすればよいですか?私が今知っている唯一の方法は書くことです

@property
def x(self):
    return self._x

そうすれば属性を読み取ることはできますobj.xが、設定できないobj.x = 1ので問題ありません。

しかし、設定してはいけないオブジェクトの設定を本当に気にする必要がありますか?多分私はそれを残すべきです。しかし、ユーザーにとって読み取りobj._xが奇妙であるため、アンダースコアを使用することはできません。したがって、使用するobj.x必要があります。ユーザーは、この属性を設定してはならないことを知りません。

あなたの意見や慣習は何ですか?

4

10 に答える 10

94

ちょうど私の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
于 2013-04-04T13:30:24.703 に答える
82

一般に、Pythonプログラムは、すべてのユーザーが成人に同意していることを前提として作成する必要があります。したがって、Pythonプログラムは、自分で正しく使用する責任があります。ただし、属性を設定可能にすることが意味をなさないまれな例(派生値、静的データソースから読み取られた値など)では、通常、getter-onlyプロパティが推奨されるパターンです。

于 2013-01-29T23:42:35.503 に答える
67

これは、次のような仮定を回避する方法です。

すべてのユーザーは成人に同意しているため、自分で物事を正しく使用する責任があります。

以下の私の更新を参照してください

の使用@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つのアンダースコア:これはプライベート変数です。

最後のものを除いて、それは慣習です。それでも、本当に一生懸命努力すれば、二重アンダースコアで変数にアクセスできます。

どうしようか?Pythonで読み取り専用プロパティを使用することを諦めますか?

見よ!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
于 2016-09-27T04:31:27.267 に答える
5

これは、読み取り専用プロパティへのわずかに異なるアプローチです。初期化する必要があるため、おそらくライトワンスプロパティと呼ばれる必要がありますね。オブジェクトの辞書に直接アクセスしてプロパティを変更できることを心配している私たちの間のパラノイドのために、私は「極端な」名前マングリングを導入しました。

from uuid import uuid4

class ReadOnlyProperty:
    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 = ReadOnlyProperty('x')
    y = ReadOnlyProperty('y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

if __name__ == '__main__':
    try:
        p = Point(2, 3)
        print(p.x, p.y)
        p.x = 9
    except Exception as e:
        print(e)
于 2018-01-27T15:07:07.160 に答える
3

最初のソリューションでは読み取り専用属性を削除してから設定でき、ブロックされないため、読み取り専用プロパティを作成する前の2つの回答に不満があります__dict__。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 __name__ == '__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つよりも優れていると思いますが、改善される可能性があります。このコードの弱点は次のとおりです。

  1. 読み取り専用属性を設定または削除するサブクラスのメソッドに追加することはできません。サブクラスで定義されたメソッドは、スーパークラスのバージョンのメソッドを呼び出した場合でも、読み取り専用属性へのアクセスが自動的に禁止されます。

  2. クラスの読み取り専用メソッドを変更して、読み取り専用の制限を無効にすることができます。

ただし、クラスを編集せずに読み取り専用属性を設定または削除する方法はありません。これは命名規則に依存していません。Pythonは命名規則とあまり一貫性がないため、これは良いことです。これにより、クラス自体を編集せずに非表示の抜け穴で変更できない読み取り専用属性を作成する方法が提供されます。デコレータを引数として呼び出すときに読み取り専用になる属性をリストするだけで、読み取り専用になります。

呼び出し元のクラスとメソッドを取得したBriceの回答の功績です。

于 2019-05-21T16:49:22.893 に答える
2

それが私の回避策です。

@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
于 2018-05-21T08:24:50.863 に答える
0

インスタンスメソッドも(クラスの)属性であり、本当に悪者になりたい場合は、クラスまたはインスタンスレベルで設定できることに注意してください。または、クラス変数(クラスの属性でもある)を設定すると、便利な読み取り専用プロパティがそのままではうまく機能しません。私が言おうとしているのは、「読み取り専用属性」の問題は、実際には通常認識されているよりも一般的であるということです。幸いなことに、これらの他のケースでは私たちを盲目にするほど強力な従来の期待が働いています(結局のところ、ほとんどすべてがPythonのある種の属性です)。

これらの期待に基づいて、最も一般的で軽量なアプローチは、「パブリック」(先頭の下線なし)属性は、書き込み可能として明示的に文書化されている場合を除き、読み取り専用であるという規則を採用することだと思います。これは、メソッドにパッチが適用されないという通常の期待を包含し、インスタンスのデフォルトを示すクラス変数はもちろんのこと、より適切です。特別な属性について本当に妄想を感じている場合は、最後のリソース測定として読み取り専用記述子を使用してください。

于 2016-03-10T01:41:06.877 に答える
0

私はOz123のクラスデコレータが好きですが、次のこともできます。これは、明示的なクラスラッパーと、クロージャ内のクラスを返すクラスファクトリメソッドで__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()
于 2016-12-05T09:57:32.000 に答える
0

誰かがプロキシオブジェクトの使用について言及しましたが、その例が見当たらないので、結局試してみました[不十分]。

/!\可能であれば、クラス定義とクラスコンストラクターを優先してください

このコードはclass.__new__、あらゆる点で悪いことを除いて、効果的に書き直されています(クラスコンストラクター)。自分の痛みを軽減し、可能であればこのパターンを使用しないでください。

def attr_proxy(obj):
    """ Use dynamic class definition to bind obj and proxy_attrs.
        If you can extend the target class constructor that is 
        cleaner, but its not always trivial to do so.
    """
    proxy_attrs = dict()

    class MyObjAttrProxy():
        def __getattr__(self, name):
            if name in proxy_attrs:
                return proxy_attrs[name]  # overloaded

            return getattr(obj, name)  # proxy

        def __setattr__(self, name, value):
            """ note, self is not bound when overloading methods
            """
            proxy_attrs[name] = value

    return MyObjAttrProxy()


myobj = attr_proxy(Object())
setattr(myobj, 'foo_str', 'foo')

def func_bind_obj_as_self(func, self):
    def _method(*args, **kwargs):
        return func(self, *args, **kwargs)
    return _method

def mymethod(self, foo_ct):
    """ self is not bound because we aren't using object __new__
        you can write the __setattr__ method to bind a self 
        argument, or declare your functions dynamically to bind in 
        a static object reference.
    """
    return self.foo_str + foo_ct

setattr(myobj, 'foo', func_bind_obj_as_self(mymethod, myobj))
于 2020-03-20T18:25:13.437 に答える
-2

このスレッドを完全に復活させていることはわかっていますが、プロパティを読み取り専用にする方法を検討していて、このトピックを見つけた後、すでに共有されているソリューションに満足できませんでした。

したがって、このコードから始める場合は、最初の質問に戻ります。

@property
def x(self):
    return self._x

Xを読み取り専用にしたい場合は、次を追加するだけです。

@x.setter
def x(self, value):
    raise Exception("Member readonly")

次に、以下を実行すると、次のようになります。

print (x) # Will print whatever X value is
x = 3 # Will raise exception "Member readonly"
于 2017-06-22T18:49:56.137 に答える