มัณฑนากร @property ทำงานอย่างไร


980

ฉันต้องการที่จะเข้าใจว่าฟังก์ชั่นในตัวทำงานpropertyอย่างไร สิ่งที่ทำให้ฉันสับสนคือpropertyสามารถใช้เป็นมัณฑนากรได้ แต่ใช้อาร์กิวเมนต์เมื่อใช้เป็นฟังก์ชันในตัวเท่านั้นไม่ใช่เมื่อใช้เป็นมัณฑนากร

ตัวอย่างนี้มาจากเอกสาร :

class C(object):
    def __init__(self):
        self._x = None

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

propertyข้อโต้แย้งของมีgetx, setx, delxและสตริง doc

ในรหัสด้านล่างpropertyใช้เป็นมัณฑนากร วัตถุของมันคือxฟังก์ชั่น แต่ในรหัสข้างต้นไม่มีสถานที่สำหรับฟังก์ชั่นวัตถุในการขัดแย้ง

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

และจะสร้างx.setterและx.deleterตกแต่งอย่างไร? ฉันสับสน.



3
propertyจริงๆแล้วเป็นคลาส (ไม่ใช่ฟังก์ชั่น) ถึงแม้ว่ามันอาจจะเรียก__init__()เมธอดเมื่อคุณสร้างวัตถุแน่นอน การใช้help(property)จากเครื่องเทอร์มินัลนั้นชาญฉลาด helpเป็นคลาสด้วยเหตุผลบางอย่าง
Brōtsyorfuzthrāx

ฉันคิดว่าลิงก์นี้เป็นตัวอย่างที่ดี: [property] ( journaldev.com/14893/python-property-decorator )
Sheng Bi

4
@Shule เธรด 2 ปี แต่ยัง: ทุกอย่างเป็นคลาส แม้กระทั่งชั้นเรียน
อาร์ทิมิสยังไม่ไว้วางใจ SE

2
นี่ก็ทำให้ฉันสับสนเช่นกัน ในที่สุดฉันก็พบบทความที่สามารถทำลายมันให้ฉันได้ ฉันหวังว่านี่จะช่วยคนอื่นได้ programiz.com/python-programming/propertyฉันไม่ได้เป็นส่วนหนึ่งของเว็บไซต์นี้ แต่อย่างใด
jjwdesign

คำตอบ:


1008

property()ฟังก์ชันส่งกลับพิเศษวัตถุบ่ง :

>>> property()
<property object at 0x10ff07940>

มันเป็นวัตถุที่มีวิธีการพิเศษ :

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

ทำหน้าที่เป็นผู้ตกแต่งเช่นกัน พวกเขากลับวัตถุทรัพย์สินใหม่:

>>> property().getter(None)
<property object at 0x10ff079f0>

นั่นคือสำเนาของวัตถุเก่า แต่มีการแทนที่หนึ่งในฟังก์ชัน

โปรดจำไว้ว่า@decoratorไวยากรณ์เป็นเพียงน้ำตาลประโยค ไวยากรณ์:

@property
def foo(self): return self._foo

จริงๆหมายถึงสิ่งเดียวกัน

def foo(self): return self._foo
foo = property(foo)

ดังนั้นfooฟังก์ชั่นจะถูกแทนที่ด้วยproperty(foo)ซึ่งเราเห็นด้านบนเป็นวัตถุพิเศษ จากนั้นเมื่อคุณใช้@foo.setter()สิ่งที่คุณกำลังทำคือเรียกproperty().setterวิธีการที่ฉันแสดงให้คุณเห็นด้านบนซึ่งส่งคืนสำเนาของคุณสมบัติใหม่ แต่คราวนี้ด้วยฟังก์ชั่น setter แทนที่ด้วยวิธีการตกแต่ง

ลำดับต่อไปนี้ยังสร้างคุณสมบัติแบบเต็มโดยใช้วิธีการตกแต่งภายในเหล่านั้น

ครั้งแรกที่เราสร้างฟังก์ชั่นและpropertyวัตถุที่มีเพียงแค่ทะเยอทะยาน:

>>> def getter(self): print('Get!')
... 
>>> def setter(self, value): print('Set to {!r}!'.format(value))
... 
>>> def deleter(self): print('Delete!')
... 
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

ต่อไปเราจะใช้.setter()วิธีการเพิ่ม setter:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

สุดท้ายเราเพิ่ม deleter ด้วย.deleter()วิธีการ:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

สุดท้าย แต่ไม่น้อยที่propertyวัตถุที่ทำหน้าที่เป็นวัตถุบ่งจึงมี.__get__(), .__set__()และ.__delete__()วิธีการในการขอเข้าสู่แอตทริบิวต์อินสแตนซ์ที่ได้รับการตั้งค่าและการลบ:

>>> class Foo: pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

Descriptor Howto รวมถึงการใช้งานตัวอย่างของPython อย่างแท้จริงproperty() :

class Property:
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

10
ดีมาก. คุณสามารถเพิ่มความจริงที่ว่าหลังจากที่Foo.prop = propคุณสามารถทำFoo().prop = 5; pront Foo().prop; del Foo().propกับผลลัพธ์ที่ต้องการ
glglgl

12
วัตถุวิธีการถูกสร้างขึ้นอย่างรวดเร็วและสามารถนำตำแหน่งหน่วยความจำเดียวกันกลับมาใช้ได้หากมี
Martijn Pieters

1
@MarkusMeskanen: ฉันค่อนข้างใช้type()เป็นการเข้าถึงแอตทริบิวต์ dunder และวิธีการที่มีวัตถุประสงค์เพื่อใช้เป็นจุดส่วนขยายโดยฟังก์ชันมาตรฐานและตัวดำเนินการ
Martijn Pieters

2
@MarkusMeskanen: เนื่องจากวัตถุนั้นไม่เปลี่ยนรูปและหากคุณกลายพันธุ์ในสถานที่คุณไม่สามารถระบุได้ในคลาสย่อย
Martijn Pieters

5
@MarkusMeskanen: ดูPython แทนที่ getter โดยไม่ต้อง setter ; หาก@human.name.getterมีการเปลี่ยนแปลงpropertyวัตถุในสถานที่แทนที่จะกลับมาใหม่human.nameแอตทริบิวต์จะมีการเปลี่ยนแปลงการเปลี่ยนพฤติกรรมของซูเปอร์คลาสนั้น
Martijn Pieters

201

เอกสารอธิบายว่าเป็นเพียงทางลัดสำหรับการสร้างคุณสมบัติแบบอ่านอย่างเดียว ดังนั้น

@property
def x(self):
    return self._x

เทียบเท่ากับ

def getx(self):
    return self._x
x = property(getx)

19
บริบทที่สมบูรณ์ (คำตอบที่ถูกโหวตมากที่สุด) นั้นดี แต่คำตอบนี้มีประโยชน์สำหรับการหาสาเหตุที่คนอื่นใช้@propertyเป็นมัณฑนากรในชั้นเรียนของพวกเขา
ijoseph

1
@ คุณสมบัติยังสามารถใช้เมื่อคุณต้องการเพิ่มคุณลักษณะให้กับคลาสและต้องการรักษาความเข้ากันได้กับวัตถุที่สร้างไว้ก่อนหน้าของคลาสนั้น (เช่นที่อาจถูกบันทึกในไฟล์ดอง)
AndyP

111

นี่คือตัวอย่างเล็กน้อยของวิธี@propertyการใช้งาน:

class Thing:
    def __init__(self, my_word):
        self._word = my_word 
    @property
    def word(self):
        return self._word

>>> print( Thing('ok').word )
'ok'

มิฉะนั้นwordจะเป็นวิธีการแทนคุณสมบัติ

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'

1
ตัวอย่างนี้จะดูอย่างไรถ้าต้องการคำนิยามฟังก์ชัน () ฟังก์ชัน / คุณสมบัติในinit ?
JJ

5
ใครช่วยอธิบายself.word = my_wordprint( Thing('ok').word ) = 'ok'
หน่อยได้ไหม

1
@SilverSlash นี่เป็นเพียงตัวอย่างง่ายๆจริงกรณีการใช้งานจะเกี่ยวข้องกับวิธีการที่ซับซ้อนมากขึ้น
AlexG

คุณช่วยอธิบายฉันหน่อยได้ไหมว่าการพิมพ์Thing('ok').wordเรียกใช้ฟังก์ชันภายใน ณ รันไทม์ได้อย่างไร
Vicrobot

83

ส่วนแรกนั้นง่าย:

@property
def x(self): ...

เป็นเช่นเดียวกับ

def x(self): ...
x = property(x)
  • ซึ่งในทางกลับกันเป็นไวยากรณ์ที่ง่ายขึ้นสำหรับการสร้างpropertyด้วยเพียงแค่ทะเยอทะยาน

ขั้นตอนต่อไปคือการขยายคุณสมบัตินี้ด้วย setter และ deleter และสิ่งนี้เกิดขึ้นกับวิธีการที่เหมาะสม:

@x.setter
def x(self, value): ...

ส่งคืนคุณสมบัติใหม่ซึ่งสืบทอดทุกสิ่งจากตัวเก่าxพร้อมตัวกำหนด

x.deleter ทำงานในลักษณะเดียวกัน


49

ต่อไปนี้:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

เหมือนกับ:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

เหมือนกับ:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

เหมือนกับ:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

    def _x_set(self, value):
        self._x = value
    x = x.setter(_x_set)

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

ซึ่งเหมือนกับ:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

4
ตัวอย่างรหัสแรกและสุดท้ายเหมือนกัน (คำต่อคำ)
Adomas Baliuka

47

ด้านล่างเป็นอีกตัวอย่างของวิธีการที่@propertyสามารถช่วยเมื่อมีการ refactor รหัสซึ่งนำมาจากที่นี่ (ฉันสรุปได้เฉพาะด้านล่าง):

ลองนึกภาพคุณสร้างชั้นเรียนMoneyเช่นนี้:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

และผู้ใช้สร้างห้องสมุดขึ้นอยู่กับชั้นนี้ที่เขา / เธอใช้เช่น

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

ตอนนี้สมมติว่าคุณตัดสินใจที่จะเปลี่ยนMoneyชั้นเรียนของคุณและกำจัดdollarsและcentsคุณสมบัติ แต่ตัดสินใจที่จะติดตามจำนวนเซ็นต์ทั้งหมด:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

หากผู้ใช้ที่กล่าวถึงข้างต้นพยายามเรียกใช้ไลบรารีของตนตามเดิม

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

มันจะส่งผลให้เกิดข้อผิดพลาด

AttributeError: วัตถุ 'เงิน' ไม่มีแอตทริบิวต์ 'ดอลลาร์'

นั่นหมายความว่าตอนนี้ทุกคนที่อาศัยMoneyชั้นเรียนดั้งเดิมของคุณจะต้องเปลี่ยนรหัสทุกบรรทัดที่dollarsและcentsใช้ซึ่งอาจเจ็บปวดมาก ... ดังนั้นสิ่งนี้จะหลีกเลี่ยงได้อย่างไร โดยใช้@property!

นั่นคือวิธี:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

ตอนนี้เราโทรจากห้องสมุดของเรา

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

มันจะทำงานได้ตามที่คาดหวังและเราไม่จำเป็นต้องเปลี่ยนรหัสบรรทัดเดียวในห้องสมุดของเรา! ในความเป็นจริงเราไม่จำเป็นต้องรู้ด้วยซ้ำว่าห้องสมุดที่เราพึ่งพานั้นเปลี่ยนแปลงไป

ยังใช้setterงานได้ดี:

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.

คุณสามารถใช้@propertyในคลาสนามธรรมได้เช่นกัน ฉันให้เป็นตัวอย่างที่น้อยที่สุดที่นี่


ข้อมูลสรุปของคุณนั้นดีมากตัวอย่างที่เว็บไซต์ใช้นั้นดูแปลก ๆ นิดหน่อย .. ผู้เริ่มต้นจะถาม .. ทำไมเราไม่ยึดติดกับมันself.dollar = dollars? เราได้ทำสิ่งต่างๆมากมายด้วย @property แต่ดูเหมือนว่าไม่มีการเพิ่มฟังก์ชั่นการแยก
Sheng Bi

1
@ShengBi: อย่าให้ความสำคัญกับตัวอย่างที่เกิดขึ้นจริง แต่เพิ่มเติมตามหลักการพื้นฐาน: ถ้า - ด้วยเหตุผลใดก็ตาม - คุณต้อง refactor code คุณสามารถทำได้โดยไม่กระทบกับรหัสของคนอื่น
Cleb

21

ฉันอ่านโพสต์ทั้งหมดที่นี่และรู้ว่าเราอาจต้องการตัวอย่างชีวิตจริง ทำไมเรามี @property ดังนั้นให้พิจารณาแอป Flask ที่คุณใช้ระบบการตรวจสอบ คุณประกาศผู้ใช้แบบจำลองในmodels.py:

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    ...

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

ในรหัสนี้เราได้ "ซ่อน" แอตทริบิวต์passwordโดยใช้@propertyซึ่งก่อให้เกิดAttributeErrorการยืนยันเมื่อคุณพยายามที่จะเข้าถึงได้โดยตรงในขณะที่เราใช้ @ property.setter password_hashการตั้งค่าตัวแปรเช่นที่เกิดขึ้นจริง

ในตอนนี้auth/views.pyเราสามารถยกตัวอย่างผู้ใช้ด้วย:

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

คุณลักษณะการแจ้งให้ทราบpasswordล่วงหน้าที่มาจากแบบฟอร์มการลงทะเบียนเมื่อผู้ใช้กรอกแบบฟอร์ม การยืนยันรหัสผ่านเกิดขึ้นที่ส่วนหน้าด้วยEqualTo('password', message='Passwords must match')(ในกรณีที่คุณสงสัย แต่เป็นหัวข้อที่แตกต่างกันที่เกี่ยวข้องกับรูปแบบขวด)

ฉันหวังว่าตัวอย่างนี้จะเป็นประโยชน์


18

จุดนี้ถูกลบล้างโดยคนจำนวนมากที่นั่น แต่ที่นี่เป็นจุดตรงที่ฉันกำลังค้นหา นี่คือสิ่งที่ฉันรู้สึกว่าเป็นสิ่งสำคัญที่จะต้องเริ่มด้วย @property decorator เช่น:-

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"

การเรียกใช้ฟังก์ชัน "get_config ()" จะทำงานเช่นนี้

util = UtilityMixin()
print(util.get_config)

หากคุณสังเกตเห็นว่าฉันไม่ได้ใช้วงเล็บ "()" สำหรับการเรียกใช้ฟังก์ชัน นี่เป็นสิ่งพื้นฐานที่ฉันค้นหาผู้ตกแต่ง @property เพื่อให้คุณสามารถใช้ฟังก์ชั่นของคุณเหมือนตัวแปร


1
จุดที่มีประโยชน์มากที่ช่วยให้แนวคิดรวบยอดนี้เป็นนามธรรม
Info5ek

18

เริ่มต้นด้วยการตกแต่ง Python

Python decorator เป็นฟังก์ชั่นที่ช่วยเพิ่มฟังก์ชันเพิ่มเติมให้กับฟังก์ชั่นที่กำหนดไว้แล้ว

ใน Python ทุกอย่างเป็นวัตถุ ฟังก์ชั่นใน Python เป็นวัตถุชั้นหนึ่งซึ่งหมายความว่าพวกเขาสามารถอ้างอิงโดยตัวแปรเพิ่มในรายการที่ส่งผ่านเป็นข้อโต้แย้งไปยังฟังก์ชั่นอื่น ๆ ฯลฯ

พิจารณาข้อมูลโค้ดต่อไปนี้

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

ที่นี่เราสามารถพูดได้ว่าฟังก์ชั่นมัณฑนากรปรับเปลี่ยนฟังก์ชั่น say_hello ของเราและเพิ่มบรรทัดพิเศษของรหัสในนั้น

Python ไวยากรณ์สำหรับมัณฑนากร

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

เอาเป็นว่าสรุปทุกอย่างให้ดีกว่าสถานการณ์สมมติ แต่ก่อนหน้านี้เรามาพูดถึงโอ๊ะโอเบื้องต้น

Getters และ setters ใช้ในการเขียนโปรแกรมเชิงวัตถุหลายภาษาเพื่อให้แน่ใจว่าหลักการของการห่อหุ้มข้อมูล (ถูกมองว่าเป็นการรวมข้อมูลด้วยวิธีการที่ทำงานกับข้อมูลเหล่านี้)

แน่นอนว่าวิธีการเหล่านี้เป็นตัวเรียกใช้เพื่อดึงข้อมูลและตัวตั้งค่าสำหรับการเปลี่ยนแปลงข้อมูล

ตามหลักการนี้แอตทริบิวต์ของคลาสนั้นถูกทำให้เป็นส่วนตัวเพื่อซ่อนและปกป้องพวกเขาจากรหัสอื่น

Yup, @propertyนั้นเป็นวิธีการแบบ pythonic ในการใช้ getters และ setters

Python มีแนวคิดที่ดีที่เรียกว่าคุณสมบัติซึ่งทำให้ชีวิตของโปรแกรมเมอร์เชิงวัตถุง่ายขึ้นมาก

ให้เราสมมติว่าคุณตัดสินใจที่จะเรียนที่สามารถเก็บอุณหภูมิในหน่วยองศาเซลเซียสได้

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

Refactored Code นี่คือวิธีที่เราจะประสบความสำเร็จกับคุณสมบัติ

ใน Python คุณสมบัติ () เป็นฟังก์ชันในตัวที่สร้างและส่งคืนวัตถุคุณสมบัติ

วัตถุคุณสมบัติมีสามวิธี, getter (), setter () และลบ ()

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

ที่นี่

temperature = property(get_temperature,set_temperature)

อาจถูกทำลายได้

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

ชี้ไปที่หมายเหตุ:

  • get_temperature ยังคงเป็นคุณสมบัติแทนวิธีการ

ตอนนี้คุณสามารถเข้าถึงค่าอุณหภูมิโดยการเขียน

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

เราสามารถดำเนินการต่อไปและไม่กำหนดชื่อget_temperatureและset_temperatureเนื่องจากไม่มีความจำเป็นและทำให้เนมสเปซคลาสสกปรก

วิธี pythonicที่จะจัดการกับปัญหาดังกล่าวคือการใช้@property

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

คะแนนที่ควรทราบ -

  1. วิธีการที่ใช้สำหรับรับค่านั้นถูกตกแต่งด้วย "@property"
  2. วิธีการที่จะทำหน้าที่เป็น setter นั้นถูกตกแต่งด้วย "@ temperature.setter" หากฟังก์ชั่นนั้นถูกเรียกว่า "x" เราจะต้องตกแต่งด้วย "@ x.setter"
  3. เราเขียนวิธีการ "สอง" ที่มีชื่อเดียวกันและจำนวนพารามิเตอร์ที่แตกต่างกัน "อุณหภูมิ def (ตัวเอง)" และ "อุณหภูมิ def (ตัวเอง, x)"

อย่างที่คุณเห็นรหัสนั้นดูสง่างามน้อยลงอย่างแน่นอน

ทีนี้เรามาพูดถึงฉากจริงสำหรับชีวิตจริง

สมมติว่าคุณได้ออกแบบคลาสดังนี้:

class OurClass:

    def __init__(self, a):
        self.x = a


y = OurClass(10)
print(y.x)

ทีนี้มาลองสมมติว่าคลาสของเราได้รับความนิยมในหมู่ลูกค้าและพวกเขาเริ่มใช้มันในโปรแกรมของพวกเขาพวกเขาทำการมอบหมายทุกอย่างให้กับวัตถุ

และวันหนึ่งเป็นเวรเป็นกรรมลูกค้าที่เชื่อถือได้มาหาเราและแนะนำว่า "x" ต้องมีค่าระหว่าง 0 ถึง 1,000 นี่เป็นสถานการณ์ที่น่ากลัวจริงๆ!

เนื่องจากคุณสมบัติเป็นเรื่องง่าย: เราสร้างรุ่นคุณสมบัติของ "x"

class OurClass:

    def __init__(self,x):
        self.x = x

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

นี่มันยอดเยี่ยมใช่มั้ย: คุณสามารถเริ่มต้นด้วยการติดตั้งที่ง่ายที่สุดเท่าที่จะเป็นไปได้และคุณสามารถโยกย้ายไปยังรุ่นคุณสมบัติได้ในภายหลังโดยไม่ต้องเปลี่ยนอินเตอร์เฟส ดังนั้นคุณสมบัติไม่ได้เป็นเพียงการแทนที่ตัวรับและตัวตั้งค่า!

คุณสามารถตรวจสอบการใช้งานได้ที่นี่


2
คลาส Celsius ของคุณกำลังจะถูกรีโหลดแบบไม่ จำกัด เมื่อตั้งค่า (ซึ่งหมายถึงการสร้างอินสแตนซ์)
Ted Petrou

1
@Ted Petrou ฉันไม่ได้รับคุณ มันจะคืนค่าไม่ จำกัด เมื่อตั้งค่าอย่างไร
Divyanshu Rawat

จริง ๆ แล้วยังไม่ชัดเจน ... ผู้คนถามว่าทำไม แต่ตัวอย่างไม่น่าเชื่อถือ ...
Bi Sheng

1
มันเป็นเพียงความคิดเห็นความเห็นส่วนตัวของฉัน คำตอบของคุณอาจดีจริงๆ ดังนั้นทิ้งไว้
Sheng Bi

1
เมื่อเทียบกับคำตอบที่ได้รับการโหวตสูงสุดคำตอบนี้ถูกออกแบบมาสำหรับมนุษย์ ขอบคุณ
Info5ek

6

propertyเป็นคลาสที่อยู่เบื้องหลัง@propertyมัณฑนากร

คุณสามารถตรวจสอบสิ่งนี้ได้เสมอ:

print(property) #<class 'property'>

ฉันเขียนตัวอย่างจากhelp(property)เพื่อแสดงให้เห็นว่า@propertyไวยากรณ์

class C:
    def __init__(self):
        self._x=None

    @property 
    def x(self):
        return self._x

    @x.setter 
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

c = C()
c.x="a"
print(c.x)

มีหน้าที่เหมือนกับproperty()ไวยากรณ์:

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

ไม่มีความแตกต่างในวิธีที่เราใช้คุณสมบัติตามที่คุณเห็น

เพื่อตอบคำถาม@propertyมัณฑนากรถูกนำไปใช้ผ่านpropertyชั้นเรียน


ดังนั้นคำถามคืออธิบายpropertyชั้นเรียนเล็กน้อย สายนี้:

prop = property(g,s,d)

เป็นการเริ่มต้น เราสามารถเขียนใหม่ได้เช่นนี้

prop = property(fget=g,fset=s,fdel=d)

ความหมายของfget, fsetและfdel:

 |    fget
 |      function to be used for getting an attribute value
 |    fset
 |      function to be used for setting an attribute value
 |    fdel
 |      function to be used for del'ing an attribute
 |    doc
 |      docstring

ภาพถัดไปแสดงสามภาพที่เรามีจากชั้นเรียนproperty:

ป้อนคำอธิบายรูปภาพที่นี่

__get__, __set__และ__delete__จะมีการจะแทนที่ นี่คือการใช้รูปแบบตัวอธิบายใน Python

โดยทั่วไปแล้ว descriptor เป็นคุณสมบัติของวัตถุที่มี "การผูกพฤติกรรม" ซึ่งการเข้าถึงคุณลักษณะนั้นถูกแทนที่โดยวิธีการในโปรโตคอล descriptor

นอกจากนี้เรายังสามารถใช้สถานที่ให้บริการsetter, getterและdeleterวิธีการที่จะผูกฟังก์ชั่นในทรัพย์สิน ตรวจสอบตัวอย่างต่อไป วิธีการs2ของการเรียนCจะตั้งค่าคุณสมบัติสองเท่า

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x


    x=property(g)
    x=x.setter(s)
    x=x.deleter(d)      


c = C()
c.x="a"
print(c.x) # outputs "a"

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"

1

สามารถประกาศคุณสมบัติได้สองวิธี

  • สร้างเมธอด getter, setter สำหรับแอ็ตทริบิวต์แล้วส่งค่าเหล่านี้เป็นอาร์กิวเมนต์ไปยังฟังก์ชันคุณสมบัติ
  • ใช้ตัวตกแต่ง@property

คุณสามารถมีลักษณะที่ไม่กี่ตัวอย่างผมได้เขียนเกี่ยวกับคุณสมบัติในหลาม


คุณสามารถอัปเดตคำตอบของคุณโดยบอกว่าทรัพย์สินเป็นชั้นเรียนได้หรือไม่
prosti

1

คำอธิบายที่ดีที่สุดมีอยู่ที่นี่: Python @Property อธิบาย - วิธีการใช้และเมื่อใด (ตัวอย่างเต็ม) โดย Selva Prabhakaran | โพสต์เมื่อวันที่ 5 พฤศจิกายน 2018

มันช่วยให้ฉันเข้าใจว่าทำไมไม่เพียงเท่านั้น

https://www.machinelearningplus.com/python/python-property/


0

นี่เป็นอีกตัวอย่าง:

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None


##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234

    return None


##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )

    ## Return the value - we are a getter afterall...
    return _value


##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )

    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value


##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x


##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )


    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'

##
##
##
_test = GetterSetterExample( )
print( _test )

## For some reason the deleter isn't being called...
del _test.x

โดยพื้นฐานแล้วเช่นเดียวกับตัวอย่าง C (วัตถุ) ยกเว้นฉันกำลังใช้ x แทน ... ฉันยังไม่เริ่มต้นใน __init - ... ดี .. ฉันทำ แต่มันสามารถลบออกได้เพราะ __x ถูกกำหนดเป็นส่วนหนึ่ง ของชั้น ....

ผลลัพธ์คือ:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

และถ้าฉันแสดงความคิดเห็น self.x = 1234 ในinitแล้วผลลัพธ์คือ:

[ Test Class ] Get x = None
[ x ] None

และถ้าฉันตั้งค่า _default = None เป็น _default = 0 ในฟังก์ชั่น getter (เนื่องจาก getters ทั้งหมดควรมีค่าเริ่มต้น แต่ไม่ได้ผ่านค่าคุณสมบัติจากสิ่งที่ฉันได้เห็นเพื่อให้คุณสามารถกำหนดได้ที่นี่และ มันไม่ได้เลวร้ายจริงๆเพราะคุณสามารถกำหนดค่าเริ่มต้นหนึ่งครั้งและใช้งานได้ทุกที่) เช่น: def x (self, _default = 0):

[ Test Class ] Get x = 0
[ x ] 0

หมายเหตุ: getter ลอจิกอยู่ที่นั่นเพียงเพื่อให้มีการจัดการค่าเพื่อให้แน่ใจว่ามันถูกจัดการโดย - เหมือนกันสำหรับคำสั่งการพิมพ์ ...

หมายเหตุ: ฉันคุ้นเคยกับ Lua และสามารถสร้างตัวช่วย 10+ แบบไดนามิกได้เมื่อฉันเรียกใช้ฟังก์ชันเดียวและฉันทำบางอย่างที่คล้ายกับ Python โดยไม่ใช้คุณสมบัติและมันใช้งานได้ในระดับหนึ่ง แต่ถึงแม้ว่าจะมีการสร้างฟังก์ชันก่อนหน้านี้ การใช้งานยังคงมีปัญหาอยู่หลายครั้งที่พวกมันถูกเรียกก่อนที่จะถูกสร้างขึ้นซึ่งแปลกเพราะมันไม่ได้เข้ารหัสแบบนั้น ... ฉันชอบความยืดหยุ่นของ Lua meta-tables และความจริงที่ว่าฉันสามารถใช้ setters / getters จริง แทนที่จะเข้าถึงตัวแปรโดยตรงเป็นหลัก ... ฉันชอบความรวดเร็วของบางสิ่งที่สามารถสร้างด้วย Python ได้ - เช่นโปรแกรม gui แม้ว่าหนึ่งฉันกำลังออกแบบอาจเป็นไปไม่ได้โดยไม่ต้องมีห้องสมุดเพิ่มเติมมากมาย - ถ้าฉันรหัสใน AutoHotkey ฉันสามารถเข้าถึงการเรียก dll ที่ฉันต้องการโดยตรงและสามารถทำได้ใน Java, C #, C ++,

หมายเหตุ: รหัสออกในฟอรัมนี้เสีย - ฉันต้องเพิ่มช่องว่างในส่วนแรกของรหัสเพื่อให้ทำงาน - เมื่อคัดลอก / วางให้แน่ใจว่าคุณแปลงช่องว่างทั้งหมดเป็นแท็บ .... ฉันใช้แท็บสำหรับ Python เพราะใน ไฟล์ที่มีขนาด 10,000 บรรทัดขนาดไฟล์สามารถเป็น 512KB ถึง 1MB พร้อมช่องว่างและ 100 ถึง 200KB พร้อมแท็บซึ่งเท่ากับความแตกต่างอย่างมากสำหรับขนาดไฟล์และลดเวลาในการประมวลผล ...

แท็บยังสามารถปรับได้ต่อผู้ใช้ - ดังนั้นหากคุณต้องการความกว้างของช่องว่าง 2, 4, 8 หรืออะไรก็ได้ที่คุณสามารถทำได้หมายความว่านักพัฒนาที่มีการขาดดุลสายตาควรพิจารณา

หมายเหตุ: ฟังก์ชั่นทั้งหมดที่กำหนดในชั้นเรียนไม่ได้ถูกเยื้องอย่างเหมาะสมเนื่องจากข้อผิดพลาดในซอฟต์แวร์ฟอรั่ม - ให้แน่ใจว่าคุณเยื้องมันถ้าคุณคัดลอก / วาง


-3

ข้อสังเกตหนึ่ง: สำหรับฉันสำหรับ Python 2.x @propertyไม่ทำงานตามที่โฆษณาไว้เมื่อฉันไม่ได้รับมรดกobject:

class A():
    pass

แต่ทำงานเมื่อ:

class A(object):
    pass

สำหรับ Python 3 ทำงานได้ตลอดเวลา


5
นั่นเป็นเพราะใน Python 2 คลาสที่ไม่ได้สืบทอดมาobjectเป็นคลาสแบบเก่าและคลาสแบบเก่าไม่สนับสนุนโปรโตคอล descriptor (ซึ่งเป็นสิ่งที่propertyใช้ในการทำงานอย่างที่มันทำ) ใน Python 3 คลาสแบบเก่าจะไม่มีอยู่อีกต่อไป ชั้นเรียนทั้งหมดเป็นสิ่งที่เราเรียกว่าชั้นเรียนแบบใหม่ใน Python 2
chepner
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.