Tôi không hài lòng với hai câu trả lời trước để tạo thuộc tính chỉ đọc vì giải pháp đầu tiên cho phép xóa thuộc tính chỉ đọc, sau đó đặt và không chặn __dict__. Giải pháp thứ hai có thể được giải quyết với thử nghiệm - tìm giá trị bằng với những gì bạn đặt nó hai và thay đổi nó cuối cùng.
Bây giờ, đối với mã.
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()
Không có lý do gì để tạo thuộc tính chỉ đọc ngoại trừ khi mã thư viện đang viết của bạn , mã đang được phân phối cho người khác dưới dạng mã để sử dụng nhằm nâng cao chương trình của họ, không phải mã cho bất kỳ mục đích nào khác, như phát triển ứng dụng. Vấn đề __dict__ đã được giải quyết, vì __dict__ bây giờ thuộc loại bất biến.MappingProxyType , vì vậy các thuộc tính không thể thay đổi thông qua __dict__. Cài đặt hoặc xóa __dict__ cũng bị chặn. Cách duy nhất để thay đổi các thuộc tính chỉ đọc là thông qua việc thay đổi các phương thức của chính lớp đó.
Mặc dù tôi tin rằng giải pháp của tôi tốt hơn hai giải pháp trước, nhưng nó có thể được cải thiện. Đây là những điểm yếu của mã này:
a) Không cho phép thêm vào một phương thức trong một lớp con để đặt hoặc xóa một thuộc tính chỉ đọc. Một phương thức được định nghĩa trong lớp con sẽ tự động bị cấm truy cập vào thuộc tính chỉ đọc, ngay cả khi gọi phiên bản của phương thức này.
b) Các phương thức chỉ đọc của lớp 'có thể được thay đổi để đánh bại các hạn chế chỉ đọc.
Tuy nhiên, không có cách nào mà không chỉnh sửa lớp để đặt hoặc xóa thuộc tính chỉ đọc. Điều này không phụ thuộc vào quy ước đặt tên, điều này tốt vì Python không nhất quán với quy ước đặt tên. Điều này cung cấp một cách để thực hiện các thuộc tính chỉ đọc mà không thể thay đổi với các sơ hở ẩn mà không cần chỉnh sửa chính lớp đó. Đơn giản chỉ cần liệt kê các thuộc tính chỉ được đọc khi gọi trình trang trí dưới dạng đối số và chúng sẽ trở thành chỉ đọc.
Ghi nhận câu trả lời của Brice trong Cách lấy tên lớp người gọi bên trong một hàm của lớp khác trong python? để lấy các lớp và phương thức của người gọi.
self.x
và tin tưởng rằng không ai sẽ thay đổix
. Nếu đảm bảo rằng điều đóx
không thể thay đổi là quan trọng, thì hãy sử dụng thuộc tính.