การแบ่งคลาสย่อย: เป็นไปได้หรือไม่ที่จะแทนที่คุณสมบัติด้วยแอตทริบิวต์ดั้งเดิม?


14

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

class Math_Set_Base:
    @property
    def size(self):
        return len(self.elements)

ดังนั้นคลาสย่อยจะสามารถนับองค์ประกอบโดยอัตโนมัติในตัวอย่างที่ค่อนข้างงี่เง่า

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self,*elements):
        self.elements = elements

Concrete_Math_Set(1,2,3).size
# 3

แต่ถ้าหากคลาสย่อยไม่ต้องการใช้ค่าเริ่มต้นนี้ สิ่งนี้ไม่ทำงาน:

import math

class Square_Integers_Below(Math_Set_Base):
    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

Square_Integers_Below(7)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute

ฉันรู้ว่ามีวิธีการแทนที่คุณสมบัติด้วยอสังหาริมทรัพย์ แต่ฉันต้องการหลีกเลี่ยงปัญหานั้น เพราะจุดประสงค์ของคลาสฐานคือการทำให้ชีวิตง่ายที่สุดเท่าที่จะเป็นไปได้สำหรับผู้ใช้ไม่ต้องเพิ่ม bloat โดยการกำหนด (จากมุมมองที่แคบของคลาสย่อย) วิธีการเข้าถึงที่ซับซ้อนและไม่จำเป็น

มันสามารถทำได้? ถ้าไม่ใช่ทางออกที่ดีที่สุดถัดไปคืออะไร

คำตอบ:


6

สถานที่ให้บริการเป็นตัวบ่งชี้ข้อมูลซึ่งจะมีความสำคัญมากกว่าแอตทริบิวต์ที่มีชื่อเดียวกัน คุณสามารถกำหนดบ่งไม่ใช่ข้อมูลที่ไม่ซ้ำกัน__get__()วิธีการ: แอตทริบิวต์เช่นจะมีความสำคัญมากกว่าบ่งไม่ใช่ข้อมูลที่มีชื่อเดียวกันดูเอกสาร ปัญหาที่นี่คือที่non_data_propertyกำหนดไว้ด้านล่างมีวัตถุประสงค์เพื่อการคำนวณเท่านั้น (คุณไม่สามารถกำหนด setter หรือ deleter) แต่ดูเหมือนว่าจะเป็นกรณีในตัวอย่างของคุณ

import math

class non_data_property:
    def __init__(self, fget):
        self.__doc__ = fget.__doc__
        self.fget = fget

    def __get__(self, obj, cls):
        if obj is None:
            return self
        return self.fget(obj)

class Math_Set_Base:
    @non_data_property
    def size(self, *elements):
        return len(self.elements)

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self, *elements):
        self.elements = elements


class Square_Integers_Below(Math_Set_Base):
    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1, 2, 3).size) # 3
print(Square_Integers_Below(1).size) # 1
print(Square_Integers_Below(4).size) # 2
print(Square_Integers_Below(9).size) # 3

อย่างไรก็ตามสิ่งนี้ถือว่าคุณมีสิทธิ์เข้าถึงคลาสพื้นฐานเพื่อทำการเปลี่ยนแปลงนี้


มันค่อนข้างใกล้เคียงกับความสมบูรณ์แบบ ดังนั้นหมายความว่า poperty แม้ว่าคุณจะกำหนดเพียงผู้ทะเยอทะยานก็เพิ่มตัวตั้งที่ไม่ทำอะไรเลยนอกจากบล็อกการบ้าน น่าสนใจ ฉันอาจจะยอมรับคำตอบนี้
Paul Panzer

ใช่ตามเอกสารคุณสมบัติเสมอกำหนดทั้งสามวิธีบ่ง ( __get__(), __set__()และ__delete__()) และพวกเขายกAttributeErrorถ้าคุณไม่ให้การทำงานใด ๆ สำหรับพวกเขา ดูเทียบเท่าหลามของการดำเนินงานของสถานที่
Arkelis

ตราบใดที่คุณไม่สนใจคุณสมบัติsetterหรือ__set__สิ่งใดสิ่งนี้จะได้ผล อย่างไรก็ตามฉันขอเตือนคุณว่านี่ยังหมายความว่าคุณสามารถเขียนทับคุณสมบัติได้อย่างง่ายดายไม่เพียง แต่ในระดับคลาส ( self.size = ...) แต่ยังอยู่ในระดับอินสแตนซ์ (เช่นConcrete_Math_Set(1, 2, 3).size = 10จะใช้ได้อย่างเท่าเทียมกัน) อาหารเพื่อความคิด :)
r.ook

และ Square_Integers_Below(9).size = 1ยังใช้ได้เพราะมันเป็นคุณสมบัติที่เรียบง่าย ในกรณีนี้ใช้ "ขนาด" โดยเฉพาะนี่อาจดูเหมือน akward (ใช้คุณสมบัติแทน) แต่ในกรณีทั่วไป "แทนที่เสาคำนวณได้อย่างง่ายดายในคลาสย่อย" มันอาจจะดีในบางกรณี คุณสามารถควบคุมการเข้าถึงแอททริบิวด้วย__setattr__()ได้
Arkelis

1
ฉันไม่เห็นด้วยที่อาจมีกรณีการใช้งานที่ถูกต้องสำหรับสิ่งเหล่านี้ฉันแค่อยากจะพูดถึงข้อแม้เพราะมันอาจทำให้ความพยายามในการแก้ไขข้อบกพร่องซับซ้อนขึ้นในอนาคต ตราบใดที่ทั้งคุณและ OP ตระหนักถึงความหมายแล้วทั้งหมดก็ใช้ได้
r.ook

10

นี่จะเป็นคำตอบที่ยืดยาวที่อาจจะให้บริการฟรีเท่านั้น ... แต่คำถามของคุณพาฉันไปที่หลุมกระต่ายดังนั้นฉันจึงต้องการแบ่งปันสิ่งที่ฉันค้นพบ (และความเจ็บปวด) เช่นกัน

ในที่สุดคุณอาจพบคำตอบนี้ไม่เป็นประโยชน์ต่อปัญหาที่แท้จริงของคุณ ที่จริงแล้วข้อสรุปของฉันคือ - ฉันจะไม่ทำสิ่งนี้เลย ต้องบอกว่าเบื้องหลังของข้อสรุปนี้อาจทำให้คุณเพลิดเพลินเล็กน้อยเนื่องจากคุณกำลังมองหารายละเอียดเพิ่มเติม


การแก้ไขความเข้าใจผิดบางประการ

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

class Foo:
    def __init__(self):
        self.name = 'Foo!'
        @property
        def inst_prop():
            return f'Retrieving {self.name}'
        self.inst_prop = inst_prop

inst_propในขณะที่เป็นproperty, มันเป็นคุณสมบัติตัวอย่าง:

>>> Foo.inst_prop
Traceback (most recent call last):
  File "<pyshell#60>", line 1, in <module>
    Foo.inst_prop
AttributeError: type object 'Foo' has no attribute 'inst_prop'
>>> Foo().inst_prop
<property object at 0x032B93F0>
>>> Foo().inst_prop.fget()
'Retrieving Foo!'

ทุกอย่างขึ้นอยู่กับที่คุณpropertyกำหนดไว้ตั้งแต่แรก หากคุณ@propertyกำหนดไว้ใน "ขอบเขต" ของคลาส (หรือจริง ๆnamespace) มันจะกลายเป็นแอตทริบิวต์ของคลาส ในตัวอย่างของฉันชั้นเรียนเองไม่ได้ตระหนักถึงสิ่งใดinst_propจนกระทั่งอินสแตนซ์ แน่นอนว่ามันไม่ได้มีประโยชน์มากเหมือนเป็นสถานที่ให้บริการเลย


แต่ก่อนอื่นเรามาพูดถึงความคิดเห็นของคุณเกี่ยวกับการแก้ปัญหาการสืบทอด ...

แล้วการถ่ายทอดทางพันธุกรรมมีผลอย่างไรต่อปัญหานี้? บทความต่อไปนี้ดำน้ำเล็กน้อยในหัวข้อและลำดับการแก้ไขเมธอดค่อนข้างเกี่ยวข้องแม้ว่าจะกล่าวถึงความกว้างของมรดกแทนที่จะเป็นความลึก

เมื่อรวมกับการค้นหาของเราการตั้งค่าด้านล่างนี้:

@property
def some_prop(self):
    return "Family property"

class Grandparent:
    culture = some_prop
    world_view = some_prop

class Parent(Grandparent):
    world_view = "Parent's new world_view"

class Child(Parent):
    def __init__(self):
        try:
            self.world_view = "Child's new world_view"
            self.culture = "Child's new culture"
        except AttributeError as exc:
            print(exc)
            self.__dict__['culture'] = "Child's desired new culture"

ลองนึกภาพว่าจะเกิดอะไรขึ้นเมื่อบรรทัดเหล่านี้ทำงาน:

print("Instantiating Child class...")
c = Child()
print(f'c.__dict__ is: {c.__dict__}')
print(f'Child.__dict__ is: {Child.__dict__}')
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')

ผลที่ได้คือ:

Instantiating Child class...
can't set attribute
c.__dict__ is: {'world_view': "Child's new world_view", 'culture': "Child's desired new culture"}
Child.__dict__ is: {'__module__': '__main__', '__init__': <function Child.__init__ at 0x0068ECD8>, '__doc__': None}
c.world_view is: Child's new world_view
Child.world_view is: Parent's new world_view
c.culture is: Family property
Child.culture is: <property object at 0x00694C00>

สังเกตว่า:

  1. self.world_viewสามารถนำไปใช้ในขณะที่self.cultureล้มเหลว
  2. cultureไม่มีอยู่ในChild.__dict__( mappingproxyของชั้นเรียนเพื่อไม่ให้สับสนกับอินสแตนซ์__dict__)
  3. แม้ว่าจะcultureมีอยู่ในc.__dict__ก็ไม่ได้อ้างอิง

คุณอาจจะสามารถเดาได้ว่าทำไม - world_viewถูกเขียนทับโดยParentชั้นเรียนว่าไม่ใช่สถานที่ให้บริการดังนั้นจึงChildสามารถเขียนทับได้เช่นกัน ขณะเดียวกันนับตั้งแต่cultureมีการสืบทอดมันมีอยู่เพียงภายในmappingproxyของGrandparent :

Grandparent.__dict__ is: {
    '__module__': '__main__', 
    'culture': <property object at 0x00694C00>, 
    'world_view': <property object at 0x00694C00>, 
    ...
}

หากคุณพยายามลบParent.culture:

>>> del Parent.culture
Traceback (most recent call last):
  File "<pyshell#67>", line 1, in <module>
    del Parent.culture
AttributeError: culture

Parentคุณจะสังเกตเห็นว่ามันไม่ได้มีอยู่สำหรับ Grandparent.cultureเนื่องจากวัตถุโดยตรงหมายกลับไป


แล้วคำสั่งแก้ปัญหาคืออะไร

ดังนั้นเราจึงสนใจที่จะสังเกตคำสั่งการแก้ปัญหาที่แท้จริงลองเอาออกParent.world_viewแทน:

del Parent.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

สงสัยว่าผลลัพธ์คืออะไร?

c.world_view is: Family property
Child.world_view is: <property object at 0x00694C00>

มันเปลี่ยนกลับไปเป็นของปู่ย่าตายายworld_view propertyแม้ว่าเราจะสามารถกำหนดได้สำเร็จself.world_viewก่อนหน้านี้! แต่ถ้าเราเปลี่ยนworld_viewระดับชั้นเรียนอย่างแรงเหมือนคำตอบอื่น ถ้าเราลบมันล่ะ? จะเกิดอะไรขึ้นถ้าเรากำหนดแอตทริบิวต์ class ปัจจุบันให้เป็นคุณสมบัติ

Child.world_view = "Child's independent world_view"
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

del c.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

Child.world_view = property(lambda self: "Child's own property")
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

ผลลัพธ์คือ:

# Creating Child's own world view
c.world_view is: Child's new world_view
Child.world_view is: Child's independent world_view

# Deleting Child instance's world view
c.world_view is: Child's independent world_view
Child.world_view is: Child's independent world_view

# Changing Child's world view to the property
c.world_view is: Child's own property
Child.world_view is: <property object at 0x020071B0>

สิ่งนี้น่าสนใจเพราะc.world_viewได้รับการกู้คืนสู่แอตทริบิวต์ของอินสแตนซ์ในขณะChild.world_viewที่เราได้รับมอบหมาย หลังจากลบแอ็ตทริบิวต์อินสแตนซ์แล้วมันจะเปลี่ยนเป็นแอตทริบิวต์คลาส และหลังจากที่กำหนดChild.world_viewให้กับคุณสมบัติอีกครั้งเราจะสูญเสียการเข้าถึงคุณลักษณะอินสแตนซ์ทันที

ดังนั้นเราสามารถคาดการณ์คำสั่งแก้ปัญหาต่อไปนี้ :

  1. หากมีคลาสของแอตทริบิวต์และเป็น a propertyให้เรียกคืนค่าผ่านทางgetterหรือfget(เพิ่มเติมในภายหลัง) คลาสปัจจุบันก่อนถึงคลาส Base ล่าสุด
  2. มิฉะนั้นหากมีแอตทริบิวต์ของอินสแตนซ์อยู่ให้ดึงค่าของคุณสมบัติของอินสแตนซ์
  3. มิฉะนั้นให้เรียกคืนpropertyแอ็ตทริบิวต์ที่ไม่ใช่คลาส คลาสปัจจุบันก่อนถึงคลาส Base ล่าสุด

ในกรณีนี้เราจะลบรูทproperty:

del Grandparent.culture
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')

ซึ่งจะช่วยให้:

c.culture is: Child's desired new culture
Traceback (most recent call last):
  File "<pyshell#74>", line 1, in <module>
    print(f'Child.culture is: {Child.culture}')
AttributeError: type object 'Child' has no attribute 'culture'

Ta-Dah! Childตอนนี้มีของตัวเองอยู่บนพื้นฐานของการแทรกเข้าไปในพลัง culture ไม่มีอยู่แน่นอนเนื่องจากไม่เคยกำหนดไว้ในหรือแอตทริบิวต์ class และถูกลบออกc.__dict__Child.cultureParentChildGrandparent


นี่เป็นสาเหตุของปัญหาของฉันหรือไม่

ที่จริงแล้วไม่มี ข้อผิดพลาดที่คุณได้รับซึ่งเราก็ยังคงสังเกตเมื่อกำหนดself.cultureเป็นที่แตกต่างกันโดยสิ้นเชิง แต่ลำดับการสืบทอดกำหนดฉากหลังให้กับคำตอบ - ซึ่งก็คือpropertyตัวมันเอง

นอกเหนือจากgetterวิธีการที่กล่าวถึงก่อนหน้านี้propertyยังมีเคล็ดลับเรียบร้อยขึ้นแขนเสื้อของมัน สิ่งที่เกี่ยวข้องมากที่สุดในกรณีนี้คือsetter, หรือfsetวิธีการ, ซึ่งถูกทริกเกอร์โดยself.culture = ...บรรทัด เนื่องจากคุณpropertyไม่ได้ใช้งานsetterหรือfgetฟังก์ชั่นใด ๆไพ ธ อนจึงไม่ทราบว่าจะต้องทำอะไรและจะAttributeErrorแทนที่ (เช่นcan't set attribute)

อย่างไรก็ตามหากคุณใช้setterวิธีการ:

@property
def some_prop(self):
    return "Family property"

@some_prop.setter
def some_prop(self, val):
    print(f"property setter is called!")
    # do something else...

เมื่อสร้างChildคลาสคุณจะได้รับ:

Instantiating Child class...
property setter is called!

แทนที่จะได้รับAttributeErrorคุณจะได้รับการเรียกsome_prop.setterวิธีการ ซึ่งช่วยให้คุณควบคุมวัตถุของคุณได้มากขึ้น ... จากการค้นพบครั้งก่อนของเราเรารู้ว่าเราต้องมีการเขียนทับแอตทริบิวต์ class ก่อนที่จะถึงคุณสมบัติ สิ่งนี้สามารถนำมาใช้ภายในคลาสฐานเป็นทริกเกอร์ นี่คือตัวอย่างใหม่:

class Grandparent:
    @property
    def culture(self):
        return "Family property"

    # add a setter method
    @culture.setter
    def culture(self, val):
        print('Fine, have your own culture')
        # overwrite the child class attribute
        type(self).culture = None
        self.culture = val

class Parent(Grandparent):
    pass

class Child(Parent):
    def __init__(self):
        self.culture = "I'm a millennial!"

c = Child()
print(c.culture)

ซึ่งผลลัพธ์ใน:

Fine, have your own culture
I'm a millennial!

TA-DAH! ตอนนี้คุณสามารถเขียนทับแอตทริบิวต์ของอินสแตนซ์ของคุณเองผ่านคุณสมบัติที่สืบทอดมา!


ดังนั้นการแก้ไขปัญหา?

... ไม่จริง ปัญหาของวิธีนี้คือตอนนี้คุณไม่มีsetterวิธีการที่เหมาะสม propertyมีหลายกรณีที่คุณไม่ต้องการค่าชุดบนเป็นของคุณ แต่ตอนนี้เมื่อใดก็ตามที่คุณตั้งค่าself.culture = ...ก็จะมักจะเขียนทับสิ่งที่ทำงานคุณกำหนดไว้ในgetter(ซึ่งในกรณีนี้มันเป็นเพียง@propertyส่วนหนึ่งห่อ. คุณสามารถเพิ่มมาตรการที่เหมาะสมยิ่งขึ้นแต่หรืออีกวิธีหนึ่งก็มักจะเกี่ยวข้องกับการมากกว่าเพียงแค่self.culture = ... เช่น:

class Grandparent:
    # ...
    @culture.setter
    def culture(self, val):
        if isinstance(val, tuple):
            if val[1]:
                print('Fine, have your own culture')
                type(self).culture = None
                self.culture = val[0]
        else:
            raise AttributeError("Oh no you don't")

# ...

class Child(Parent):
    def __init__(self):
        try:
            # Usual setter
            self.culture = "I'm a Gen X!"
        except AttributeError:
            # Trigger the overwrite condition
            self.culture = "I'm a Boomer!", True

มันwaaaaayซับซ้อนกว่าคำตอบอื่น ๆsize = Noneในระดับชั้นเรียน

คุณสามารถลองเขียนdescriptorของคุณเองแทนเพื่อจัดการกับ__get__และ__set__หรือวิธีการเพิ่มเติม แต่ในตอนท้ายของวันเมื่อself.cultureมีการอ้างอิง__get__จะถูกเรียกก่อนเสมอและเมื่อself.culture = ...มีการอ้างอิง__set__จะถูกเรียกใช้ก่อนเสมอ มันไม่มีทางที่จะไปได้ไกลเท่าที่ฉันได้ลอง


ประเด็นสำคัญของ IMO

ปัญหาที่ฉันเห็นที่นี่คือ - คุณไม่สามารถมีเค้กของคุณและกินมันเกินไป propertyมีความหมายเหมือนคำอธิบายถึงที่มีการเข้าถึงสะดวกเช่นวิธีการหรือgetattr setattrหากคุณต้องการวิธีการเหล่านี้เพื่อให้บรรลุวัตถุประสงค์ที่แตกต่างกันคุณแค่ขอปัญหา ฉันอาจจะคิดทบทวนวิธีการ:

  1. ฉันต้องการสิ่งpropertyนี้หรือไม่?
  2. วิธีการสามารถให้บริการฉันที่แตกต่างกันหรือไม่?
  3. หากฉันต้องการ a propertyมีเหตุผลใดที่ฉันจะต้องเขียนทับมัน?
  4. คลาสย่อยนั้นเป็นสมาชิกของครอบครัวเดียวกันpropertyหรือไม่หากไม่ได้ใช้
  5. หากฉันต้องการเขียนทับใด ๆ / ทั้งหมดpropertyวิธีการแยกจะให้บริการฉันดีกว่าเพียงแค่กำหนดใหม่เนื่องจากการกำหนดใหม่อาจทำให้โมฆะเป็นโมฆะpropertyได้หรือไม่

สำหรับจุดที่ 5 แนวทางของฉันจะมีoverwrite_prop()วิธีการในคลาสฐานที่เขียนทับแอตทริบิวต์คลาสปัจจุบันเพื่อที่propertyจะไม่ถูกทริกเกอร์อีกต่อไป:

class Grandparent:
    # ...
    def overwrite_props(self):
        # reassign class attributes
        type(self).size = None
        type(self).len = None
        # other properties, if necessary

# ...

# Usage
class Child(Parent):
    def __init__(self):
        self.overwrite_props()
        self.size = 5
        self.len = 10

ที่คุณสามารถดูในขณะที่ยังคงบิต contrived size = Noneมันเป็นอย่างน้อยอย่างชัดเจนกว่าที่เป็นความลับ ที่กล่าวว่าในที่สุดฉันจะไม่เขียนทับทรัพย์สินเลยและจะพิจารณาการออกแบบของฉันใหม่จากราก

ถ้าคุณทำมาไกลขนาดนี้ - ขอบคุณสำหรับการเดินทางครั้งนี้กับฉัน มันเป็นการออกกำลังกายสนุก ๆ


2
ว้าวขอบคุณมาก! มันจะใช้เวลาสักครู่ในการย่อยนี้ แต่ฉันคิดว่าฉันขอมัน
Paul Panzer

6

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

ผลที่ตามมาอย่างหนึ่งคือคุณสามารถมอบหมายใหม่ได้อย่างอิสระในระดับชั้นเรียน :

print(Math_Set_Base.size)
# <property object at 0x10776d6d0>

Math_Set_Base.size = 4
print(Math_Set_Base.size)
# 4

และเช่นเดียวกับชื่อระดับชั้นอื่น ๆ (เช่นวิธีการ) คุณสามารถแทนที่มันในคลาสย่อยโดยเพียงกำหนดอย่างชัดเจนแตกต่างกัน:

class Square_Integers_Below(Math_Set_Base):
    # explicitly define size at the class level to be literally anything other than a @property
    size = None

    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

print(Square_Integers_Below(4).size)  # 2
print(Square_Integers_Below.size)     # None

เมื่อเราสร้างอินสแตนซ์จริงตัวแปรอินสแตนซ์เพียงแค่เงาตัวแปรคลาสที่มีชื่อเดียวกัน โดยpropertyปกติวัตถุจะใช้ shenanigans บางอย่างเพื่อจัดการกระบวนการนี้ (เช่นการใช้ getters และ setters) แต่เมื่อชื่อระดับชั้นไม่ได้ถูกกำหนดเป็นคุณสมบัติไม่มีอะไรพิเศษเกิดขึ้นและดังนั้นจึงทำหน้าที่ตามที่คุณคาดหวังของตัวแปรอื่น ๆ


ขอบคุณที่เรียบง่ายสดชื่น มันหมายความว่าแม้ว่าจะไม่เคยมีคุณสมบัติใด ๆ ในชั้นเรียนของฉันและบรรพบุรุษของมันมันจะยังคงค้นหาต้นไม้ลำดับชั้นทั้งหมดก่อนเพื่อค้นหาชื่อระดับชั้นก่อนที่มันจะรบกวนจิตใจด้วย__dict__? นอกจากนี้ยังมีวิธีใดที่จะทำให้สิ่งนี้เป็นแบบอัตโนมัติหรือไม่ ใช่มันเป็นเพียงหนึ่งบรรทัด แต่มันเป็นสิ่งที่อ่านได้ยากหากคุณไม่คุ้นเคยกับรายละเอียดของ proprties ที่เต็มไปด้วยเลือด
Paul Panzer

@PaulPanzer ฉันไม่ได้ดูวิธีการที่อยู่เบื้องหลังนี้ดังนั้นฉันไม่สามารถให้คำตอบที่น่าพอใจได้ คุณสามารถลองไขมันออกมาจากซอร์สโค้ด cpythonถ้าคุณต้องการ สำหรับกระบวนการอัตโนมัติฉันไม่คิดว่าจะมีวิธีที่ดีในการทำสิ่งอื่นนอกเหนือจากการไม่ทำให้เป็นคุณสมบัติในตอนแรกหรือเพียงแค่เพิ่มความคิดเห็น / docstring เพื่อให้ผู้ที่อ่านรหัสของคุณรู้ว่าคุณกำลังทำอะไรอยู่ . มีบางอย่างที่เหมือนกัน# declare size to not be a @property
Green Cloak Guy

4

คุณไม่จำเป็นต้องได้รับมอบหมาย (ถึงsize) เลย sizeเป็นคุณสมบัติในคลาสพื้นฐานดังนั้นคุณสามารถแทนที่คุณสมบัตินั้นในคลาสย่อยได้:

class Math_Set_Base:
    @property
    def size(self):
        return len(self.elements)

    # size = property(lambda self: self.elements)


class Square_Integers_Below(Math_Set_Base):

    def __init__(self, cap):
        self._cap = cap

    @property
    def size(self):
        return int(math.sqrt(self._cap))

    # size = property(lambda self: int(math.sqrt(self._cap)))

คุณสามารถ (ไมโคร) ปรับสิ่งนี้ได้โดยทำการคำนวณสแควร์รูทล่วงหน้า:

class Square_Integers_Below(Math_Set_Base):

    def __init__(self, cap):
        self._size = int(math.sqrt(self._cap))

    @property
    def size(self):
        return self._size

นั่นเป็นจุดที่ดีเพียงแทนที่คุณสมบัติผู้ปกครองด้วยคุณสมบัติลูก! +1
r.ook

นี่เป็นวิธีที่จะทำสิ่งที่ตรงไปตรงมา แต่ฉันสนใจและขอเฉพาะวิธีที่ tio อนุญาตให้มีการแทนที่คุณสมบัติที่ไม่ใช่เพื่อที่จะกำหนดความไม่สะดวกของการเขียนคุณสมบัติช่วยจำที่มอบหมายง่ายจะทำ งาน.
Paul Panzer

ฉันไม่แน่ใจว่าทำไมคุณต้องการทำให้โค้ดของคุณซับซ้อนเช่นนั้น สำหรับราคาเล็กน้อยในการรับรู้ว่าsizeเป็นคุณสมบัติและไม่ใช่แอตทริบิวต์ของอินสแตนซ์คุณไม่ต้องทำอะไรแฟนซี
chepner

กรณีใช้งานของฉันเป็นคลาสเด็กจำนวนมากที่มีคุณลักษณะมากมาย ไม่ต้องเขียน 5 บรรทัดแทนที่จะเป็นหนึ่งสำหรับทุกคนที่แสดงให้เห็นถึงความเพ้อฝันจำนวนหนึ่ง IMO
Paul Panzer

2

ดูเหมือนว่าคุณต้องการกำหนดsizeในชั้นเรียนของคุณ:

class Square_Integers_Below(Math_Set_Base):
    size = None

    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

อีกทางเลือกหนึ่งคือการจัดเก็บcapในชั้นเรียนของคุณและคำนวณด้วยการsizeกำหนดเป็นคุณสมบัติ (ซึ่งแทนที่คุณสมบัติของชั้นฐานsize)


2

ฉันขอแนะนำให้เพิ่ม setter ดังนี้:

class Math_Set_Base:
    @property
    def size(self):
        try:
            return self._size
        except:
            return len(self.elements)

    @size.setter
    def size(self, value):
        self._size = value

วิธีนี้คุณสามารถแทนที่.sizeคุณสมบัติเริ่มต้นเช่น:

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self,*elements):
        self.elements = elements

class Square_Integers_Below(Math_Set_Base):
    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1,2,3).size) # 3
print(Square_Integers_Below(7).size) # 2

1

นอกจากนี้คุณสามารถทำสิ่งต่อไป

class Math_Set_Base:
    _size = None

    def _size_call(self):
       return len(self.elements)

    @property
    def size(self):
        return  self._size if self._size is not None else self._size_call()

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self, *elements):
        self.elements = elements


class Square_Integers_Below(Math_Set_Base):
    def __init__(self, cap):
        self._size = int(math.sqrt(cap))

นั่นเป็นที่เรียบร้อย แต่คุณไม่ต้องการจริงๆ_sizeหรือ_size_callมี คุณสามารถอบในการเรียกใช้ฟังก์ชั่นภายในsizeเป็นเงื่อนไขและใช้try... except... ในการทดสอบ_sizeแทนการอ้างอิงชั้นพิเศษที่จะไม่ใช้ แม้ว่าฉันจะรู้สึกว่านี่เป็นความลับยิ่งกว่าการsize = Noneเขียนทับง่าย ๆตั้งแต่แรก
r.ook
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.