Tôi đang cố gắng hiểu mô tả của Python là gì và chúng có thể hữu ích cho việc gì.
Mô tả là các thuộc tính lớp (như thuộc tính hoặc phương thức) với bất kỳ phương thức đặc biệt nào sau đây:
__get__
(phương pháp mô tả phi dữ liệu, ví dụ về phương thức / hàm)
__set__
(phương pháp mô tả dữ liệu, ví dụ trên một ví dụ thuộc tính)
__delete__
(phương pháp mô tả dữ liệu)
Các đối tượng mô tả này có thể được sử dụng làm thuộc tính trên các định nghĩa lớp đối tượng khác. (Đó là, họ sống trong __dict__
đối tượng của lớp.)
Các đối tượng mô tả có thể được sử dụng để lập trình quản lý các kết quả của một tra cứu chấm (ví dụ foo.descriptor
) trong một biểu thức bình thường, một bài tập và thậm chí xóa.
Chức năng / phương pháp, phương pháp ràng buộc, property
, classmethod
, và staticmethod
tất cả sử dụng những phương pháp đặc biệt để kiểm soát cách thức chúng được truy cập thông qua tra cứu rải rác.
Một bộ mô tả dữ liệu , như property
, có thể cho phép đánh giá lười biếng các thuộc tính dựa trên trạng thái đơn giản hơn của đối tượng, cho phép các cá thể sử dụng ít bộ nhớ hơn nếu bạn tính toán trước từng thuộc tính có thể.
Một mô tả dữ liệu khác, a member_descriptor
, được tạo bởi __slots__
, cho phép tiết kiệm bộ nhớ bằng cách cho phép lớp lưu trữ dữ liệu trong cơ sở dữ liệu giống như bộ dữ liệu thay đổi thay vì linh hoạt hơn nhưng tốn nhiều không gian hơn __dict__
.
Các bộ mô tả phi dữ liệu, thường là các thể hiện, lớp và các phương thức tĩnh, nhận các đối số đầu tiên ẩn (thường được đặt tên cls
và self
, tương ứng) từ phương thức mô tả không dữ liệu của chúng , __get__
.
Hầu hết người dùng Python chỉ cần học cách sử dụng đơn giản và không cần phải tìm hiểu hoặc hiểu thêm về việc triển khai các mô tả.
Trong chiều sâu: Mô tả là gì?
Một mô tả là một đối tượng với bất kỳ phương thức nào sau đây ( __get__
, __set__
hoặc __delete__
), được dự định sẽ được sử dụng thông qua tra cứu chấm chấm như thể nó là một thuộc tính điển hình của một thể hiện. Đối với một đối tượng chủ sở hữu obj_instance
, với một descriptor
đối tượng:
obj_instance.descriptor
yêu cầu
descriptor.__get__(self, obj_instance, owner_class)
trả về một value
Đây là cách tất cả các phương thức và get
trên một thuộc tính hoạt động.
obj_instance.descriptor = value
gọi
descriptor.__set__(self, obj_instance, value)
trở lại None
Đây là cách làm việc setter
trên một tài sản.
del obj_instance.descriptor
gọi
descriptor.__delete__(self, obj_instance)
trở lại None
Đây là cách làm việc deleter
trên một tài sản.
obj_instance
là cá thể có lớp chứa đối tượng mô tả. self
là ví dụ của bộ mô tả (có lẽ chỉ là một cho lớp của obj_instance
)
Để xác định điều này với mã, một đối tượng là một mô tả nếu tập hợp các thuộc tính của nó giao với bất kỳ thuộc tính bắt buộc nào:
def has_descriptor_attrs(obj):
return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))
def is_descriptor(obj):
"""obj can be instance of descriptor or the descriptor class"""
return bool(has_descriptor_attrs(obj))
Một mô tả dữ liệu có một __set__
và / hoặc __delete__
.
Một Non-Data-Descriptor có không __set__
hay __delete__
.
def has_data_descriptor_attrs(obj):
return set(['__set__', '__delete__']) & set(dir(obj))
def is_data_descriptor(obj):
return bool(has_data_descriptor_attrs(obj))
Ví dụ về đối tượng mô tả dựng sẵn:
classmethod
staticmethod
property
- chức năng nói chung
Mô tả phi dữ liệu
Chúng ta có thể thấy điều đó classmethod
và staticmethod
là những người không mô tả dữ liệu:
>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)
Cả hai chỉ có __get__
phương pháp:
>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))
Lưu ý rằng tất cả các chức năng cũng không phải là Mô tả dữ liệu:
>>> def foo(): pass
...
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)
Mô tả dữ liệu, property
Tuy nhiên, property
là một Mô tả dữ liệu:
>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])
Lệnh tra cứu chấm
Đây là những điểm khác biệt quan trọng , vì chúng ảnh hưởng đến thứ tự tra cứu cho một tra cứu rải rác.
obj_instance.attribute
- Đầu tiên, ở trên để xem liệu thuộc tính có phải là Mô tả dữ liệu trên lớp của thể hiện hay không,
- Nếu không, nó sẽ xem liệu thuộc tính có trong
obj_instance
's không __dict__
, sau đó
- cuối cùng nó rơi trở lại một Mô tả không dữ liệu.
Hậu quả của thứ tự tra cứu này là các Mô tả / Dữ liệu không giống như các hàm / phương thức có thể bị ghi đè bởi các thể hiện .
Tóm tắt và các bước tiếp theo
Chúng tôi đã học được rằng mô tả là những vật thể với bất kỳ __get__
, __set__
hoặc __delete__
. Các đối tượng mô tả này có thể được sử dụng làm thuộc tính trên các định nghĩa lớp đối tượng khác. Bây giờ chúng tôi sẽ xem xét cách chúng được sử dụng, sử dụng mã của bạn làm ví dụ.
Phân tích mã từ câu hỏi
Đây là mã của bạn, theo sau là câu hỏi và câu trả lời của bạn cho từng câu:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
- Tại sao tôi cần lớp mô tả?
Bộ mô tả của bạn đảm bảo bạn luôn có dấu phẩy cho thuộc tính lớp này Temperature
và bạn không thể sử dụng del
để xóa thuộc tính:
>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
Mặt khác, các mô tả của bạn bỏ qua lớp chủ sở hữu và các thể hiện của chủ sở hữu, thay vào đó, lưu trữ trạng thái trong bộ mô tả. Bạn có thể dễ dàng chia sẻ trạng thái trên tất cả các trường hợp bằng một thuộc tính lớp đơn giản (miễn là bạn luôn đặt nó ở dạng nổi cho lớp và không bao giờ xóa nó, hoặc thoải mái với người dùng mã của bạn làm như vậy):
class Temperature(object):
celsius = 0.0
Điều này giúp bạn có chính xác hành vi tương tự như ví dụ của bạn (xem câu trả lời cho câu hỏi 3 bên dưới), nhưng sử dụng hàm Pythons dựng sẵn ( property
) và sẽ được coi là thành ngữ hơn:
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
- Ví dụ và chủ sở hữu ở đây là gì? (trong nhận ). Mục đích của các tham số này là gì?
instance
là ví dụ của chủ sở hữu đang gọi mô tả. Chủ sở hữu là lớp trong đó đối tượng mô tả được sử dụng để quản lý quyền truy cập vào điểm dữ liệu. Xem các mô tả về các phương pháp đặc biệt xác định các mô tả bên cạnh đoạn đầu tiên của câu trả lời này để biết thêm các tên biến mô tả.
- Làm thế nào tôi có thể gọi / sử dụng ví dụ này?
Đây là một minh chứng:
>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>>
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0
Bạn không thể xóa thuộc tính:
>>> del t2.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
Và bạn không thể chỉ định một biến không thể chuyển đổi thành float:
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02
Mặt khác, những gì bạn có ở đây là trạng thái toàn cầu cho tất cả các trường hợp, được quản lý bằng cách gán cho bất kỳ trường hợp nào.
Cách dự kiến mà hầu hết các lập trình viên Python có kinh nghiệm sẽ thực hiện kết quả này là sử dụng trình property
trang trí, sử dụng cùng một mô tả dưới mui xe, nhưng đưa hành vi vào việc thực hiện lớp chủ sở hữu (một lần nữa, như được định nghĩa ở trên):
class Temperature(object):
_celsius = 0.0
@property
def celsius(self):
return type(self)._celsius
@celsius.setter
def celsius(self, value):
type(self)._celsius = float(value)
Có hành vi dự kiến chính xác giống như đoạn mã gốc:
>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02
Phần kết luận
Chúng tôi đã đề cập đến các thuộc tính xác định mô tả, sự khác biệt giữa mô tả dữ liệu và không mô tả dữ liệu, các đối tượng dựng sẵn sử dụng chúng và các câu hỏi cụ thể về việc sử dụng.
Vì vậy, một lần nữa, bạn sẽ sử dụng ví dụ của câu hỏi như thế nào? Tôi hy vọng bạn sẽ không. Tôi hy vọng bạn sẽ bắt đầu với đề xuất đầu tiên của tôi (một thuộc tính lớp đơn giản) và chuyển sang đề xuất thứ hai (trang trí thuộc tính) nếu bạn cảm thấy cần thiết.
self
và làinstance
gì?