ฉันทำมันเหมือน:
def set_property(property,value):
def get_property(property):
หรือ
object.property = value
value = object.property
ฉันใหม่กับ Python ดังนั้นฉันยังคงสำรวจไวยากรณ์อยู่และฉันต้องการคำแนะนำในการทำสิ่งนี้
ฉันทำมันเหมือน:
def set_property(property,value):
def get_property(property):
หรือ
object.property = value
value = object.property
ฉันใหม่กับ Python ดังนั้นฉันยังคงสำรวจไวยากรณ์อยู่และฉันต้องการคำแนะนำในการทำสิ่งนี้
คำตอบ:
ลองสิ่งนี้: คุณสมบัติของ Python
รหัสตัวอย่างคือ:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
print("getter of x called")
return self._x
@x.setter
def x(self, value):
print("setter of x called")
self._x = value
@x.deleter
def x(self):
print("deleter of x called")
del self._x
c = C()
c.x = 'foo' # setter called
foo = c.x # getter called
del c.x # deleter called
._x
(ซึ่งไม่ใช่สถานที่ให้บริการเพียงแอตทริบิวต์ธรรมดา) หลีกเลี่ยงการproperty
ตัดคำ เฉพาะการอ้างอิงถึงผ่านไป.x
property
pythonic เป็นวิธีการใช้ getters และ setters อะไร
วิธี "Pythonic" ไม่ใช้ "getters" และ "setters" แต่ใช้แอตทริบิวต์แบบธรรมดาเช่นคำถามแสดงให้เห็นและdel
สำหรับการลบ (แต่ชื่อจะเปลี่ยนเพื่อปกป้องผู้บริสุทธิ์ ... builtins):
value = 'something'
obj.attribute = value
value = obj.attribute
del obj.attribute
หากภายหลังคุณต้องการแก้ไขการตั้งค่าและรับคุณสามารถทำได้โดยไม่ต้องเปลี่ยนรหัสผู้ใช้โดยใช้เครื่องมือproperty
ตกแต่ง:
class Obj:
"""property demo"""
#
@property # first decorate the getter method
def attribute(self): # This getter method name is *the* name
return self._attribute
#
@attribute.setter # the property decorates with `.setter` now
def attribute(self, value): # name, e.g. "attribute", is the same
self._attribute = value # the "value" name isn't special
#
@attribute.deleter # decorate with `.deleter`
def attribute(self): # again, the method name is the same
del self._attribute
(สำเนาการใช้มัณฑนากรแต่ละคนและอัปเดตวัตถุคุณสมบัติก่อนหน้าดังนั้นโปรดทราบว่าคุณควรใช้ชื่อเดียวกันสำหรับแต่ละชุดรับและลบฟังก์ชัน / เมธอด
หลังจากกำหนดข้างต้นการตั้งค่าการรับและการลบรหัสเดิมจะเหมือนกัน:
obj = Obj()
obj.attribute = value
the_value = obj.attribute
del obj.attribute
คุณควรหลีกเลี่ยงสิ่งนี้:
def set_property(property,value): def get_property(property):
ประการแรกข้างต้นใช้งานไม่ได้เนื่องจากคุณไม่ได้ระบุอาร์กิวเมนต์สำหรับอินสแตนซ์ที่คุณสมบัติจะถูกตั้งค่าเป็น (โดยปกติself
) ซึ่งจะเป็น:
class Obj:
def set_property(self, property, value): # don't do this
...
def get_property(self, property): # don't do this either
...
ประการที่สองนี่เป็นการทำซ้ำวัตถุประสงค์ของสองวิธีพิเศษ__setattr__
และ__getattr__
และ
ประการที่สามเรายังมีฟังก์ชั่นsetattr
และgetattr
builtin
setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value) # default is optional
@property
มัณฑนากรสำหรับ getters สร้างและ setters
ตัวอย่างเช่นเราสามารถปรับเปลี่ยนพฤติกรรมการตั้งค่าเพื่อ จำกัด การตั้งค่าไว้:
class Protective(object):
@property
def protected_value(self):
return self._protected_value
@protected_value.setter
def protected_value(self, value):
if acceptable(value): # e.g. type or range check
self._protected_value = value
โดยทั่วไปเราต้องการหลีกเลี่ยงการใช้property
และเพียงแค่ใช้คุณสมบัติโดยตรง
นี่คือสิ่งที่ผู้ใช้ Python คาดหวัง การปฏิบัติตามกฎของความประหลาดใจน้อยที่สุดคุณควรพยายามให้สิ่งที่พวกเขาคาดหวังแก่ผู้ใช้ของคุณเว้นแต่คุณจะมีเหตุผลที่น่าสนใจอย่างมาก
ตัวอย่างเช่นสมมติว่าเราต้องการแอตทริบิวต์ที่มีการป้องกันของวัตถุเป็นจำนวนเต็มระหว่าง 0 ถึง 100 และป้องกันการลบโดยมีข้อความที่เหมาะสมเพื่อแจ้งให้ผู้ใช้ทราบถึงการใช้งานที่เหมาะสม:
class Protective(object):
"""protected property demo"""
#
def __init__(self, start_protected_value=0):
self.protected_value = start_protected_value
#
@property
def protected_value(self):
return self._protected_value
#
@protected_value.setter
def protected_value(self, value):
if value != int(value):
raise TypeError("protected_value must be an integer")
if 0 <= value <= 100:
self._protected_value = int(value)
else:
raise ValueError("protected_value must be " +
"between 0 and 100 inclusive")
#
@protected_value.deleter
def protected_value(self):
raise AttributeError("do not delete, protected_value can be set to 0")
(โปรดทราบว่า__init__
หมายถึงself.protected_value
แต่อ้างถึงวิธีการของคุณสมบัติself._protected_value
นี่เป็นการ__init__
ใช้คุณสมบัติผ่าน API สาธารณะเพื่อให้มั่นใจว่าเป็น "ป้องกัน")
และการใช้งาน:
>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0
ใช่พวกเขาทำ .setter
และ.deleter
ทำสำเนาของคุณสมบัติดั้งเดิม สิ่งนี้ทำให้คลาสย่อยสามารถปรับเปลี่ยนพฤติกรรมได้อย่างเหมาะสมโดยไม่ต้องปรับเปลี่ยนพฤติกรรมในพาเรนต์
class Obj:
"""property demo"""
#
@property
def get_only(self):
return self._attribute
#
@get_only.setter
def get_or_set(self, value):
self._attribute = value
#
@get_or_set.deleter
def get_set_or_delete(self):
del self._attribute
ตอนนี้เพื่อให้ใช้งานได้คุณต้องใช้ชื่อที่เกี่ยวข้อง:
obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error
ฉันไม่แน่ใจว่าสิ่งนี้จะมีประโยชน์ แต่กรณีใช้คือถ้าคุณต้องการได้รับการตั้งค่าและ / หรือคุณสมบัติลบเท่านั้น อาจดีที่สุดที่จะติดกับคุณสมบัติเดียวกันความหมายมีชื่อเดียวกัน
เริ่มต้นด้วยคุณสมบัติง่าย ๆ
หากคุณต้องการฟังก์ชันการทำงานในภายหลังเกี่ยวกับการตั้งค่าการรับและการลบคุณสามารถเพิ่มได้ด้วยตัวตกแต่งคุณสมบัติ
หลีกเลี่ยงการใช้ชื่อฟังก์ชั่นset_...
และget_...
- นั่นคือคุณสมบัติที่มีไว้สำหรับ
__init__
วิธีหมายถึงself.protected_value
แต่ทะเยอทะยานและ setters self._protected_value
อ้างถึง คุณช่วยอธิบายการทำงานของมันได้มั้ย ฉันทดสอบโค้ดของคุณและใช้งานได้ตามปกติ - นี่ไม่ใช่การพิมพ์ผิด
__init__
มันจะ?
self.protected_value = start_protected_value
จริง ๆ แล้วกำลังเรียกฟังก์ชัน setter ฉันคิดว่ามันเป็นงานที่มอบหมาย
In [1]: class test(object):
def __init__(self):
self.pants = 'pants'
@property
def p(self):
return self.pants
@p.setter
def p(self, value):
self.pants = value * 2
....:
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20
การใช้@property
และ@attribute.setter
ช่วยให้คุณไม่เพียง แต่ใช้วิธี "pythonic" แต่ยังตรวจสอบความถูกต้องของคุณลักษณะทั้งในขณะที่สร้างวัตถุและเมื่อทำการเปลี่ยนแปลง
class Person(object):
def __init__(self, p_name=None):
self.name = p_name
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if type(new_name) == str: #type checking for name property
self._name = new_name
else:
raise Exception("Invalid value for name")
โดยสิ่งนี้คุณ 'ซ่อน' _name
แอตทริบิวต์จากนักพัฒนาไคลเอนต์และทำการตรวจสอบกับชนิดคุณสมบัติชื่อ โปรดทราบว่าโดยทำตามวิธีนี้แม้ในระหว่างการเริ่มต้น setter จะถูกเรียก ดังนั้น:
p = Person(12)
จะนำไปสู่:
Exception: Invalid value for name
แต่:
>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception
ตรวจสอบมัณฑนากร@property
คุณสามารถใช้ accessors / mutators (เช่น@attr.setter
และ@property
) หรือไม่ แต่สิ่งที่สำคัญที่สุดคือให้สอดคล้อง!
หากคุณใช้@property
เพื่อเข้าถึงแอตทริบิวต์เช่น
class myClass:
def __init__(a):
self._a = a
@property
def a(self):
return self._a
ใช้เพื่อเข้าถึงทุก ๆคุณสมบัติ* ! มันจะเป็นการปฏิบัติที่ไม่ดีในการเข้าถึงคุณลักษณะบางอย่างโดยใช้@property
และปล่อยให้คุณสมบัติอื่น ๆสาธารณะ (เช่นชื่อที่ไม่มีเครื่องหมายขีดเส้นใต้) โดยไม่มีการเข้าถึงเช่นไม่ทำ
class myClass:
def __init__(a, b):
self.a = a
self.b = b
@property
def a(self):
return self.a
โปรดทราบว่าself.b
ไม่มีการเข้าถึงอย่างชัดเจนที่นี่แม้ว่าจะเป็นสาธารณะ
ในทำนองเดียวกันกับsetters (หรือmutators ) อย่าลังเลที่จะใช้@attribute.setter
แต่ต้องสอดคล้อง! เมื่อคุณทำเช่น
class myClass:
def __init__(a, b):
self.a = a
self.b = b
@a.setter
def a(self, value):
return self.a = value
มันยากสำหรับฉันที่จะคาดเดาความตั้งใจของคุณ ในมือข้างหนึ่งคุณกำลังบอกว่าทั้งสองa
และb
เป็นสาธารณะ (ไม่มีขีดเส้นใต้นำในชื่อของพวกเขา) ดังนั้นฉันควรได้รับอนุญาตตามหลักวิชาในการเข้าถึง / กลายพันธุ์ (รับ / ชุด) ทั้งสอง แต่จากนั้นคุณระบุตัวแปลที่ชัดเจนเท่านั้นสำหรับa
ซึ่งบอกฉันว่าบางทีฉันไม่สามารถตั้งค่าb
ได้ เนื่องจากคุณได้ระบุ Mutator อย่างชัดเจนฉันไม่แน่ใจว่าการขาด accessor ( @property
) อย่างชัดเจนหมายความว่าฉันไม่ควรเข้าถึงตัวแปรเหล่านั้นหรือคุณเพียงแค่ประหยัดในการใช้@property
หมายความว่าฉันไม่ควรจะสามารถเข้าถึงอย่างใดอย่างหนึ่งของตัวแปรเหล่านั้นหรือคุณแค่ประหยัดในการใช้
* ข้อยกเว้นคือเมื่อคุณต้องการทำให้ตัวแปรบางตัวสามารถเข้าถึงได้หรือไม่แน่นอนแต่ไม่ใช่ทั้งสองอย่างหรือคุณต้องการดำเนินการตรรกะเพิ่มเติมเมื่อเข้าถึงหรือกลายพันธุ์ของแอททริบิวต์ นี่คือเมื่อฉันใช้เป็นการส่วนตัว@property
และ@attribute.setter
(ไม่เช่นนั้นจะไม่มี acessor / mutators ที่ชัดเจนสำหรับแอตทริบิวต์สาธารณะ)
สุดท้ายคำแนะนำ PEP8 และ Google Style Guide:
PEP8 การออกแบบเพื่อรับมรดกพูดว่า:
สำหรับแอ็ตทริบิวต์ข้อมูลพับลิกแบบง่ายที่สุดควรเปิดเผยเพียงชื่อแอ็ตทริบิวต์โดยไม่มีเมธอด accessor / mutator ซับซ้อนที่ดีที่สุดที่จะเปิดเผยเพียงชื่อแอตทริบิวต์โดยไม่ต้องเข้าถึงวิธีการที่ซับซ้อนโปรดทราบว่า Python จัดเตรียมเส้นทางที่ง่ายไปสู่การปรับปรุงในอนาคตหากคุณพบว่าแอตทริบิวต์ข้อมูลที่เรียบง่ายจำเป็นต้องมีพฤติกรรมการใช้งานที่เพิ่มขึ้น ในกรณีดังกล่าวให้ใช้คุณสมบัติเพื่อซ่อนการนำไปใช้งานด้านหลังไวยากรณ์การเข้าถึงแอ็ตทริบิวต์ข้อมูลแบบง่าย
ในทางตรงกันข้ามตามที่ระบุไว้ในคู่มือสไตล์ของ Google Python ภาษากฎ / คุณสมบัติคำแนะนำคือ:
ใช้คุณสมบัติในรหัสใหม่เพื่อเข้าถึงหรือตั้งค่าข้อมูลที่ปกติแล้วคุณจะใช้วิธีการเข้าถึงที่เรียบง่ายน้ำหนักเบาหรือตัวตั้งค่า คุณสมบัติควรสร้างด้วย
@property
มัณฑนากร
ข้อดีของวิธีนี้:
ความสามารถในการอ่านเพิ่มขึ้นโดยกำจัดการเรียกและตั้งค่าการเรียกใช้เมธอดเพื่อเข้าถึงแอ็ตทริบิวต์อย่างง่าย ช่วยให้การคำนวณขี้เกียจ ถือว่าเป็นวิธี Pythonic ในการรักษาส่วนต่อประสานของคลาส ในแง่ของประสิทธิภาพการอนุญาตให้คุณสมบัติข้ามต้องใช้วิธีการเข้าถึงแบบไม่สำคัญเมื่อการเข้าถึงตัวแปรโดยตรงนั้นสมเหตุสมผล นอกจากนี้ยังอนุญาตให้เพิ่มวิธีการเข้าถึงได้ในอนาคตโดยไม่ทำลายส่วนต่อประสาน
และข้อเสีย:
ต้องสืบทอดมาจาก
object
ใน Python 2 สามารถซ่อนผลข้างเคียงได้เหมือนกับการใช้งานเกินพิกัด อาจสร้างความสับสนให้กับคลาสย่อย
@property
การทำให้ส่วนที่เหลือใช้@property
ดูเหมือนการตัดสินใจที่ไม่ดี
@property
(เช่นดำเนินการตรรกะพิเศษบางอย่างก่อนส่งคืนแอตทริบิวต์) มิฉะนั้นแล้วทำไมคุณถึงต้องตกแต่งด้วยคุณลักษณะหนึ่งด้วย@propery
และไม่ใช่อย่างอื่น?
@property
เริ่มด้วยใช่ไหม? หากทะเยอทะยานของคุณreturn this._x
และตัวตั้งค่าของคุณthis._x = new_x
แล้วการใช้@property
เลยก็โง่นะ
@property
คือความสอดคล้อง"
คุณสามารถใช้วิธีมายากลและ__getattribute__
__setattr__
class MyClass:
def __init__(self, attrvalue):
self.myattr = attrvalue
def __getattribute__(self, attr):
if attr == "myattr":
#Getter for myattr
def __setattr__(self, attr):
if attr == "myattr":
#Setter for myattr
โปรดระวัง__getattr__
และ__getattribute__
ไม่เหมือนกัน __getattr__
ถูกเรียกใช้เฉพาะเมื่อไม่พบคุณลักษณะ