2015-05-04 22 views
15

Trong bài nói chuyện của Raymond Hettinger "Super considered super speak" tại PyCon 2015, ông giải thích những lợi thế của việc sử dụng super trong Python trong nhiều ngữ cảnh thừa kế. Đây là một trong những ví dụ mà Raymond sử dụng trong buổi nói chuyện:Python tự và siêu thừa kế nhiều

class DoughFactory(object): 
    def get_dough(self): 
     return 'insecticide treated wheat dough' 


class Pizza(DoughFactory): 
    def order_pizza(self, *toppings): 
     print('Getting dough') 
     dough = super().get_dough() 
     print('Making pie with %s' % dough) 
     for topping in toppings: 
      print('Adding: %s' % topping) 


class OrganicDoughFactory(DoughFactory): 
    def get_dough(self): 
     return 'pure untreated wheat dough' 


class OrganicPizza(Pizza, OrganicDoughFactory): 
    pass 


if __name__ == '__main__': 
    OrganicPizza().order_pizza('Sausage', 'Mushroom') 

Ai đó trong khán giả asked Raymond về sự khác biệt của việc sử dụng self.get_dough() thay super().get_dough(). Tôi không hiểu rõ câu trả lời ngắn gọn của Raymond nhưng tôi đã mã hóa hai ví dụ này để thấy sự khác biệt. Kết quả là như nhau cho cả hai trường hợp:

Getting dough 
Making pie with pure untreated wheat dough 
Adding: Sausage 
Adding: Mushroom 

Nếu bạn thay đổi thứ tự lớp từ OrganicPizza(Pizza, OrganicDoughFactory) để OrganicPizza(OrganicDoughFactory, Pizza) sử dụng self.get_dough(), bạn sẽ nhận được kết quả này:

Making pie with pure untreated wheat dough

Tuy nhiên nếu bạn sử dụng super().get_dough() này là đầu ra:

Making pie with insecticide treated wheat dough

Tôi hiểu hành vi super() như Raymond đã giải thích. Nhưng hành vi mong đợi của self trong nhiều kịch bản thừa kế là gì?

+0

những gì - pizza kế thừa từ một nhà máy * bột *? Mã này là lạ. – user2357112

+0

'self' hoạt động giống như bất kỳ tham chiếu nào khác đối tượng. Nếu bạn làm 'p = OrganicPizza()', thì 'self.get_dough()' trong bất kỳ phương thức 'p' nào tương đương với' p.get_dough() ', bất kể ở đâu trong đồ thị kế thừa, sử dụng' self' xảy ra. Bạn có hiểu 'p.get_dough()' sẽ làm gì không? – user2357112

+0

@ user2357112 tại sao lại lạ? Chỉ là một ví dụ đơn giản mà Raymond đề xuất trong bài nói chuyện của anh ấy. –

Trả lời

12

Chỉ cần làm rõ, có bốn trường hợp, dựa trên việc thay đổi dòng thứ hai trong Pizza.order_pizza và định nghĩa của OrganicPizza:

  1. super(), (Pizza, OrganicDoughFactory)(bản gốc): 'Making pie with pure untreated wheat dough'
  2. self, (Pizza, OrganicDoughFactory): 'Making pie with pure untreated wheat dough'
  3. super(), (OrganicDoughFactory, Pizza): 'Making pie with insecticide treated wheat dough'
  4. self, (OrganicDoughFactory, Pizza): 'Making pie with pure untreated wheat dough'

Trường hợp 3. là một trong đó là bạn ngạc nhiên; nếu chúng ta chuyển đổi thứ tự thừa kế nhưng vẫn sử dụng super, chúng ta dường như kết thúc gọi số DoughFactory.get_dough gốc.


super thực sự là hỏi "đó là tiếp theo trong MRO (phương pháp để giải quyết)?" Vì vậy, OrganicPizza.mro() trông như thế nào?

  • (Pizza, OrganicDoughFactory): [<class '__main__.OrganicPizza'>, <class '__main__.Pizza'>, <class '__main__.OrganicDoughFactory'>, <class '__main__.DoughFactory'>, <class 'object'>]
  • (OrganicDoughFactory, Pizza): [<class '__main__.OrganicPizza'>, <class '__main__.OrganicDoughFactory'>, <class '__main__.Pizza'>, <class '__main__.DoughFactory'>, <class 'object'>]

Câu hỏi quan trọng ở đây là: mà đi kèm sauPizza? Khi chúng tôi gọi số super từ bên trong Pizza, đó là nơi Python sẽ tìm đến số get_dough *. Dành cho 1. và 2.đó là OrganicDoughFactory, vì vậy chúng tôi nhận được bột tinh khiết, không được xử lý, nhưng đối với 3. và 4. đó là nguyên bản, thuốc trừ sâu được xử lý DoughFactory.


Tại sao self khác nhau, sau đó? self luôn là ví dụ, vì vậy, Python sẽ tìm kiếm get_dough từ đầu MRO. Trong cả hai trường hợp, như được hiển thị ở trên, OrganicDoughFactory sớm hơn trong danh sách hơn DoughFactory, đó là lý do tại sao các phiên bản selfluôn luôn nhận bột không được xử lý; self.get_dough luôn phân giải thành OrganicDoughFactory.get_dough(self).


* Tôi nghĩ rằng đây là thực sự rõ ràng hơn theo hình thức hai đối số của super sử dụng trong Python 2.x, đó sẽ là super(Pizza, self).get_dough(); đối số đầu tiên là lớp để bỏ qua (tức là Python nhìn vào phần còn lại của MRO sau lớp đó).

+0

cảm ơn vì câu trả lời tuyệt vời .. – javed

2

Tôi muốn chia sẻ một vài quan sát về điều này.

Calling self.get_dough() có thể không thể thực hiện nếu bạn đang trọng phương pháp get_dough() của lớp cha mẹ, như ở đây:

class AbdullahStore(DoughFactory): 
    def get_dough(self): 
     return 'Abdullah`s special ' + super().get_dough() 

Tôi nghĩ rằng đây là một kịch bản thường xuyên trong thực tế. Nếu chúng tôi gọi trực tiếp DoughFactory.get_dough(self) thì hành vi sẽ được khắc phục. Lớp học phát sinh AbdullahStore sẽ phải ghi đè phương thức đầy đủ và không thể sử dụng lại 'giá trị gia tăng' của AbdullahStore. Mặt khác, nếu chúng ta sử dụng super.get_dough(self), điều này có một hương vị của một mẫu: trong bất kỳ lớp được thừa kế từ AbdullahStore, nói

class Kebab(AbdullahStore): 
    def order_kebab(self, sauce): 
     dough = self.get_dough() 
     print('Making kebab with %s and %s sauce' % (dough, sauce)) 

chúng ta có thể 'thuyết minh' get_dough() sử dụng trong AbdullahStore cách khác nhau, bằng cách chặn nó trong MRO như thế này

class OrganicKebab(Kebab, OrganicDoughFactory):pass 

Dưới đây là những gì nó làm:

Kebab().order_kebab('spicy') 
Making kebab with Abdullah`s special insecticide treated wheat dough and spicy sauce 
OrganicKebab().order_kebab('spicy') 
Making kebab with Abdullah`s special pure untreated wheat dough and spicy sauce 

Kể từ OrganicDoughFactory có một phụ huynh đơn DoughFactory, tôi được đảm bảo được chèn vào MRO ngay trước DoughFactory và do đó ghi đè các phương thức của nó cho tất cả các lớp trước trong MRO. Tôi đã mất một thời gian để hiểu thuật toán tuyến tính hóa C3 được sử dụng để xây dựng MRO. Vấn đề là hai quy tắc

children come before parents 
parents order is preserved 

từ tài liệu tham khảo này https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ vẫn chưa xác định thứ tự rõ ràng. Trong hệ thống phân cấp lớp

D->C->B->A 
\ /
    --E-- 

(loại A; lớp B (A); lớp C (B); lớp E (A); lớp D (C, E)) trong đó E sẽ được chèn vào trong MRO? Có phải DCBEA hay DCEBA không? Có lẽ trước khi người ta có thể tự tin trả lời các câu hỏi như thế này, nó không phải là một ý tưởng tốt để bắt đầu chèn super ở khắp mọi nơi. Tôi vẫn không hoàn toàn chắc chắn, nhưng tôi nghĩ C3 tuyến tính hóa, mà rõ ràng và sẽ chọn DCBEA đặt hàng trong ví dụ này, không cho phép chúng tôi thực hiện thủ thuật đánh chặn theo cách chúng tôi đã thực hiện nó một cách rõ ràng.

Bây giờ, tôi giả sử bạn có thể dự đoán kết quả của

class KebabNPizza(Kebab, OrganicPizza): pass 
KebabNPizza().order_kebab('hot') 

mà là một kebab cải thiện:

Making kebab with Abdullah`s special pure untreated wheat dough and hot sauce 

Nhưng nó đã có thể đưa bạn một thời gian để tính toán.

Khi lần đầu tiên tôi xem tài liệu super tài liệu https://docs.python.org/3.5/library/functions.html?highlight=super#super một yếu trước đây, đến từ nền C++, nó giống như "wow, đây là các quy tắc, nhưng cách này có thể hoạt động và không khai báo bạn ở phía sau?". Bây giờ tôi hiểu thêm về nó, nhưng vẫn miễn cưỡng chèn super ở khắp mọi nơi. Tôi nghĩ rằng hầu hết các codebase tôi đã thấy là làm nó chỉ vì super() là thuận tiện hơn để gõ hơn tên lớp cơ sở. Và điều này thậm chí không nói về việc sử dụng cực đoan của super() trong chuỗi các chức năng __init__. Những gì tôi quan sát trong thực tế là mọi người viết các nhà xây dựng với chữ ký thuận tiện cho lớp đó (và không phải là phổ dụng) và sử dụng super() để gọi những gì họ nghĩ là trình tạo lớp cơ sở của họ.