[ TL; DR? Bạn có thể bỏ qua đến cuối cho một ví dụ mã .]
Tôi thực sự thích sử dụng một thành ngữ khác, một chút liên quan đến việc sử dụng như một từ tắt, nhưng thật tuyệt nếu bạn có trường hợp sử dụng phức tạp hơn.
Một chút nền tảng đầu tiên.
Các thuộc tính hữu ích ở chỗ chúng cho phép chúng ta xử lý cả cài đặt và nhận giá trị theo cách lập trình nhưng vẫn cho phép các thuộc tính được truy cập dưới dạng thuộc tính. Chúng ta có thể biến 'được' thành 'tính toán' (về cơ bản) và chúng ta có thể biến 'bộ' thành 'sự kiện'. Vì vậy, giả sử chúng ta có lớp sau, mà tôi đã mã hóa bằng các getters và setters giống như Java.
class Example(object):
def __init__(self, x=None, y=None):
self.x = x
self.y = y
def getX(self):
return self.x or self.defaultX()
def getY(self):
return self.y or self.defaultY()
def setX(self, x):
self.x = x
def setY(self, y):
self.y = y
def defaultX(self):
return someDefaultComputationForX()
def defaultY(self):
return someDefaultComputationForY()
Bạn có thể tự hỏi tại sao tôi không gọi defaultX
và defaultY
trong __init__
phương thức của đối tượng . Lý do là vì trường hợp của chúng tôi, tôi muốn giả sử rằng các someDefaultComputation
phương thức trả về các giá trị thay đổi theo thời gian, giả sử dấu thời gian và bất cứ khi nào x
(hoặc y
) không được đặt (trong đó, với mục đích của ví dụ này, "không đặt" có nghĩa là "đặt" thành Không ") Tôi muốn giá trị của tính toán mặc định x
của (hoặc y
).
Vì vậy, điều này là khập khiễng vì một số lý do mô tả ở trên. Tôi sẽ viết lại bằng các thuộc tính:
class Example(object):
def __init__(self, x=None, y=None):
self._x = x
self._y = y
@property
def x(self):
return self.x or self.defaultX()
@x.setter
def x(self, value):
self._x = value
@property
def y(self):
return self.y or self.defaultY()
@y.setter
def y(self, value):
self._y = value
# default{XY} as before.
Chúng ta đã đạt được gì? Chúng tôi đã đạt được khả năng coi các thuộc tính này là các thuộc tính mặc dù, đằng sau hậu trường, chúng tôi kết thúc các phương thức chạy.
Tất nhiên, sức mạnh thực sự của các thuộc tính là chúng ta thường muốn các phương thức này thực hiện một việc gì đó ngoài việc chỉ nhận và thiết lập các giá trị (nếu không thì không có điểm nào trong việc sử dụng các thuộc tính). Tôi đã làm điều này trong ví dụ getter của tôi. Về cơ bản, chúng tôi đang chạy một thân hàm để lấy mặc định bất cứ khi nào giá trị không được đặt. Đây là một mô hình rất phổ biến.
Nhưng chúng ta đang mất gì và chúng ta không thể làm gì?
Khó chịu chính, theo quan điểm của tôi, là nếu bạn xác định một getter (như chúng tôi làm ở đây), bạn cũng phải xác định một setter. [1] Đó là tiếng ồn thêm làm tắc mã.
Một phiền toái khác là chúng ta vẫn phải khởi tạo x
và y
các giá trị trong __init__
. (Chà, tất nhiên chúng ta có thể thêm chúng bằng cách sử dụng setattr()
nhưng đó là nhiều mã hơn.)
Thứ ba, không giống như trong ví dụ giống như Java, getters không thể chấp nhận các tham số khác. Bây giờ tôi có thể nghe bạn nói rồi, nếu nó lấy tham số thì nó không phải là một getter! Trong một ý nghĩa chính thức, đó là sự thật. Nhưng trong một ý nghĩa thực tế, không có lý do gì chúng ta không thể tham số hóa một thuộc tính được đặt tên - như x
- và đặt giá trị của nó cho một số tham số cụ thể.
Thật tuyệt nếu chúng ta có thể làm một cái gì đó như:
e.x[a,b,c] = 10
e.x[d,e,f] = 20
ví dụ. Cách gần nhất chúng ta có thể nhận được là ghi đè lên bài tập để ngụ ý một số ngữ nghĩa đặc biệt:
e.x = [a,b,c,10]
e.x = [d,e,f,30]
và tất nhiên đảm bảo rằng trình thiết lập của chúng tôi biết cách trích xuất ba giá trị đầu tiên làm khóa cho từ điển và đặt giá trị của nó thành một số hoặc một cái gì đó.
Nhưng ngay cả khi chúng tôi đã làm điều đó, chúng tôi vẫn không thể hỗ trợ nó với các thuộc tính vì không có cách nào để nhận được giá trị vì chúng tôi không thể truyền tham số nào cho getter. Vì vậy, chúng tôi đã phải trả lại mọi thứ, giới thiệu một sự bất cân xứng.
Trình getter / setter kiểu Java không cho phép chúng ta xử lý việc này, nhưng chúng ta quay lại cần getter / setters.
Trong tâm trí của tôi, những gì chúng tôi thực sự muốn là một cái gì đó nắm bắt các yêu cầu sau đây:
Người dùng chỉ xác định một phương thức cho một thuộc tính nhất định và có thể chỉ ra rằng thuộc tính đó là chỉ đọc hay đọc-ghi. Thuộc tính thất bại kiểm tra này nếu thuộc tính có thể ghi.
Người dùng không cần xác định một biến phụ bên dưới hàm, vì vậy chúng ta không cần __init__
hoặc setattr
trong mã. Biến chỉ tồn tại bởi thực tế chúng ta đã tạo thuộc tính kiểu mới này.
Bất kỳ mã mặc định nào cho thuộc tính thực thi trong chính thân phương thức.
Chúng ta có thể đặt thuộc tính làm thuộc tính và tham chiếu nó như một thuộc tính.
Chúng ta có thể tham số hóa thuộc tính.
Về mặt mã, chúng tôi muốn có một cách để viết:
def x(self, *args):
return defaultX()
và có thể sau đó làm:
print e.x -> The default at time T0
e.x = 1
print e.x -> 1
e.x = None
print e.x -> The default at time T1
và kể từ đó trở đi.
Chúng tôi cũng muốn có một cách để làm điều này cho trường hợp đặc biệt của thuộc tính tham số hóa, nhưng vẫn cho phép trường hợp gán mặc định hoạt động. Bạn sẽ thấy cách tôi giải quyết điều này dưới đây.
Bây giờ đến điểm (yay! Điểm!). Giải pháp tôi đưa ra cho việc này là như sau.
Chúng tôi tạo ra một đối tượng mới để thay thế khái niệm về một tài sản. Đối tượng dự định lưu trữ giá trị của một biến được đặt cho nó, nhưng cũng duy trì một điều khiển trên mã biết cách tính toán mặc định. Công việc của nó là lưu trữ tập hợp value
hoặc chạy method
nếu giá trị đó không được đặt.
Hãy gọi nó là một UberProperty
.
class UberProperty(object):
def __init__(self, method):
self.method = method
self.value = None
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def clearValue(self):
self.value = None
self.isSet = False
Tôi giả sử method
ở đây là một phương thức lớp, value
là giá trị của UberProperty
và tôi đã thêm vào isSet
bởi vì None
có thể là một giá trị thực và điều này cho phép chúng ta một cách rõ ràng để tuyên bố thực sự là "không có giá trị". Một cách khác là một trọng điểm của một số loại.
Điều này về cơ bản mang đến cho chúng ta một đối tượng có thể làm những gì chúng ta muốn, nhưng làm thế nào để chúng ta thực sự đưa nó vào lớp học? Vâng, tài sản sử dụng trang trí; Tại sao chúng ta không thể? Chúng ta hãy xem nó trông như thế nào (từ đây trở đi tôi sẽ chỉ sử dụng một 'thuộc tính' duy nhất, x
).
class Example(object):
@uberProperty
def x(self):
return defaultX()
Điều này thực sự không hoạt động, tất nhiên. Chúng tôi phải thực hiện uberProperty
và đảm bảo rằng nó xử lý cả get và set.
Hãy bắt đầu với.
Nỗ lực đầu tiên của tôi chỉ đơn giản là tạo một đối tượng UberProperty mới và trả lại nó:
def uberProperty(f):
return UberProperty(f)
Tất nhiên, tôi nhanh chóng phát hiện ra rằng điều này không hoạt động: Python không bao giờ liên kết có thể gọi được với đối tượng và tôi cần đối tượng để gọi hàm. Ngay cả việc tạo trình trang trí trong lớp cũng không hoạt động, vì mặc dù bây giờ chúng ta có lớp, chúng ta vẫn không có đối tượng để làm việc.
Vì vậy, chúng ta sẽ cần phải có thể làm nhiều hơn ở đây. Chúng tôi biết rằng một phương thức chỉ cần được trình bày một lần, vì vậy hãy tiếp tục và giữ trang trí của chúng tôi, nhưng sửa đổi UberProperty
để chỉ lưu trữ method
tham chiếu:
class UberProperty(object):
def __init__(self, method):
self.method = method
Nó cũng không thể gọi được, vì vậy tại thời điểm này không có gì là làm việc.
Làm thế nào để chúng ta hoàn thành bức tranh? Chà, cuối cùng chúng ta sẽ làm gì khi tạo lớp mẫu bằng cách sử dụng trang trí mới của chúng tôi:
class Example(object):
@uberProperty
def x(self):
return defaultX()
print Example.x <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x <__main__.UberProperty object at 0x10e1fb8d0>
trong cả hai trường hợp, chúng tôi nhận lại được UberProperty
điều mà tất nhiên không thể gọi được, vì vậy điều này không được sử dụng nhiều.
Những gì chúng ta cần là một số cách để tự động liên kết UberProperty
cá thể được tạo bởi trình trang trí sau khi lớp đã được tạo cho một đối tượng của lớp trước khi đối tượng đó được trả về cho người dùng đó để sử dụng. Ừm, ừ, đó là một __init__
cuộc gọi, anh bạn.
Hãy viết những gì chúng tôi muốn kết quả tìm thấy của chúng tôi là đầu tiên. Chúng tôi đang ràng buộc một UberProperty
trường hợp, vì vậy một điều rõ ràng để trở lại sẽ là BoundUberProperty. Đây là nơi chúng ta sẽ thực sự duy trì trạng thái cho x
thuộc tính.
class BoundUberProperty(object):
def __init__(self, obj, uberProperty):
self.obj = obj
self.uberProperty = uberProperty
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def getValue(self):
return self.value if self.isSet else self.uberProperty.method(self.obj)
def clearValue(self):
del self.value
self.isSet = False
Bây giờ chúng tôi đại diện; Làm thế nào để có được những điều này trên một đối tượng? Có một vài cách tiếp cận, nhưng cách dễ nhất để giải thích chỉ sử dụng __init__
phương pháp để thực hiện ánh xạ đó. Theo thời gian __init__
được gọi là trang trí của chúng tôi đã chạy, vì vậy chỉ cần xem qua các đối tượng __dict__
và cập nhật bất kỳ thuộc tính nào trong đó giá trị của thuộc tính là loại UberProperty
.
Bây giờ, các thuộc tính uber rất tuyệt và có lẽ chúng ta sẽ muốn sử dụng chúng rất nhiều, vì vậy sẽ rất hợp lý khi tạo một lớp cơ sở thực hiện điều này cho tất cả các lớp con. Tôi nghĩ bạn biết lớp cơ sở sẽ được gọi là gì.
class UberObject(object):
def __init__(self):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, UberProperty):
v = BoundUberProperty(self, v)
setattr(self, k, v)
Chúng tôi thêm điều này, thay đổi ví dụ của chúng tôi để kế thừa từ UberObject
và ...
e = Example()
print e.x -> <__main__.BoundUberProperty object at 0x104604c90>
Sau khi sửa đổi x
thành:
@uberProperty
def x(self):
return *datetime.datetime.now()*
Chúng tôi có thể chạy thử nghiệm đơn giản:
print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()
Và chúng tôi nhận được đầu ra mà chúng tôi muốn:
2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310
(Gee, tôi đang làm việc muộn.)
Lưu ý rằng tôi đã sử dụng getValue
, setValue
và clearValue
ở đây. Điều này là do tôi chưa được liên kết trong các phương tiện để có những thứ này tự động được trả lại.
Nhưng tôi nghĩ rằng đây là một nơi tốt để dừng lại vì tôi đang mệt mỏi. Bạn cũng có thể thấy rằng chức năng cốt lõi mà chúng tôi muốn đã có; phần còn lại là thay đồ cửa sổ. Thay đổi cửa sổ khả năng sử dụng quan trọng, nhưng điều đó có thể đợi cho đến khi tôi có thay đổi để cập nhật bài viết.
Tôi sẽ hoàn thành ví dụ trong bài đăng tiếp theo bằng cách giải quyết những điều sau:
Chúng tôi cần đảm bảo rằng UberObject __init__
luôn được các lớp con gọi.
- Vì vậy, chúng tôi hoặc buộc nó được gọi ở đâu đó hoặc chúng tôi ngăn chặn nó được thực hiện.
- Chúng ta sẽ xem làm thế nào để làm điều này với một siêu dữ liệu.
Chúng ta cần đảm bảo rằng chúng ta xử lý trường hợp phổ biến trong đó ai đó 'bí danh' một chức năng cho một thứ khác, chẳng hạn như:
class Example(object):
@uberProperty
def x(self):
...
y = x
Chúng tôi cần e.x
phải trả lại e.x.getValue()
theo mặc định.
- Những gì chúng ta thực sự sẽ thấy là đây là một lĩnh vực mà mô hình thất bại.
- Hóa ra chúng ta sẽ luôn cần sử dụng lệnh gọi hàm để nhận giá trị.
- Nhưng chúng ta có thể làm cho nó trông giống như một cuộc gọi chức năng thông thường và tránh phải sử dụng
e.x.getValue()
. (Làm điều này là rõ ràng, nếu bạn chưa sửa nó.)
Chúng tôi cần hỗ trợ cài đặt e.x directly
, như trong e.x = <newvalue>
. Chúng tôi cũng có thể làm điều này trong lớp cha, nhưng chúng tôi sẽ cần cập nhật __init__
mã của chúng tôi để xử lý nó.
Cuối cùng, chúng ta sẽ thêm các thuộc tính được tham số hóa. Nó cũng khá rõ ràng về cách chúng ta cũng sẽ làm điều này.
Đây là mã như nó tồn tại cho đến nay:
import datetime
class UberObject(object):
def uberSetter(self, value):
print 'setting'
def uberGetter(self):
return self
def __init__(self):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, UberProperty):
v = BoundUberProperty(self, v)
setattr(self, k, v)
class UberProperty(object):
def __init__(self, method):
self.method = method
class BoundUberProperty(object):
def __init__(self, obj, uberProperty):
self.obj = obj
self.uberProperty = uberProperty
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def getValue(self):
return self.value if self.isSet else self.uberProperty.method(self.obj)
def clearValue(self):
del self.value
self.isSet = False
def uberProperty(f):
return UberProperty(f)
class Example(UberObject):
@uberProperty
def x(self):
return datetime.datetime.now()
[1] Tôi có thể đứng sau về việc liệu đây có còn là vấn đề không.