2011-07-11 33 views
11

Hãy xem ví dụ này về mẫu chiến lược bằng Python (được điều chỉnh từ ví dụ here). Trong trường hợp này, chiến lược thay thế là một hàm.Phương thức python tự động nhận 'tự' làm đối số đầu tiên như thế nào?

class StrategyExample(object): 
    def __init__(self, strategy=None) : 
     if strategy: 
      self.execute = strategy 

    def execute(*args): 
     # I know that the first argument for a method 
     # must be 'self'. This is just for the sake of 
     # demonstration 
     print locals() 

#alternate strategy is a function 
def alt_strategy(*args): 
    print locals() 

Đây là kết quả cho chiến lược mặc định.

>>> s0 = StrategyExample() 
>>> print s0 
<__main__.StrategyExample object at 0x100460d90> 
>>> s0.execute() 
{'args': (<__main__.StrategyExample object at 0x100460d90>,)} 

Trong ví dụ trên s0.execute là một phương pháp (không phải là một chức năng vani đồng bằng) và do đó số đầu tiên trong args, như mong đợi, là self.

Dưới đây là kết quả cho chiến lược thay thế.

>>> s1 = StrategyExample(alt_strategy) 
>>> s1.execute() 
{'args':()} 

Trong trường hợp này s1.execute là hàm vani thuần túy và như mong đợi, không nhận được self. Do đó args trống. Đợi tí! Làm sao chuyện này lại xảy ra?

Cả phương pháp và hàm đều được gọi theo cùng một kiểu. Phương thức tự động nhận được self làm đối số đầu tiên như thế nào? Và khi một phương pháp được thay thế bằng một hàm vani đơn giản, làm thế nào để nó không phải là nhận được đối số đầu tiên là self?

Sự khác biệt duy nhất mà tôi có thể tìm thấy là khi tôi kiểm tra các thuộc tính của chiến lược mặc định và chiến lược thay thế.

>>> print dir(s0.execute) 
['__cmp__', '__func__', '__self__', ...] 
>>> print dir(s1.execute) 
# does not have __self__ attribute 

Liệu sự hiện diện của __self__ thuộc tính trên s0.execute (phương pháp), nhưng thiếu nó trên s1.execute (chức năng) bằng cách nào đó giải thích cho sự khác biệt này trong hành vi? Làm thế nào để tất cả điều này làm việc trong nội bộ?

+1

Bạn có thể nghĩ của 'instance.method (arg)' như một cách viết tắt cho 'InstanceClass.method (Ví dụ , arg) '. Python cố gắng giữ cho mọi thứ đơn giản và rõ ràng nhất có thể, và tôi thấy đây là cách "minh bạch" để truy cập cá thể gọi là hàm –

Trả lời

3

Bạn có thể đọc giải thích đầy đủ here trong tham chiếu python, trong "Phương thức do người dùng xác định". Một lời giải thích ngắn hơn và dễ dàng hơn có thể được tìm thấy trong mô tả của hướng dẫn python method objects:

Nếu bạn vẫn không hiểu cách thức hoạt động của phương pháp, hãy xem xét việc thực hiện có thể làm rõ vấn đề. Khi một thuộc tính thể hiện được tham chiếu không phải là thuộc tính dữ liệu, lớp của nó được tìm kiếm. Nếu tên biểu thị thuộc tính lớp hợp lệ là đối tượng hàm, đối tượng phương thức được tạo bằng cách đóng gói (con trỏ đến) đối tượng thể hiện và đối tượng hàm chỉ tìm thấy cùng nhau trong đối tượng trừu tượng: đây là đối tượng phương thức. Khi đối tượng method được gọi với một danh sách đối số, một danh sách đối số mới được xây dựng từ đối tượng thể hiện và danh sách đối số, và đối tượng hàm được gọi với danh sách đối số mới này.

Về cơ bản, những gì xảy ra trong ví dụ của bạn này là:

  • một chức năng được gán cho một lớp (như sẽ xảy ra khi bạn khai báo một phương pháp bên trong cơ thể lớp) là ... một phương pháp.
    • Khi bạn truy cập phương thức đó thông qua lớp, ví dụ: StrategyExample.execute bạn nhận được một phương thức "không liên kết": nó không "biết" trường hợp nó "thuộc về", vì vậy nếu bạn muốn sử dụng nó trên một cá thể, bạn sẽ cần cung cấp cá thể làm đối số đầu tiên, ví dụ: StrategyExample.execute(s0)
    • Khi bạn truy cập phương thức thông qua cá thể, ví dụ: self.execute hoặc s0.execute, bạn nhận được một "phương thức ràng buộc": nó "biết" đối tượng đó "thuộc về" nào và sẽ được gọi với đối tượng là đối số đầu tiên.
  • một hàm mà bạn gán trực tiếp cho thuộc tính cá thể, như trong self.execute = strategy hoặc thậm chí s0.execute = strategy là ...chỉ là một chức năng đơn giản (trái với một phương pháp, nó không vượt qua thông qua các lớp)

Để có được ví dụ bạn để làm việc như nhau trong cả hai trường hợp:

  • hoặc bạn bật chức năng vào một phương pháp "thực": bạn có thể làm điều này với types.MethodType:

    self.execute = types.MethodType(strategy, self, StrategyExample) 
    

    (bạn nhiều hay ít nói với lớp rằng khi execute được hỏi ví dụ đặc biệt này, nó nên bật strategy thành một phương pháp ràng buộc)

  • hoặc - nếu chiến lược của bạn không thực sự cần quyền truy cập vào cá thể - bạn đi theo cách khác và xoay phương thức gốc execute thành phương pháp tĩnh (làm cho nó trở thành hàm bình thường một lần nữa: nó sẽ không được gọi với ví dụ như là đối số đầu tiên, vì vậy s0.execute() sẽ làm chính xác giống như StrategyExample.execute()):

    @staticmethod 
    def execute(*args): 
        print locals() 
    
+0

Câu trả lời rất rõ ràng và quá thẳng từ hướng dẫn Python !!! Cho thấy rằng tôi nên xem lại hướng dẫn một lần nữa để hấp thụ các chi tiết ngôn ngữ sâu sắc hơn mà tôi chỉ lướt qua một thời gian dài trở lại. Cảm ơn nhiều! –

1

Khi bạn làm self.execute = strategy bạn thiết lập các thuộc tính đến một phương pháp đơn giản:

>>> s = StrategyExample() 
>>> s.execute 
<bound method StrategyExample.execute of <__main__.StrategyExample object at 0x1dbbb50>> 
>>> s2 = StrategyExample(alt_strategy) 
>>> s2.execute 
<function alt_strategy at 0x1dc1848> 

Một phương pháp ràng buộc là một đối tượng có thể được gọi mà các cuộc gọi một chức năng thông qua một ví dụ như là đối số đầu tiên ngoài đi qua tất cả các tham số của nó được gọi với.

Xem: Python: Bind an Unbound Method?

+0

Thuật ngữ này là bạn có phương thức không liên kết. Nếu bạn/bind/phương thức (mà bạn có thể làm), nó sẽ một lần nữa ngầm truyền đối tượng đó làm đối số đầu tiên. – Arafangion

+0

Cảm ơn bạn đã liên kết về việc ràng buộc phương pháp không liên kết! –

6

Phương pháp này là một wrapper cho các chức năng, và gọi hàm với các trường hợp như là đối số đầu tiên. Có, nó chứa thuộc tính __self__ (cũng là im_self trong Python trước 3.x) theo dõi trường hợp nào được đính kèm. Tuy nhiên, việc thêm thuộc tính đó vào một hàm đơn giản sẽ không làm cho nó trở thành một phương thức; bạn cần phải thêm trình bao bọc. Here is how (mặc dù bạn có thể muốn sử dụng MethodType từ các module types để có được các nhà xây dựng, thay vì sử dụng type(some_obj.some_method).

Chức năng bọc, bằng cách này, có thể truy cập thông qua các __func__ (hoặc im_func) thuộc tính của phương pháp.

+0

Tương tự rất hữu ích: "Phương thức này là trình bao bọc cho hàm ...". Và cảm ơn về liên kết này! –

7

Bạn cần gán một phương thức không liên kết (ví dụ với tham số self) cho lớp hoặc phương thức được ràng buộc cho đối tượng.

Qua descriptor mechanism, bạn có thể thực hiện phương pháp ràng buộc riêng của bạn, nó cũng lý do tại sao nó hoạt động khi bạn gán chức năng (không ràng buộc) vào một lớp:

my_instance = MyClass()  
MyClass.my_method = my_method 

Khi gọi my_instance.my_method(), tra cứu sẽ không tìm thấy một mục nhập trên my_instance, đó là lý do tại sao cuối cùng nó sẽ làm điều này: MyClass.my_method.__get__(my_instance, MyClass) - đây là giao thức mô tả. Điều này sẽ trả về một phương thức mới được liên kết với my_instance, sau đó bạn thực thi bằng cách sử dụng toán tử () sau thuộc tính.

Điều này sẽ chia sẻ phương pháp trong tất cả các trường hợp của MyClass, bất kể khi nào chúng được tạo. Tuy nhiên, chúng có thể có "ẩn" phương thức trước khi bạn gán thuộc tính đó.

Nếu bạn chỉ muốn đối tượng cụ thể để có phương pháp đó, chỉ cần tạo ra một phương pháp ràng buộc bằng tay:

my_instance.my_method = my_method.__get__(my_instance, MyClass) 

Để cụ thể hơn về mô tả (một hướng dẫn viên), xem here.

+0

Thật buồn cười là câu trả lời duy nhất thực sự cho bạn thấy bạn có thể tạo ra một phương pháp bị ràng buộc và cố gắng giải thích nó không nhận được một ưu tiên duy nhất ... – phant0m

+0

Quyền truy cập mô tả là một phương pháp tạo các phương thức mà tôi không biết. Tôi đã luôn luôn thực hiện nó bằng cách sử dụng phương thức khởi tạo phương thức. – kindall

+0

@kindall: Bạn có ý giống như được hiển thị trong bài viết bạn đã liên kết tới không? Thay vì lấy nó thông qua 'type()', bạn có thể nhập nó: 'từ kiểu nhập MethodType' – phant0m

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