2017-09-15 18 views
7

Cố gắng hiểu oop trong python Tôi đi vào tình huống này khiến tôi khó hiểu, và tôi không thể tìm được lời giải thích thỏa đáng ... Tôi đã xây dựng một lớp có thể đếm được, có bộ đếm thuộc tính đếm số lượng phiên bản của lớp đã được khởi tạo. Tôi muốn truy cập này được tăng lên khi một lớp con (hoặc lớp con) của lớp đã cho được khởi tạo. Đây là triển khai của tôi:Thừa kế các biến lớp trong python

class Countable(object): 
    counter = 0 
    def __new__(cls, *args, **kwargs): 
     cls.increment_counter() 
     count(cls) 
     return object.__new__(cls, *args, **kwargs) 

    @classmethod 
    def increment_counter(cls): 
     cls.counter += 1 
     if cls.__base__ is not object: 
      cls.__base__.increment_counter() 

nơi count(cls) có mục đích gỡ lỗi và sau đó ghi lại.

Bây giờ, chúng ta hãy có một số lớp con điều này:

class A(Countable): 
    def __init__(self, a='a'): 
     self.a = a 

class B(Countable): 
    def __init__(self, b='b'): 
     self.b = b 

class B2(B): 
    def __init__(self, b2='b2'): 
     self.b2 = b2 

def count(cls): 
    print('@{:<5} Countables: {} As: {} Bs: {} B2s: {}' 
      ''.format(cls.__name__, Countable.counter, A.counter, B.counter, B2.counter)) 

khi tôi chạy một mã như sau:

a = A() 
a = A() 
a = A() 
b = B() 
b = B() 
a = A() 
b2 = B2() 
b2 = B2() 

tôi có được đầu ra sau, trông xa lạ với tôi:

@A  Countables: 1 As: 1 Bs: 1 B2s: 1 
@A  Countables: 2 As: 2 Bs: 2 B2s: 2 
@A  Countables: 3 As: 3 Bs: 3 B2s: 3 
@B  Countables: 4 As: 3 Bs: 4 B2s: 4 
@B  Countables: 5 As: 3 Bs: 5 B2s: 5 
@A  Countables: 6 As: 4 Bs: 5 B2s: 5 
@B2  Countables: 7 As: 4 Bs: 6 B2s: 6 
@B2  Countables: 8 As: 4 Bs: 7 B2s: 7 

Tại sao khi bắt đầu cả bộ đếm A và B đều tăng, mặc dù tôi chỉ gọi số A()? Và tại sao sau lần đầu tiên tôi gọi B() nó hoạt động như mong đợi?

Tôi đã phát hiện ra rằng có một hành vi như tôi muốn nó là đủ để thêm counter = 0 tại mỗi phân lớp, nhưng tôi không thể tìm thấy giải thích tại sao nó hoạt động như thế .... Cảm ơn bạn!


Tôi đã thêm vài bản in gỡ lỗi và để đơn giản hóa việc tạo lớp giới hạn thành hai. Điều này khá lạ:

>>> a = A() 
<class '__main__.A'> incrementing 
increment parent of <class '__main__.A'> as well 
<class '__main__.Countable'> incrementing 
@A  Counters: 1 As: 1 Bs: 1 B2s: 1 
>>> B.counter 
1 
>>> B.counter is A.counter 
True 
>>> b = B() 
<class '__main__.B'> incrementing 
increment parent of <class '__main__.B'> as well 
<class '__main__.Countable'> incrementing 
@B  Counters: 2 As: 1 Bs: 2 B2s: 2 
>>> B.counter is A.counter 
False 

Làm thế nào khi B() chưa được khởi tạo, nó trỏ đến cùng biến với A.counter nhưng sau khi tạo một đối tượng thì nó khác?

+0

Tôi không thể sao chép đầu ra của bạn. Đầu ra của tôi cho 'B2s' luôn giống như' Bs'. –

+0

Tôi đã chỉnh sửa câu hỏi của bạn với ví dụ đơn giản về vấn đề. Đây là một câu hỏi thú vị, hy vọng ai đó có thể làm sáng tỏ một chút về quy trình – Vinny

+0

@Rawing bạn là đúng, tôi dán kết quả của một ví dụ khác ... bây giờ tôi sửa nó! –

Trả lời

7

Sự cố với mã của bạn là các lớp con của Countable không có thuộc tính counter riêng. Chúng chỉ đơn thuần là kế thừa từ Countable, do đó, khi Countable 's counter thay đổi, nó cũng giống như thay đổi của lớp con counter.

Minimal dụ:

class Countable: 
    counter = 0 

class A(Countable): 
    pass # A does not have its own counter, it shares Countable's counter 

print(Countable.counter) # 0 
print(A.counter) # 0 

Countable.counter += 1 

print(Countable.counter) # 1 
print(A.counter) # 1 

Nếu Acounter thuộc tính riêng của mình, mọi thứ sẽ làm việc như mong đợi:

class Countable: 
    counter = 0 

class A(Countable): 
    counter = 0 # A has its own counter now 

print(Countable.counter) # 0 
print(A.counter) # 0 

Countable.counter += 1 

print(Countable.counter) # 1 
print(A.counter) # 0 

Nhưng nếu tất cả các lớp học chia sẻ cùng một counter, tại sao chúng ta thấy số khác nhau trong đầu ra? Đó là bởi vì bạn thực sự gắn thuộc tính counter đến lớp con sau, với mã này:

cls.counter += 1 

này tương đương với cls.counter = cls.counter + 1. Tuy nhiên, điều quan trọng là phải hiểu những gì cls.counter đề cập đến. Trong số cls.counter + 1, cls chưa có thuộc tính riêng counter, do đó, điều này thực sự cung cấp cho bạn lớp counter của lớp cha.Sau đó, giá trị đó được tăng lên và cls.counter = ... thêm thuộc tính counter vào lớp con chưa tồn tại cho đến bây giờ. Về cơ bản nó tương đương với việc viết cls.counter = cls.__base__.counter + 1. Bạn có thể thấy điều này trong hành động ở đây:

class Countable: 
    counter = 0 

class A(Countable): 
    pass 

# Does A have its own counter attribute? 
print('counter' in A.__dict__) # False 

A.counter += 1 

# Does A have its own counter attribute now? 
print('counter' in A.__dict__) # True 

Vì vậy, giải pháp cho vấn đề này là gì? Bạn cần số metaclass. Điều này mang lại cho bạn khả năng cung cấp cho mỗi thuộc tính Countable phân lớp thuộc tính counter của riêng nó khi được tạo:

class CountableMeta(type): 
    def __new__(cls, name, bases, attrs): 
     new_class = super(CountableMeta, cls).__new__(cls, name, bases, attrs) 
     new_class.counter = 0 # each class gets its own counter 
     return new_class 

class Countable: 
    __metaclass__ = CountableMeta 

# in python 3 Countable would be defined like this: 
# 
# class Countable(metaclass=CountableMeta): 
# pass 

class A(Countable): 
    pass 

print(Countable.counter) # 0 
print(A.counter) # 0 

Countable.counter += 1 

print(Countable.counter) # 1 
print(A.counter) # 0 
+1

Tôi chỉ cần thêm vào trong Python3.6 +, người ta cũng có thể sử dụng ['__init_subclass __() hook'] (https://docs.python.org/3/ reference/datamodel.html # customizing-class-creation) cho cùng một mục đích (thêm thuộc tính 'counter' vào mỗi lớp con). – plamut

+0

Hoặc (bằng Python 2.7.x + và 3.x) sử dụng trình trang trí lớp. Tuy nhiên, –

+0

, sau khi tạo đối tượng đầu tiên được hoàn thành ('a = A()'), tôi nhận được 'id (Countable.counter) == id (A.counter)'. Tại sao điều này xảy ra nếu việc gán tạo một biến lớp mới cho lớp A ?? –

Các vấn đề liên quan