[ TL; DR? คุณสามารถข้ามไปยังจุดสิ้นสุดได้เพื่อดูตัวอย่างโค้ด ]
จริง ๆ แล้วฉันชอบใช้สำนวนที่แตกต่างกันซึ่งมีส่วนเกี่ยวข้องกับการใช้เพียงเล็กน้อย แต่ดีมากถ้าคุณมีกรณีการใช้ที่ซับซ้อนกว่า
พื้นหลังเล็กน้อยก่อน
คุณสมบัติมีประโยชน์ในการที่พวกเขาช่วยให้เราจัดการทั้งการตั้งค่าและรับค่าในทางโปรแกรม แต่ยังอนุญาตให้เข้าถึงคุณลักษณะเป็นคุณลักษณะ เราสามารถเปลี่ยน 'รับ' เป็น 'การคำนวณ' (เป็นหลัก) และเราสามารถเปลี่ยน 'ชุด' เป็น 'เหตุการณ์' สมมุติว่าเรามีคลาสต่อไปนี้ซึ่งฉันเขียนโค้ดด้วยตัวรับและตัวตั้งเหมือน 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()
คุณอาจสงสัยว่าทำไมฉันไม่โทรdefaultX
และdefaultY
ใน__init__
วิธีการของวัตถุ เหตุผลก็คือสำหรับกรณีของเราฉันต้องการที่จะคิดว่าsomeDefaultComputation
วิธีการส่งกลับค่าที่แตกต่างกันไปในช่วงเวลาพูดเวลาและเมื่อใดก็ตามที่x
(หรือy
) ไม่ได้ตั้งค่า (ที่สำหรับวัตถุประสงค์ของตัวอย่างนี้ "ไม่ได้ตั้ง" หมายถึง "ชุด เป็นไม่มี ") ฉันต้องการค่าการคำนวณเริ่มต้นx
ของ (หรือy
)
ดังนั้นนี่คือเหตุผลบางประการที่อธิบายไว้ข้างต้น ฉันจะเขียนมันใหม่โดยใช้คุณสมบัติ:
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.
เราได้รับอะไรบ้าง เราได้รับความสามารถในการอ้างถึงคุณลักษณะเหล่านี้เป็นคุณลักษณะแม้ว่าเบื้องหลังเราจะสิ้นสุดวิธีการทำงาน
แน่นอนว่าพลังที่แท้จริงของคุณสมบัติคือโดยทั่วไปแล้วเราต้องการวิธีการเหล่านี้เพื่อทำอะไรนอกเหนือจากการรับและตั้งค่า (มิฉะนั้นจะไม่มีประโยชน์ในการใช้คุณสมบัติ) ฉันทำสิ่งนี้ในตัวอย่างที่ทะเยอทะยานของฉัน โดยทั่วไปเราจะเรียกใช้ฟังก์ชั่นของร่างกายเพื่อรับค่าเริ่มต้นเมื่อไม่ได้ตั้งค่า นี่เป็นรูปแบบที่ธรรมดามาก
แต่เราจะสูญเสียอะไรและเราทำอะไรไม่ได้
ความรำคาญหลักในมุมมองของฉันคือถ้าคุณกำหนดผู้ทะเยอทะยาน (ในขณะที่เราทำที่นี่) คุณต้องกำหนดผู้ตั้ง [1] นั่นคือเสียงรบกวนพิเศษที่ตัดรหัส
ความรำคาญอีกประการหนึ่งคือเรายังต้องเริ่มต้นx
และy
ค่า__init__
ใหม่ (แน่นอนว่าเราสามารถเพิ่มพวกเขาโดยใช้setattr()
แต่นั่นคือรหัสพิเศษเพิ่มเติม)
ข้อที่สามไม่เหมือนกับในตัวอย่างที่เหมือนกับ Java ผู้ใช้ไม่สามารถยอมรับพารามิเตอร์อื่น ๆ ได้ ตอนนี้ฉันสามารถได้ยินคุณพูดแล้วดีถ้ามันใช้พารามิเตอร์มันไม่ทะเยอทะยาน! ในความหมายอย่างเป็นทางการนั่นเป็นเรื่องจริง แต่ในทางปฏิบัติไม่มีเหตุผลที่เราไม่ควรสร้างพารามิเตอร์คุณลักษณะที่มีชื่อเช่นx
- และตั้งค่าสำหรับพารามิเตอร์เฉพาะบางอย่าง
มันคงจะดีถ้าเราทำสิ่งที่ชอบ:
e.x[a,b,c] = 10
e.x[d,e,f] = 20
ตัวอย่างเช่น. สิ่งที่ใกล้เคียงที่สุดที่เราจะได้รับคือแทนที่การกำหนดเพื่อบ่งถึงความหมายพิเศษ:
e.x = [a,b,c,10]
e.x = [d,e,f,30]
และแน่นอนทำให้มั่นใจว่า setter ของเรารู้วิธีแยกค่าสามค่าแรกเป็นกุญแจสำคัญในพจนานุกรมและตั้งค่าเป็นตัวเลขหรือบางอย่าง
แต่แม้ว่าเราทำอย่างนั้นเราก็ยังไม่สามารถรองรับมันได้ด้วยคุณสมบัติเนื่องจากไม่มีวิธีรับค่าเพราะเราไม่สามารถส่งพารามิเตอร์ไปยังผู้ทะลุทะลวงได้ ดังนั้นเราต้องกลับมาทุกอย่างเพื่อแนะนำความไม่สมดุล
getter / setter แบบ Java ช่วยให้เราจัดการสิ่งนี้ได้ แต่เรากลับไปต้องการ getter / setters
ในใจของฉันสิ่งที่เราต้องการจริง ๆ คือสิ่งที่มีคุณสมบัติตรงตามข้อกำหนดต่อไปนี้:
ผู้ใช้กำหนดวิธีเดียวสำหรับแอตทริบิวต์ที่กำหนดและสามารถระบุได้ว่าแอตทริบิวต์นั้นเป็นแบบอ่านอย่างเดียวหรืออ่าน - เขียน คุณสมบัติล้มเหลวในการทดสอบนี้หากแอตทริบิวต์เขียนได้
ผู้ใช้ไม่จำเป็นต้องกำหนดตัวแปรเพิ่มเติมที่อ้างอิงฟังก์ชันดังนั้นเราจึงไม่จำเป็นต้องใช้__init__
หรือsetattr
ในรหัส ตัวแปรมีอยู่เพียงเพราะเราได้สร้างแอททริบิวสไตล์ใหม่นี้
โค้ดเริ่มต้นใด ๆ สำหรับแอ็ตทริบิวต์จะเรียกใช้งานในเนื้อความเมธอดเอง
เราสามารถตั้งค่าแอตทริบิวต์เป็นแอตทริบิวต์และอ้างอิงเป็นแอตทริบิวต์
เราสามารถทำให้เป็นพารามิเตอร์คุณลักษณะ
ในแง่ของรหัสเราต้องการวิธีในการเขียน:
def x(self, *args):
return defaultX()
และสามารถทำได้:
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
และอื่น ๆ
นอกจากนี้เรายังต้องการวิธีการทำเช่นนี้สำหรับกรณีพิเศษของแอตทริบิวต์ที่ปรับเปลี่ยนได้ แต่ยังคงอนุญาตให้ใช้กรณีเริ่มต้นที่มอบหมาย คุณจะเห็นว่าฉันจัดการกับปัญหาด้านล่างนี้ได้อย่างไร
ทีนี้ก็ถึงจุด (ใช่! วิธีแก้ปัญหาที่ฉันใช้ในการนี้มีดังนี้
เราสร้างวัตถุใหม่เพื่อแทนที่แนวคิดของคุณสมบัติ วัตถุมีวัตถุประสงค์เพื่อจัดเก็บค่าของตัวแปรที่ตั้งค่าไว้ แต่ยังรักษาการจัดการกับรหัสที่รู้วิธีการคำนวณค่าเริ่มต้น หน้าที่ของมันคือการจัดเก็บชุดvalue
หรือเรียกใช้method
หากไม่ได้ตั้งค่านั้น
โทร Let 's 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
ฉันคิดว่าmethod
ที่นี่เป็นวิธีการเรียนvalue
เป็นมูลค่าของUberProperty
และฉันได้เพิ่มisSet
เพราะNone
อาจเป็นมูลค่าที่แท้จริงและสิ่งนี้ช่วยให้เราวิธีที่สะอาดที่จะประกาศว่าจริงๆคือ "ไม่มีค่า" อีกวิธีหนึ่งคือยามรักษาการณ์บางอย่าง
โดยพื้นฐานแล้วสิ่งนี้ทำให้เรามีวัตถุที่สามารถทำสิ่งที่เราต้องการ แต่เราจะใส่มันในชั้นเรียนของเราได้อย่างไร? คุณสมบัติใช้มัณฑนากร ทำไมเราถึงทำไม่ได้? เรามาดูกันว่ามันจะมีลักษณะอย่างไร (จากนี้ไปฉันจะใช้โดยการใช้คุณลักษณะ 'เดียว' x
)
class Example(object):
@uberProperty
def x(self):
return defaultX()
แน่นอนว่ามันยังใช้งานไม่ได้ เราต้องดำเนินการuberProperty
และตรวจสอบให้แน่ใจว่ามันจัดการทั้งการรับและการตั้งค่า
มาเริ่มกันที่การได้รับ
ความพยายามครั้งแรกของฉันคือเพียงแค่สร้างวัตถุ UberProperty ใหม่และส่งคืน:
def uberProperty(f):
return UberProperty(f)
แน่นอนฉันค้นพบได้อย่างรวดเร็วว่าสิ่งนี้ไม่ได้ผล: Python ไม่ผูกกับ callable กับวัตถุและฉันต้องการวัตถุเพื่อเรียกใช้ฟังก์ชัน แม้การสร้างมัณฑนากรในชั้นเรียนก็ไม่ทำงานเหมือนตอนนี้เรามีชั้นเรียน แต่เราก็ยังไม่มีวัตถุที่จะทำงานด้วย
ดังนั้นเราจะต้องสามารถทำอะไรได้มากกว่านี้ที่นี่ เรารู้ว่าวิธีการนั้นต้องการเพียงแค่การแสดงเพียงครั้งเดียวดังนั้นไปข้างหน้าและรักษามัณฑนากรของเรา แต่แก้ไขUberProperty
เพื่อเก็บการmethod
อ้างอิงเท่านั้น:
class UberProperty(object):
def __init__(self, method):
self.method = method
มันยังไม่สามารถเรียกได้ดังนั้นในขณะนี้ไม่มีอะไรทำงาน
เราจะเติมรูปภาพให้สมบูรณ์ได้อย่างไร? เมื่อเราสร้างคลาสตัวอย่างโดยใช้เครื่องมือตกแต่งใหม่ของเรา:
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>
ในทั้งสองกรณีเราได้รับคืนUberProperty
ซึ่งแน่นอนว่าไม่ใช่ callable ดังนั้นจึงไม่มีประโยชน์อะไรมาก
สิ่งที่เราต้องการคือวิธีการผูกUberProperty
อินสแตนซ์ที่สร้างโดยมัณฑนากรแบบไดนามิกหลังจากคลาสได้ถูกสร้างไปยังวัตถุของคลาสก่อนที่วัตถุนั้นจะถูกส่งกลับไปยังผู้ใช้นั้นเพื่อใช้งาน อืมใช่นั่นเป็น__init__
สายแล้วครับ
ลองเขียนสิ่งที่เราต้องการให้ผลลัพธ์การค้นหาของเราเป็นอันดับแรก เราเชื่อมโยงUberProperty
กับอินสแตนซ์ดังนั้นสิ่งที่ชัดเจนในการส่งคืนจะเป็น BoundUberProperty นี่คือที่เราจะรักษาสถานะสำหรับx
แอตทริบิวต์
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
ตอนนี้เราเป็นตัวแทน; จะนำสิ่งเหล่านี้ไปยังวัตถุได้อย่างไร มีวิธีการไม่กี่วิธี แต่วิธีที่ง่ายที่สุดในการอธิบายเพียงใช้__init__
วิธีการทำแผนที่นั้น เมื่อถึงเวลาที่__init__
จะเรียกว่าตกแต่งของเราได้ทำงานดังนั้นเพียงแค่ต้องมองผ่านวัตถุของ__dict__
และปรับปรุงคุณลักษณะใด ๆ UberProperty
ที่ค่าของแอตทริบิวต์เป็นประเภท
ตอนนี้คุณสมบัติของ uber นั้นเจ๋งและเราอาจต้องการใช้มันมากดังนั้นจึงเหมาะสมที่จะสร้างคลาสพื้นฐานที่ใช้สำหรับคลาสย่อยทั้งหมด ฉันคิดว่าคุณรู้ว่าสิ่งที่ชั้นฐานจะถูกเรียกว่า
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)
เราเพิ่มสิ่งนี้เปลี่ยนตัวอย่างของเราเพื่อสืบทอดจากUberObject
และ ...
e = Example()
print e.x -> <__main__.BoundUberProperty object at 0x104604c90>
หลังจากแก้ไขx
เป็น:
@uberProperty
def x(self):
return *datetime.datetime.now()*
เราสามารถทำการทดสอบอย่างง่าย:
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()
และเราได้ผลลัพธ์ที่เราต้องการ:
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 ฉันทำงานช้า)
โปรดทราบว่าฉันได้ใช้getValue
, setValue
และclearValue
ที่นี่ นี่เป็นเพราะฉันยังไม่ได้เชื่อมโยงในวิธีที่จะให้สิ่งเหล่านี้กลับมาโดยอัตโนมัติ
แต่ฉันคิดว่านี่เป็นสถานที่ที่ดีที่จะหยุดเพราะตอนนี้ฉันเหนื่อย คุณจะเห็นได้ว่าฟังก์ชั่นหลักที่เราต้องการนั้นมีอยู่แล้ว ส่วนที่เหลือคือการแต่งกายหน้าต่าง การแต่งกายที่หน้าต่างการใช้งานที่สำคัญ แต่สามารถรอจนกว่าฉันจะมีการเปลี่ยนแปลงเพื่ออัปเดตโพสต์
ฉันจะทำตัวอย่างให้เสร็จในการโพสต์ครั้งต่อไปโดยพูดถึงสิ่งเหล่านี้:
เราต้องตรวจสอบให้แน่ใจว่า UberObject __init__
ถูกเรียกโดยคลาสย่อยเสมอ
- ดังนั้นเราจึงบังคับให้มันถูกเรียกที่ใดที่หนึ่งหรือเราป้องกันไม่ให้มันถูกนำไปใช้
- เราจะเห็นวิธีการทำเช่นนี้กับ metaclass
เราต้องตรวจสอบให้แน่ใจว่าเราจัดการกับกรณีทั่วไปที่มี 'นามแฝง' คนอื่นทำหน้าที่อย่างอื่นเช่น:
class Example(object):
@uberProperty
def x(self):
...
y = x
เราต้องe.x
กลับe.x.getValue()
โดยปริยาย
- สิ่งที่เราจะเห็นจริง ๆ คือนี่เป็นพื้นที่หนึ่งที่โมเดลล้มเหลว
- ปรากฎว่าเราจะต้องใช้การเรียกใช้ฟังก์ชันเพื่อรับค่า
e.x.getValue()
แต่เราสามารถทำให้มันมีลักษณะเหมือนการเรียกฟังก์ชันปกติและหลีกเลี่ยงการใช้งาน (การทำเช่นนี้ชัดเจนถ้าคุณยังไม่ได้แก้ไข)
เราจำเป็นต้องตั้งค่าการสนับสนุนในขณะที่e.x directly
e.x = <newvalue>
เราสามารถทำได้ในคลาสผู้ปกครองด้วย แต่เราจะต้องอัปเดต__init__
รหัสของเราเพื่อจัดการ
สุดท้ายเราจะเพิ่มคุณสมบัติที่กำหนดพารามิเตอร์ มันควรจะชัดเจนว่าเราจะทำสิ่งนี้อย่างไร
นี่คือรหัสตามที่มีอยู่ในขณะนี้:
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] ฉันอาจจะคิดว่าเรื่องนี้ยังเป็นปัญหาอยู่หรือเปล่า