คุณสมบัติอ่านอย่างเดียวของ Python


97

ฉันไม่รู้ว่าแอตทริบิวต์ควรเป็นส่วนตัวเมื่อใดและควรใช้คุณสมบัติหรือไม่

ฉันอ่านเมื่อเร็ว ๆ นี้ว่า setters และ getters ไม่ใช่ pythonic และฉันควรใช้ property decorator ไม่เป็นไร.

แต่ถ้าฉันมีแอตทริบิวต์จะต้องไม่ถูกตั้งค่าจากภายนอกชั้นเรียน แต่สามารถอ่านได้ (แอตทริบิวต์แบบอ่านอย่างเดียว) แอตทริบิวต์นี้ควรเป็นแบบส่วนตัวหรือไม่และโดยส่วนตัวฉันหมายถึงด้วยเครื่องหมายขีดล่างเช่นนั้นself._xหรือไม่ ถ้าใช่แล้วจะอ่านโดยไม่ใช้ getter ได้อย่างไร? วิธีเดียวที่ฉันรู้ตอนนี้คือการเขียน

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

ด้วยวิธีนี้ฉันสามารถอ่านแอตทริบิวต์ได้obj.xแต่ฉันไม่สามารถตั้งค่าได้obj.x = 1จึงไม่เป็นไร

แต่ฉันควรสนใจเกี่ยวกับการตั้งค่าวัตถุที่ไม่ต้องตั้งค่าหรือไม่? บางทีฉันควรจะปล่อยมันไป แต่แล้วอีกครั้งฉันไม่สามารถใช้ขีดล่างได้เนื่องจากการอ่านobj._xเป็นเลขคี่สำหรับผู้ใช้ดังนั้นฉันจึงควรใช้obj.xอีกครั้งผู้ใช้ไม่รู้ว่าเขาต้องไม่ตั้งค่าคุณลักษณะนี้

ความคิดเห็นและการปฏิบัติของคุณคืออะไร?


1
แนวคิดของคุณสมบัติคือมันทำงานเหมือนแอตทริบิวต์ แต่สามารถมีรหัสพิเศษได้ หากสิ่งที่คุณต้องการคือการได้รับค่าฉันจะไม่ได้รำคาญงาน: ใช้งานเพียงและความไว้วางใจที่ไม่มีใครจะมีการเปลี่ยนแปลงself.x xหากแน่ใจว่าxไม่สามารถเปลี่ยนแปลงได้เป็นสิ่งสำคัญให้ใช้คุณสมบัติ
li.davidm

นอกจากนี้_xไม่ใช่เรื่องแปลก แต่อย่างใด: ตามแบบแผนหมายความว่าเป็น "ส่วนตัว"
li.davidm

1
ฉันหมายความว่าการอ่านจาก _x เป็นเลขคี่ ไม่ใช่ _x ชื่อตัวเอง หากผู้ใช้อ่านโดยตรงจาก _x แสดงว่าเขาไม่รับผิดชอบ
RafałŁużyński

3
สิ่งสำคัญ! ชั้นเรียนของคุณจะต้องเป็นคลาสรูปแบบใหม่ที่สืบทอดมาจากการที่objectคุณจะหยุดการตั้งค่าobj.xนี้ได้ ในคลาสแบบเก่าคุณยังสามารถตั้งค่าได้จริงobj.xพร้อมผลลัพธ์ที่ไม่คาดคิด
Ian H

มีเหตุผลที่ถูกต้องหลายประการในการมีคุณสมบัติแบบอ่านอย่างเดียว หนึ่งคือเมื่อคุณมีค่าที่ประกอบด้วยการรวมค่าอื่น (อ่าน / เขียน) สองค่า คุณสามารถทำได้ในวิธีการนี้ แต่คุณสามารถทำได้ในคุณสมบัติอ่านอย่างเดียวเช่นกัน
philologon

คำตอบ:


73

โดยทั่วไปโปรแกรม Python ควรเขียนโดยสมมติว่าผู้ใช้ทุกคนยินยอมผู้ใหญ่ดังนั้นจึงต้องรับผิดชอบในการใช้สิ่งต่างๆอย่างถูกต้องด้วยตนเอง อย่างไรก็ตามในกรณีที่หายากซึ่งไม่สมเหตุสมผลสำหรับแอตทริบิวต์ที่จะตั้งค่าได้ (เช่นค่าที่ได้รับหรือค่าที่อ่านจากแหล่งข้อมูลแบบคงที่) คุณสมบัติ getter-only โดยทั่วไปเป็นรูปแบบที่ต้องการ


26
ดูเหมือนคำตอบของคุณจะขัดแย้งกับตัวเอง คุณบอกว่าผู้ใช้ควรมีความรับผิดชอบและใช้สิ่งต่าง ๆ อย่างถูกต้องแล้วคุณก็บอกว่าบางครั้งมันก็ไม่สมเหตุสมผลที่แอตทริบิวต์จะสามารถตั้งค่าได้และคุณสมบัติ getter เป็นวิธีที่มีการกำหนดไว้ล่วงหน้า ในความคิดของฉันคุณสามารถตั้งค่าแอตทริบิวต์หรือไม่ได้ คำถามเพียงอย่างเดียวคือฉันควรปกป้อง attr นี้หรือปล่อยไว้ ระหว่างนั้นไม่ควรมีคำตอบ
RafałŁużyński

20
ไม่ฉันบอกว่าถ้าคุณไม่สามารถกำหนดค่าได้อย่างแท้จริงก็ไม่สมเหตุสมผลที่จะมีตัวตั้งค่า ตัวอย่างเช่นหากคุณมีวัตถุวงกลมที่มีสมาชิกรัศมีและแอตทริบิวต์เส้นรอบวงที่ได้มาจากรัศมีหรือคุณมีวัตถุที่ล้อมรอบ API แบบเรียลไทม์แบบอ่านอย่างเดียวที่มีคุณสมบัติ getter-only จำนวนหนึ่ง ไม่มีอะไรขัดแย้งอะไร
Silas Ray

9
แต่ผู้ใช้ที่รับผิดชอบจะไม่พยายามตั้งค่า Attr ที่ไม่สามารถตั้งค่าได้อย่างแท้จริง และอีกครั้งผู้ใช้ที่ไม่รับผิดชอบจะตั้งค่า Attr ที่สามารถตั้งค่าได้อย่างแท้จริงและจะเพิ่มข้อผิดพลาดที่อื่นในรหัสเนื่องจากชุดของเขา ดังนั้นในที่สุดก็ไม่สามารถตั้งค่า Attr ทั้งสองได้ ฉันควรใช้พร็อพเพอร์ตี้กับทั้งสองอย่างหรือไม่ใช้กับคุณสมบัติใด ๆ
RafałŁużyński

8
แต่ผู้ใช้ที่รับผิดชอบไม่ควรพยายามตั้งค่า Attr ที่ไม่สามารถตั้งค่าได้อย่างแท้จริง ในการเขียนโปรแกรมหากบางสิ่งบางอย่างเป็นค่าที่ไม่สามารถตั้งค่าได้อย่างเคร่งครัดสิ่งที่รับผิดชอบหรือสมเหตุสมผลคือการตรวจสอบให้แน่ใจว่าไม่สามารถทำได้ สิ่งเล็กน้อยเหล่านี้ล้วนมีส่วนช่วยให้โปรแกรมมีความน่าเชื่อถือ
Robin Smith

6
นั่นคือตำแหน่งที่ผู้คนและภาษาจำนวนมากใช้ หากเป็นตำแหน่งที่คุณพบว่าไม่สามารถต่อรองได้คุณอาจไม่ควรใช้ Python
Silas Ray

75

Silas Rayเพียงสองเซ็นต์ของฉันก็มาถูกทางแล้ว แต่ฉันรู้สึกเหมือนเพิ่มตัวอย่าง ;-)

Python เป็นภาษาที่ไม่ปลอดภัยดังนั้นคุณจะต้องไว้วางใจผู้ใช้รหัสของคุณเสมอว่าจะใช้รหัสอย่างคนที่สมเหตุสมผล (สมเหตุสมผล)

ต่อPEP 8 :

ใช้ขีดล่างเดียวสำหรับวิธีการที่ไม่ใช่สาธารณะและตัวแปรอินสแตนซ์

หากต้องการมีคุณสมบัติ 'อ่านอย่างเดียว' ในชั้นเรียนที่คุณสามารถใช้ประโยชน์จากการ@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

9
Python ไม่ใช่ประเภทที่ไม่ปลอดภัย แต่จะพิมพ์แบบไดนามิก และการโกงชื่อไม่ได้มีไว้เพื่อทำให้ยากต่อการโกง แต่เพื่อป้องกันการปะทะกันของชื่อในสถานการณ์ที่การสืบทอดอาจเป็นปัญหาได้ (หากคุณไม่ได้เขียนโปรแกรมในขนาดใหญ่คุณไม่ควรสนใจด้วยซ้ำ)
memeplex

3
แต่คุณควรจำไว้ว่าวัตถุที่เปลี่ยนแปลงได้สามารถเปลี่ยนแปลงได้โดยใช้วิธีนี้อยู่ดี ตัวอย่างเช่นถ้าself.__a = []คุณยังสามารถทำได้a.a.append('anything')และจะได้ผล
Igor

3
ยังไม่ชัดเจนสำหรับฉันว่า "บุคคลที่มีเหตุผล (มีเหตุผล) มีผลอย่างไรต่อคำตอบนี้ คุณสามารถอธิบายสิ่งต่างๆที่คุณคิดว่าคนที่มีเหตุผลจะทำและไม่ทำอย่างชัดเจนมากขึ้นได้หรือไม่?
winni2k

3
สำหรับฉันใช้ประโยชน์จากการตกแต่ง @property คุณจะต้องสืบทอดจากวัตถุเมื่อคุณทำเช่นนั้นเป็นจุดรวมของคำตอบนี้ ขอบคุณ.
akki

2
@kkm วิธีเดียวที่จะไม่ให้บั๊กแอบเข้ามาในโค้ดคือห้ามเขียนโค้ด
Alechan

58

นี่คือวิธีหลีกเลี่ยงข้อสันนิษฐานที่ว่า

ผู้ใช้ทุกคนยินยอมผู้ใหญ่ดังนั้นจึงต้องรับผิดชอบในการใช้สิ่งต่างๆอย่างถูกต้องด้วยตนเอง

โปรดดูการอัปเดตของฉันด้านล่าง

การใช้@propertyเป็น verbose มากเช่น:

   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

การใช้

ไม่มีขีดล่าง: เป็นตัวแปรสาธารณะ
ขีดล่างอันเดียว: เป็นตัวแปรที่มีการป้องกัน
ขีดล่างสองอัน: เป็นตัวแปรส่วนตัว

ยกเว้นข้อสุดท้ายมันเป็นอนุสัญญา คุณยังสามารถเข้าถึงตัวแปรด้วยขีดล่างคู่ได้หากคุณพยายามอย่างเต็มที่

ดังนั้นสิ่งที่เราจะทำ? เรายอมแพ้ที่จะมีคุณสมบัติอ่านอย่างเดียวใน 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 shell ของคุณ:

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

1
สิ่งนี้มีประโยชน์มากและทำในสิ่งที่ฉันอยากทำ ขอบคุณ. อย่างไรก็ตามสำหรับผู้ที่ติดตั้ง Python 3 ฉันใช้ Python 2.7.8 ดังนั้นฉันจึงต้องใช้การปรับแต่งเล็กน้อยสองอย่างกับโซลูชันของคุณ: "class NewClass (cls, <b> object <\ b>):" ... "<b> super (NewClass, self) <\ b> .__ setattr __ (ชื่อ, ค่า) ".
Ying Zhang

1
นอกจากนี้ควรระมัดระวังเกี่ยวกับตัวแปรสมาชิกชั้นเรียนที่เป็นรายการและพจนานุกรม คุณไม่สามารถ 'ล็อกไม่ให้อัปเดต' ด้วยวิธีนี้ได้
Ying Zhang

1
การปรับปรุงหนึ่งข้อและปัญหาสามประการที่นี่ การปรับปรุง: if..elif..elseบล็อกสามารถทำได้if name in attrs and name in self.__dict__: raise Attr...โดยไม่passจำเป็น ปัญหาที่ 1: คลาสที่ตกแต่งทั้งหมดจะจบลงด้วยการเหมือนกัน__name__และการแสดงสตริงของประเภทของพวกเขาก็ถูกทำให้เป็นเนื้อเดียวกันเช่นกัน ปัญหาที่ 2: __setattr__การตกแต่งนี้เขียนทับกำหนดเองใด ปัญหาที่ 3: ผู้ใช้สามารถเอาชนะสิ่งนี้ได้ด้วยdel MyClass.__setattr__.
TigerhawkT3

แค่เรื่องภาษา "อนิจจา ... " แปลว่า "เศร้าที่ต้องพูด ... " ซึ่งไม่ใช่สิ่งที่คุณต้องการฉันคิดว่า
Thomas Andrews

object.__setattr__(f, 'forbidden', 42)ไม่มีอะไรที่จะป้องกันไม่ให้ฉันจากการทำ ฉันไม่เห็นสิ่งที่read_only_propertiesเพิ่มที่ไม่ได้รับการจัดการโดยชื่อขีดล่างคู่ที่ยุ่งเหยิง
L3viathan

4

ต่อไปนี้เป็นวิธีการที่แตกต่างกันเล็กน้อยสำหรับคุณสมบัติอ่านอย่างเดียวซึ่งอาจเรียกว่าคุณสมบัติแบบเขียนครั้งเดียวเนื่องจากพวกเขาต้องเริ่มต้นใช่ไหม สำหรับความหวาดระแวงในหมู่พวกเราที่กังวลเกี่ยวกับความสามารถในการปรับเปลี่ยนคุณสมบัติโดยการเข้าถึงพจนานุกรมของวัตถุโดยตรงฉันได้แนะนำชื่อ "สุดขั้ว":

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 __name__ == '__main__':
    try:
        p = Point(2, 3)
        print(p.x, p.y)
        p.x = 9
    except Exception as e:
        print(e)

ดี. หากคุณยุ่งเหยิงdict_nameแทนเช่นdict_name = "_spam_" + nameจะลบการพึ่งพาuuid4และทำให้การดีบักง่ายขึ้นมาก
cz

แต่ฉันสามารถพูดได้ว่าp.__dict__['_spam_x'] = 5จะเปลี่ยนค่าของp.xดังนั้นสิ่งนี้จึงไม่ได้ให้ชื่อที่เพียงพอ
Booboo

1

ฉันไม่พอใจกับสองคำตอบก่อนหน้านี้ในการสร้างคุณสมบัติแบบอ่านอย่างเดียวเนื่องจากโซลูชันแรกอนุญาตให้ลบแอตทริบิวต์แบบอ่านอย่างเดียวจากนั้นตั้งค่าและไม่บล็อก __dict__ วิธีที่สองสามารถแก้ไขได้ด้วยการทดสอบ - ค้นหาค่าที่เท่ากับสิ่งที่คุณตั้งไว้สองค่าและเปลี่ยนค่าในที่สุด

ตอนนี้สำหรับรหัส

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__ จะถูกบล็อกด้วย วิธีเดียวที่จะเปลี่ยนคุณสมบัติอ่านอย่างเดียวคือการเปลี่ยนวิธีการของคลาสเอง

แม้ว่าฉันเชื่อว่าวิธีแก้ปัญหาของฉันดีกว่าสองข้อก่อนหน้านี้ แต่ก็ควรปรับปรุงได้ นี่คือจุดอ่อนของโค้ดนี้:

ก) ไม่อนุญาตให้เพิ่มเมธอดในคลาสย่อยที่กำหนดหรือลบแอตทริบิวต์แบบอ่านอย่างเดียว เมธอดที่กำหนดไว้ในคลาสย่อยจะถูกกันไม่ให้เข้าถึงแอตทริบิวต์แบบอ่านอย่างเดียวโดยอัตโนมัติแม้จะเรียกเมธอดเวอร์ชัน superclass ก็ตาม

b) วิธีการอ่านอย่างเดียวของคลาสสามารถเปลี่ยนแปลงได้เพื่อเอาชนะข้อ จำกัด แบบอ่านอย่างเดียว

อย่างไรก็ตามไม่มีวิธีใดหากไม่แก้ไขคลาสเพื่อตั้งค่าหรือลบแอตทริบิวต์แบบอ่านอย่างเดียว สิ่งนี้ไม่ได้ขึ้นอยู่กับรูปแบบการตั้งชื่อซึ่งเป็นสิ่งที่ดีเนื่องจาก Python ไม่สอดคล้องกับหลักการตั้งชื่อ นี่เป็นวิธีสร้างแอตทริบิวต์แบบอ่านอย่างเดียวที่ไม่สามารถเปลี่ยนแปลงได้ด้วยช่องโหว่ที่ซ่อนอยู่โดยไม่ต้องแก้ไขคลาสเอง เพียงแสดงรายการแอตทริบิวต์ที่จะอ่านเฉพาะเมื่อเรียกมัณฑนากรว่าอาร์กิวเมนต์และจะกลายเป็นแบบอ่านอย่างเดียว

ให้เครดิตกับคำตอบของ Brice ในHow to get the caller class name in a function of another class in python? สำหรับการรับคลาสผู้โทรและวิธีการ


object.__setattr__(pfi, 'readonly', 'foobar')ทำลายโซลูชันนี้โดยไม่ต้องแก้ไขคลาสเอง
L3viathan

0

สังเกตว่าเมธอดอินสแตนซ์ยังเป็นแอตทริบิวต์ (ของคลาส) และคุณสามารถตั้งค่าที่ระดับคลาสหรืออินสแตนซ์ได้หากคุณต้องการเป็นคนเลวจริงๆ หรือว่าคุณอาจตั้งค่าตัวแปรคลาส (ซึ่งเป็นแอตทริบิวต์ของคลาสด้วย) โดยที่คุณสมบัติแบบอ่านอย่างเดียวที่ใช้งานง่ายจะไม่ทำงานอย่างเป็นระเบียบ สิ่งที่ฉันพยายามจะบอกก็คือปัญหา "แอตทริบิวต์แบบอ่านอย่างเดียว" ในความเป็นจริงนั้นมีอยู่ทั่วไปมากกว่าที่มักจะรับรู้ โชคดีที่มีความคาดหวังแบบเดิมในการทำงานที่แข็งแกร่งจนทำให้เราตาบอดในกรณีอื่น ๆ เหล่านี้ (ท้ายที่สุดเกือบทุกอย่างเป็นคุณลักษณะบางอย่างใน python)

จากความคาดหวังเหล่านี้ฉันคิดว่าวิธีการทั่วไปและมีน้ำหนักเบาที่สุดคือการปรับใช้อนุสัญญาว่าแอตทริบิวต์ "สาธารณะ" (ไม่มีขีดล่างนำหน้า) เป็นแบบอ่านอย่างเดียวยกเว้นเมื่อมีการบันทึกไว้อย่างชัดเจนว่าสามารถเขียนได้ สิ่งนี้จะช่วยลดความคาดหวังตามปกติที่เมธอดจะไม่ได้รับการแก้ไขและตัวแปรคลาสที่ระบุค่าเริ่มต้นของอินสแตนซ์จะดีกว่านับประสา หากคุณรู้สึกหวาดระแวงเกี่ยวกับคุณลักษณะพิเศษบางอย่างให้ใช้ตัวอธิบายแบบอ่านอย่างเดียวเป็นตัววัดทรัพยากรสุดท้าย


0

ในขณะที่ฉันชอบมัณฑนากรชั้นเรียนจาก Oz123 แต่คุณสามารถทำสิ่งต่อไปนี้ได้ซึ่งใช้กระดาษห่อคลาสที่ชัดเจนและ __new__ ด้วยวิธีการคลาส Factory ที่ส่งคืนคลาสภายในการปิด:

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()

คุณควรเพิ่มการทดสอบที่แสดงว่ามันทำงานอย่างไรกับคุณสมบัติหลายอย่าง
Oz123

0

นั่นเป็นวิธีแก้ปัญหาของฉัน

@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

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))

-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"

3
แต่ถ้าคุณไม่สร้าง setter การพยายามกำหนดจะทำให้เกิดข้อผิดพลาดเช่นกัน (An AttributeError('can't set attribute'))
Artyer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.