Phân lớp: Có thể ghi đè một thuộc tính bằng thuộc tính thông thường không?


14

Giả sử chúng ta muốn tạo ra một nhóm các lớp là các triển khai hoặc chuyên môn hóa khác nhau của một khái niệm bao quát. Giả sử có một triển khai mặc định hợp lý cho một số thuộc tính dẫn xuất. Chúng tôi muốn đưa nó vào một lớp cơ sở

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

Vì vậy, một lớp con sẽ tự động có thể đếm các phần tử của nó trong ví dụ khá ngớ ngẩn này

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

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

Nhưng nếu một lớp con không muốn sử dụng mặc định này thì sao? Điều này không hoạt động:

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

Tôi nhận thấy có nhiều cách để ghi đè một tài sản bằng một tài sản, nhưng tôi muốn tránh điều đó. Bởi vì mục đích của lớp cơ sở là làm cho cuộc sống của người dùng dễ dàng nhất có thể, không thêm sự phình to bằng cách áp đặt một (từ quan điểm hẹp của lớp con) phương thức truy cập phức tạp và không cần thiết.

Nó có thể được thực hiện? Nếu không những gì là giải pháp tốt nhất tiếp theo?

Câu trả lời:


6

Một thuộc tính là một mô tả dữ liệu được ưu tiên hơn một thuộc tính thể hiện có cùng tên. Bạn có thể định nghĩa một bộ mô tả không dữ liệu bằng một __get__()phương thức duy nhất : một thuộc tính thể hiện được ưu tiên hơn bộ mô tả không dữ liệu có cùng tên, xem các tài liệu . Vấn đề ở đây là non_data_propertyđịnh nghĩa dưới đây chỉ nhằm mục đích tính toán (bạn không thể định nghĩa một setter hoặc deleter) nhưng dường như đó là trường hợp trong ví dụ của bạn.

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

Tuy nhiên, điều này giả định rằng bạn có quyền truy cập vào lớp cơ sở để thực hiện thay đổi này.


Điều này khá gần với hoàn hảo. Vì vậy, điều này có nghĩa là một poperty ngay cả khi bạn chỉ xác định getter hoàn toàn thêm một setter không có gì ngoài việc chặn gán? Hấp dẫn. Có lẽ tôi sẽ chấp nhận câu trả lời này.
Paul Panzer

Vâng, theo các tài liệu, một tài sản luôn luôn xác định tất cả ba phương pháp mô tả ( __get__(), __set__()__delete__()) và họ nâng cao một AttributeErrornếu bạn không cung cấp bất kỳ chức năng cho họ. Xem Python tương đương với việc thực hiện thuộc tính .
Arkelis

Miễn là bạn không quan tâm đến setterhoặc __set__của tài sản, điều này sẽ hoạt động. Tuy nhiên tôi sẽ cảnh báo bạn rằng điều này cũng có nghĩa là bạn có thể dễ dàng ghi đè lên tài sản không chỉ ở cấp độ lớp ( self.size = ...) mà ngay cả ở cấp độ cá thể (ví dụ: Concrete_Math_Set(1, 2, 3).size = 10sẽ có giá trị như nhau). Thức ăn cho những suy nghĩ :)
r.ook

Square_Integers_Below(9).size = 1cũng hợp lệ vì nó là một thuộc tính đơn giản. Trong trường hợp sử dụng "kích thước" cụ thể này, điều này có vẻ bất thường (thay vào đó là sử dụng một thuộc tính), nhưng trong trường hợp chung "ghi đè một prop được tính toán dễ dàng trong lớp con", trong một số trường hợp có thể tốt. Bạn cũng có thể kiểm soát quyền truy cập thuộc tính __setattr__()nhưng nó có thể áp đảo.
Arkelis

1
Tôi không đồng ý có thể có trường hợp sử dụng hợp lệ cho những điều này, tôi chỉ muốn đề cập đến cảnh báo vì nó có thể làm phức tạp các nỗ lực gỡ lỗi trong tương lai. Miễn là cả bạn và OP đều nhận thức được ý nghĩa thì tất cả đều ổn.
r.ook

10

Đây sẽ là một câu trả lời dài dòng có thể chỉ phục vụ miễn phí ... nhưng câu hỏi của bạn đã đưa tôi đi xe xuống hố thỏ vì vậy tôi cũng muốn chia sẻ những phát hiện của mình (và nỗi đau).

Cuối cùng bạn có thể thấy câu trả lời này không hữu ích cho vấn đề thực tế của bạn. Trên thực tế, kết luận của tôi là - tôi hoàn toàn không làm điều này. Phải nói rằng, nền tảng cho kết luận này có thể giúp bạn giải trí một chút, vì bạn đang tìm kiếm thêm chi tiết.


Giải quyết một số quan niệm sai lầm

Câu trả lời đầu tiên, trong khi chính xác trong hầu hết các trường hợp, không phải lúc nào cũng đúng. Ví dụ, hãy xem xét lớp này:

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

inst_prop, trong khi là một property, là một thuộc tính thể hiện:

>>> 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!'

Tất cả phụ thuộc vào nơi bạn propertyđược xác định ở nơi đầu tiên. Nếu của bạn @propertyđược định nghĩa trong "phạm vi" lớp (hoặc thực sự, namespace), nó sẽ trở thành một thuộc tính lớp. Trong ví dụ của tôi, lớp học không nhận ra bất kỳ điều gì inst_propcho đến khi được khởi tạo. Tất nhiên, nó không hữu dụng như một tài sản ở đây.


Nhưng trước tiên, hãy giải quyết nhận xét của bạn về độ phân giải thừa kế ...

Vì vậy, làm thế nào chính xác yếu tố thừa kế vào vấn đề này? Bài viết tiếp theo này đi sâu một chút vào chủ đề và Thứ tự giải quyết phương pháp có phần liên quan, mặc dù nó thảo luận chủ yếu về Bề rộng của thừa kế thay vì Độ sâu.

Kết hợp với phát hiện của chúng tôi, đưa ra những thiết lập dưới đây:

@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"

Hãy tưởng tượng những gì xảy ra khi những dòng này được thực thi:

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}')

Kết quả là như vậy:

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>

Nhận thấy như thế nào:

  1. self.world_viewđã có thể được áp dụng, trong khi self.culturethất bại
  2. culturekhông tồn tại trong Child.__dict__( mappingproxylớp, không bị nhầm lẫn với thể hiện __dict__)
  3. Mặc dù culturetồn tại trong c.__dict__, nó không được tham chiếu.

Bạn có thể đoán tại sao - world_viewđược ghi đè bởi Parentlớp là một tài sản, vì vậy Childcũng có thể ghi đè lên nó. Trong khi đó, kể từ khi cultuređược thừa hưởng, nó chỉ tồn tại trong phạm vi mappingproxycủaGrandparent :

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

Trong thực tế nếu bạn cố gắng loại bỏ Parent.culture:

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

Bạn sẽ nhận thấy nó thậm chí không tồn tại Parent. Bởi vì đối tượng đang trực tiếp tham khảo lại Grandparent.culture.


Vậy, còn về Nghị quyết thì sao?

Vì vậy, chúng tôi quan tâm để quan sát Thứ tự Nghị quyết thực tế, Parent.world_viewthay vào đó , hãy thử xóa :

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

Tự hỏi kết quả là gì?

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

Nó được hoàn nguyên về Grandparent world_view property, mặc dù chúng tôi đã quản lý thành công để gán self.world_viewtrước đó! Nhưng điều gì sẽ xảy ra nếu chúng ta thay đổi mạnh mẽ world_viewở cấp độ lớp, giống như câu trả lời khác? Nếu chúng ta xóa nó thì sao? Điều gì xảy ra nếu chúng ta gán thuộc tính lớp hiện tại là một thuộc tính?

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}')

Kết quả là:

# 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>

Điều này rất thú vị vì c.world_viewđược khôi phục lại thuộc tính cá thể của nó, trong khi đó Child.world_viewlà thuộc tính chúng ta đã gán. Sau khi loại bỏ thuộc tính cá thể, nó trở lại thuộc tính lớp. Và sau khi gán lại Child.world_viewtài sản, chúng tôi ngay lập tức mất quyền truy cập vào thuộc tính cá thể.

Do đó, chúng ta có thể phỏng đoán thứ tự giải quyết sau :

  1. Nếu một thuộc tính lớp tồn tại nó là một property, hãy lấy giá trị của nó thông qua getterhoặc fget(nhiều hơn về điều này sau). Lớp hiện tại đầu tiên đến lớp Cơ sở cuối cùng.
  2. Mặt khác, nếu một thuộc tính thể hiện tồn tại, lấy giá trị thuộc tính thể hiện.
  3. Khác, lấy propertythuộc tính phi lớp. Lớp hiện tại đầu tiên đến lớp Cơ sở cuối cùng.

Trong trường hợp đó, hãy xóa gốc property:

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

Cung cấp cho:

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! Childbây giờ có riêng của họ culturedựa trên chèn mạnh mẽ vào c.__dict__. Child.culturedĩ nhiên không tồn tại vì nó không bao giờ được định nghĩa trong Parenthoặc Childthuộc tính lớp và Grandparentđã bị xóa.


Đây có phải là nguyên nhân gốc rễ của vấn đề của tôi?

Thật ra, không . Lỗi bạn gặp phải, mà chúng tôi vẫn đang quan sát khi gán self.culture, là hoàn toàn khác nhau . Nhưng thứ tự thừa kế đặt bối cảnh cho câu trả lời - đó là propertychính nó.

Bên cạnh getterphương pháp đã đề cập trước đó , propertycũng có một vài thủ thuật gọn gàng lên tay áo của nó. Liên quan nhất trong trường hợp này là setter, hoặc fsetphương thức, được kích hoạt bởiself.culture = ... dòng. Vì bạn propertykhông thực hiện bất kỳ chức năng setterhoặc fgetchức năng nào, python không biết phải làm gì và ném AttributeErrorthay vào đó (nghĩa là can't set attribute).

Tuy nhiên, nếu bạn thực hiện một setterphương pháp:

@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...

Khi bắt đầu Child lớp bạn sẽ nhận được:

Instantiating Child class...
property setter is called!

Thay vì nhận được một AttributeError, bây giờ bạn thực sự gọisome_prop.setter phương thức. Điều này cho phép bạn kiểm soát nhiều hơn đối tượng của mình ... với những phát hiện trước đây của chúng tôi, chúng tôi biết rằng chúng tôi cần phải có một thuộc tính lớp được ghi đè trước khi nó đến tài sản. Điều này có thể được thực hiện trong lớp cơ sở như là một kích hoạt. Đây là một ví dụ mới:

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)

Kết quả nào trong:

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

TA-DAH! Bây giờ bạn có thể ghi đè lên thuộc tính cá thể của riêng bạn trên một thuộc tính được kế thừa!


Vậy, vấn đề được giải quyết?

... Không hẳn vậy. Vấn đề với phương pháp này là, bây giờ bạn không thể có một setterphương pháp thích hợp . Có những trường hợp bạn muốn đặt giá trị của bạn property. Nhưng bây giờ, bất cứ khi nào bạn thiết lập, self.culture = ...nó sẽ luôn ghi đè bất kỳ chức năng nào bạn đã xác định trong getter(trong trường hợp này, thực sự chỉ là @propertyphần được bao bọc. Bạn có thể thêm vào các biện pháp nhiều sắc thái hơn, nhưng bằng cách này hay cách khác, nó sẽ luôn liên quan nhiều hơn chỉ self.culture = .... ví dụ:

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

Đó là waaaaay phức tạp hơn câu trả lời khác, size = Noneở cấp độ lớp.

Thay vào đó, bạn cũng có thể xem xét việc viết mô tả của riêng mình để xử lý __get____set__hoặc các phương pháp bổ sung. Nhưng vào cuối ngày, khi self.cultuređược tham chiếu, ý __get__chí sẽ luôn được kích hoạt trước và khi self.culture = ...được tham chiếu,__set__ sẽ luôn được kích hoạt trước. Không có xung quanh nó như tôi đã cố gắng.


Mấu chốt của vấn đề, IMO

Vấn đề tôi thấy ở đây là - bạn không thể có bánh của bạn và cũng ăn nó. propertycó nghĩa giống như một mô tả với truy cập thuận tiện từ các phương thức như getattrhoặcsetattr . Nếu bạn cũng muốn các phương pháp này đạt được mục đích khác, bạn chỉ cần yêu cầu sự cố. Tôi có lẽ sẽ suy nghĩ lại về cách tiếp cận:

  1. Tôi có thực sự cần một property cái này không?
  2. Một phương pháp có thể phục vụ tôi khác nhau?
  3. Nếu tôi cần một property, có bất kỳ lý do tôi sẽ cần phải ghi đè lên nó?
  4. Có phải lớp con thực sự thuộc về cùng một gia đình nếu những propertyđiều này không áp dụng?
  5. Nếu tôi cần ghi đè lên bất kỳ / tất cả property, liệu một phương thức riêng có phục vụ tôi tốt hơn là chỉ định lại, vì việc gán lại có thể vô tình làm mất hiệu lực propertycủa s không?

Đối với điểm 5, cách tiếp cận của tôi sẽ có một overwrite_prop()phương thức trong lớp cơ sở ghi đè lên thuộc tính lớp hiện tại để ý propertychí không còn được kích hoạt:

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

Như bạn có thể thấy, trong khi vẫn còn một chút giả định, nó ít nhất là rõ ràng hơn một mật mã size = None. Điều đó nói rằng, cuối cùng, tôi sẽ không ghi đè lên tài sản, và sẽ xem xét lại thiết kế của tôi từ gốc.

Nếu bạn đã đi xa đến thế - cảm ơn bạn đã cùng tôi đi bộ hành trình này. Đó là một bài tập nhỏ vui vẻ.


2
Wow, cảm ơn rất nhiều! Tôi sẽ mất một chút thời gian để tiêu hóa thứ này, nhưng tôi cho rằng tôi đã yêu cầu nó.
Paul Panzer

6

A @propertyđược định nghĩa ở cấp lớp. Tài liệu đi sâu vào chi tiết đầy đủ về cách thức hoạt động, nhưng đủ để nói rằng cài đặt hoặc nhận thuộc tính giải quyết bằng cách gọi một phương thức cụ thể. Tuy nhiên, propertyđối tượng quản lý quá trình này được định nghĩa với định nghĩa riêng của lớp. Đó là, nó được định nghĩa là một biến lớp nhưng hoạt động như một biến thể hiện.

Một hậu quả của việc này là bạn có thể gán lại nó một cách tự do ở cấp độ lớp :

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

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

Và cũng giống như bất kỳ tên cấp độ lớp nào khác (ví dụ: các phương thức), bạn có thể ghi đè nó trong một lớp con bằng cách chỉ định nghĩa rõ ràng khác nhau:

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

Khi chúng ta tạo một thể hiện thực tế, biến đối tượng chỉ đơn giản làm mờ biến lớp cùng tên. Đối propertytượng thường sử dụng một số shenanigans để thao tác quy trình này (tức là áp dụng getters và setters) nhưng khi tên cấp độ lớp không được định nghĩa là một thuộc tính, không có gì đặc biệt xảy ra và do đó, nó hoạt động như bạn mong đợi của bất kỳ biến nào khác.


Cảm ơn, điều đó thật đơn giản. Điều đó có nghĩa là ngay cả khi không bao giờ có bất kỳ thuộc tính nào trong lớp của tôi và tổ tiên của nó, thì đầu tiên nó vẫn sẽ tìm kiếm trong toàn bộ cây thừa kế cho một tên cấp độ trước khi nó thậm chí không nhìn vào __dict__? Ngoài ra, có cách nào để tự động hóa điều này? Vâng, đó chỉ là một dòng nhưng đó là loại rất khó đọc nếu bạn không quen thuộc với các chi tiết chính xác của các dự án, v.v.
Paul Panzer

@PaulPanzer Tôi chưa xem xét các thủ tục đằng sau việc này, vì vậy tôi không thể tự đưa ra câu trả lời thỏa đáng cho bạn. Bạn có thể thử giải mã nó từ mã nguồn cpython nếu bạn muốn. Đối với việc tự động hóa quy trình, tôi không nghĩ rằng có một cách tốt để làm điều đó ngoài việc không biến nó thành tài sản ngay từ đầu, hoặc chỉ thêm một nhận xét / chuỗi ký tự để cho những người đọc mã của bạn biết bạn đang làm gì . Một cái gì đó giống như# declare size to not be a @property
Green Cloak Guy

4

Bạn không cần chuyển nhượng (đến size) cả. sizelà một thuộc tính trong lớp cơ sở, vì vậy bạn có thể ghi đè lên thuộc tính đó trong lớp con:

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)))

Bạn có thể (vi) tối ưu hóa điều này bằng cách tính trước căn bậc hai:

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

Đó là một điểm tuyệt vời, chỉ cần ghi đè một tài sản cha mẹ bằng một tài sản con! +1
r.ook

Đây là một cách đơn giản để làm, nhưng tôi đã quan tâm và đã hỏi cụ thể về cách tio cho phép một tài sản không ghi đè để không gây ra sự bất tiện khi phải viết một tài sản ghi nhớ trong đó việc chuyển nhượng đơn giản sẽ làm việc làm.
Paul Panzer

Tôi không chắc tại sao bạn lại muốn làm phức tạp mã của mình như vậy. Đối với mức giá nhỏ để nhận ra đó sizelà một thuộc tính và không phải là một thuộc tính thể hiện, bạn không phải làm bất cứ điều gì lạ mắt.
chepner

Trường hợp sử dụng của tôi là rất nhiều lớp con với rất nhiều thuộc tính; không phải viết 5 dòng thay vì một dòng cho tất cả những người biện minh cho một lượng fancility nhất định, IMO.
Paul Panzer

2

Có vẻ như bạn muốn xác định sizetrong lớp của mình:

class Square_Integers_Below(Math_Set_Base):
    size = None

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

Một tùy chọn khác là lưu trữ captrong lớp của bạn và tính toán nó với sizeđịnh nghĩa là thuộc tính (ghi đè lên thuộc tính của lớp cơ sở size).


2

Tôi đề nghị thêm một setter như vậy:

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

Bằng cách này, bạn có thể ghi đè thuộc tính mặc định .sizenhư vậy:

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

Ngoài ra bạn có thể làm điều tiếp theo

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))

Đó là gọn gàng, nhưng bạn không thực sự cần _sizehoặc _size_callở đó. Bạn có thể đã thực hiện lệnh gọi hàm trong sizemột điều kiện và sử dụng try... except... để kiểm tra _sizethay vì lấy một tham chiếu lớp bổ sung sẽ không được sử dụng. Mặc dù vậy, tôi cảm thấy điều này thậm chí còn khó hiểu hơn so với việc size = Noneghi đè đơn giản ở nơi đầu tiên.
r.ook
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.