Gọi lớp tĩnh trong lớp cơ thể?


159

Khi tôi cố gắng sử dụng một phương thức tĩnh từ bên trong phần thân của lớp và định nghĩa phương thức tĩnh bằng cách sử dụng hàm dựng sẵn staticmethodlàm trang trí, như thế này:

class Klass(object):

    @staticmethod  # use as decorator
    def _stat_func():
        return 42

    _ANS = _stat_func()  # call the staticmethod

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

Tôi nhận được lỗi sau đây:

Traceback (most recent call last):<br>
  File "call_staticmethod.py", line 1, in <module>
    class Klass(object): 
  File "call_staticmethod.py", line 7, in Klass
    _ANS = _stat_func() 
  TypeError: 'staticmethod' object is not callable

Tôi hiểu lý do tại sao điều này xảy ra (ràng buộc mô tả) và có thể làm việc xung quanh nó bằng cách chuyển đổi thủ công _stat_func()thành tĩnh sau khi sử dụng lần cuối, như vậy:

class Klass(object):

    def _stat_func():
        return 42

    _ANS = _stat_func()  # use the non-staticmethod version

    _stat_func = staticmethod(_stat_func)  # convert function to a static method

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

Vì vậy, câu hỏi của tôi là:

Có cách nào tốt hơn, như trong sạch hơn hay nhiều "Pythonic" hơn, để thực hiện điều này?


4
Nếu bạn đang hỏi về Pythonicity, thì lời khuyên tiêu chuẩn là không nên sử dụng staticmethod. Chúng thường hữu ích hơn như các hàm cấp mô-đun, trong trường hợp đó, vấn đề của bạn không phải là vấn đề. classmethod, mặt khác ...
Benjamin Hodgson

1
@poorsod: Vâng, tôi biết về sự thay thế đó. Tuy nhiên, trong mã thực tế mà tôi gặp phải vấn đề này, làm cho hàm trở thành một phương thức tĩnh thay vì đặt nó ở cấp độ mô-đun có ý nghĩa hơn so với trong ví dụ đơn giản được sử dụng trong câu hỏi của tôi.
martineau

Câu trả lời:


179

staticmethodcác đối tượng rõ ràng có một __func__thuộc tính lưu trữ hàm thô ban đầu (có nghĩa là chúng phải). Vì vậy, điều này sẽ làm việc:

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    _ANS = stat_func.__func__()  # call the staticmethod

    def method(self):
        ret = Klass.stat_func()
        return ret

Như một bên, mặc dù tôi nghi ngờ rằng một đối tượng tĩnh có một loại thuộc tính lưu trữ chức năng ban đầu, tôi không có ý tưởng về các chi tiết cụ thể. Với tinh thần dạy ai đó câu cá hơn là cho chúng một con cá, đây là điều tôi đã làm để điều tra và tìm ra điều đó (một C & P từ phiên Python của tôi):

>>> class Foo(object):
...     @staticmethod
...     def foo():
...         return 3
...     global z
...     z = foo

>>> z
<staticmethod object at 0x0000000002E40558>
>>> Foo.foo
<function foo at 0x0000000002E3CBA8>
>>> dir(z)
['__class__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> z.__func__
<function foo at 0x0000000002E3CBA8>

Các loại đào tương tự trong một phiên tương tác ( dirrất hữu ích) thường có thể giải quyết các loại câu hỏi này rất nhanh.


Cập nhật tốt ... Tôi chỉ định hỏi làm thế nào bạn biết điều này vì tôi không thấy nó trong tài liệu - điều này khiến tôi hơi lo lắng về việc sử dụng nó bởi vì nó có thể là một "chi tiết triển khai".
martineau

Sau khi đọc thêm, tôi thấy đó __func__chỉ là một tên khác im_funcvà được thêm vào Py 2.6 cho tính tương thích chuyển tiếp của Python 3.
martineau

2
Tôi thấy, về mặt kỹ thuật nó không có giấy tờ trong bối cảnh này.
martineau

1
@AkshayHazari __func__Thuộc tính của một phương thức tĩnh giúp bạn tham chiếu đến hàm ban đầu, chính xác như thể bạn chưa bao giờ sử dụng trình staticmethodtrang trí. Vì vậy, nếu chức năng của bạn yêu cầu đối số, bạn sẽ phải vượt qua chúng khi gọi __func__. Thông báo lỗi bạn nghe có vẻ như bạn chưa đưa ra bất kỳ đối số nào. Nếu stat_funcví dụ trong bài viết này có hai đối số, bạn sẽ sử dụng_ANS = stat_func.__func__(arg1, arg2)
Ben

1
@AkshayHazari Tôi không chắc là tôi hiểu. Chúng có thể là các biến, chúng chỉ phải là các biến trong phạm vi: được định nghĩa trước đó trong cùng phạm vi nơi lớp được định nghĩa (thường là toàn cục) hoặc được định nghĩa trước đó trong phạm vi lớp ( stat_funcchính nó là một biến như vậy). Ý bạn là bạn không thể sử dụng các thuộc tính thể hiện của lớp bạn đang xác định? Đó là sự thật, nhưng không ngạc nhiên; chúng ta không một thể hiện của lớp, vì chúng ta vẫn đang xác định lớp! Nhưng dù sao, tôi chỉ có nghĩa là chúng là điểm tựa cho bất kỳ lý lẽ nào bạn muốn vượt qua; bạn có thể sử dụng nghĩa đen ở đó.
Ben

24

Đây là cách tôi thích:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

    def method(self):
        return self.__class__.stat_func() + self.__class__._ANS

Tôi thích giải pháp này hơn Klass.stat_func, vì nguyên tắc DRY . Nhắc nhở tôi về lý do tại sao có một cái mớisuper() trong Python 3 :)

Nhưng tôi đồng ý với những người khác, thường thì sự lựa chọn tốt nhất là xác định hàm cấp mô-đun.

Ví dụ với @staticmethodchức năng, đệ quy có thể trông không được tốt lắm (Bạn sẽ cần phá vỡ nguyên tắc DRY bằng cách gọi Klass.stat_funcbên trong Klass.stat_func). Đó là bởi vì bạn không có tham chiếu đến selfbên trong phương thức tĩnh. Với chức năng cấp mô-đun, mọi thứ sẽ ổn.


Mặc dù tôi đồng ý rằng việc sử dụng self.__class__.stat_func()trong các phương pháp thông thường có lợi thế (DRY và tất cả những thứ đó) so với việc sử dụng Klass.stat_func(), đó thực sự không phải là chủ đề của câu hỏi của tôi - thực tế tôi đã tránh sử dụng phương pháp trước đây để không làm mờ đi vấn đề không thống nhất.
martineau

1
Nó không thực sự là một vấn đề của DRY mỗi se. self.__class__là tốt hơn bởi vì nếu một lớp con ghi đè stat_func, thì Subclass.methodsẽ gọi lớp con stat_func. Nhưng thành thật mà nói, trong tình huống đó, tốt hơn là chỉ sử dụng một phương thức thực sự thay vì một phương thức tĩnh.
asmeker

@asmeker: Tôi không thể sử dụng một phương thức thực sự bởi vì chưa có trường hợp nào của lớp được tạo ra mà họ không thể vì lớp này chưa được xác định hoàn toàn.
martineau

Tôi muốn có một cách để gọi phương thức tĩnh cho lớp của tôi (được kế thừa; vì vậy cha mẹ thực sự có chức năng) và điều này có vẻ tốt nhất cho đến nay (và vẫn sẽ hoạt động nếu tôi ghi đè lên sau này).
Whitey04

11

Điều gì về việc tiêm thuộc tính lớp sau định nghĩa lớp?

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    def method(self):
        ret = Klass.stat_func()
        return ret

Klass._ANS = Klass.stat_func()  # inject the class attribute with static method value

1
Điều này tương tự như những nỗ lực đầu tiên của tôi tại một công việc xung quanh, nhưng tôi thích thứ gì đó bên trong lớp ... một phần vì thuộc tính liên quan có dấu gạch dưới hàng đầu và là riêng tư.
martineau

11

Điều này là do staticmethod là một bộ mô tả và yêu cầu tìm nạp thuộc tính cấp độ lớp để thực hiện giao thức mô tả và có được khả năng gọi được thực sự.

Từ mã nguồn:

Nó có thể được gọi hoặc trên lớp (ví dụ C.f()) hoặc trên một thể hiện (ví dụ C().f()); cá thể bị bỏ qua ngoại trừ lớp của nó.

Nhưng không trực tiếp từ bên trong lớp trong khi nó đang được xác định.

Nhưng như một người bình luận đã đề cập, đây thực sự không phải là một thiết kế "Pythonic". Chỉ cần sử dụng một chức năng cấp mô-đun thay thế.


Như tôi đã nói trong câu hỏi của mình, tôi hiểu tại sao mã gốc không hoạt động. Bạn có thể giải thích (hoặc cung cấp một liên kết đến một cái gì đó không) tại sao nó được coi là unPythonic?
martineau

Tĩnh điện yêu cầu chính đối tượng lớp hoạt động chính xác. Nhưng nếu bạn gọi nó từ trong lớp ở cấp độ lớp thì lớp đó không thực sự được xác định đầy đủ tại thời điểm đó. Vì vậy, bạn không thể tham khảo nó được. Bạn có thể gọi tĩnh điện từ bên ngoài lớp, sau khi được xác định. Nhưng " Pythonic " không thực sự được xác định rõ, giống như tính thẩm mỹ. Tôi có thể nói với bạn ấn tượng đầu tiên của riêng tôi về mã đó là không thuận lợi.
Keith

Ấn tượng về phiên bản nào (hoặc cả hai)? Và tại sao, cụ thể?
martineau

Tôi chỉ có thể đoán mục tiêu cuối cùng của bạn là gì, nhưng dường như với tôi rằng bạn muốn một thuộc tính cấp độ năng động. Điều này trông giống như một công việc cho metaclass. Nhưng một lần nữa, có thể có một cách đơn giản hơn, hoặc một cách khác để xem xét thiết kế loại bỏ và đơn giản hóa mà không làm mất chức năng.
Keith

3
Không, những gì tôi muốn làm thực sự khá đơn giản - đó là tìm ra một số mã phổ biến và sử dụng lại nó, cả hai để tạo một thuộc tính lớp riêng tại thời điểm tạo lớp và sau đó trong một hoặc nhiều phương thức lớp. Mã phổ biến này không được sử dụng bên ngoài lớp, vì vậy tôi tự nhiên muốn nó là một phần của nó. Sử dụng một siêu dữ liệu (hoặc một trình trang trí lớp) sẽ hoạt động, nhưng điều đó có vẻ như quá mức cần thiết cho một thứ gì đó phải dễ làm, IMHO.
martineau

8

Giải pháp này thì sao? Nó không dựa vào kiến ​​thức về @staticmethodviệc thực hiện trang trí. Lớp bên trong StaticMethod đóng vai trò là một thùng chứa các hàm khởi tạo tĩnh.

class Klass(object):

    class StaticMethod:
        @staticmethod  # use as decorator
        def _stat_func():
            return 42

    _ANS = StaticMethod._stat_func()  # call the staticmethod

    def method(self):
        ret = self.StaticMethod._stat_func() + Klass._ANS
        return ret

2
+1 cho sự sáng tạo, nhưng tôi không còn lo lắng về việc sử dụng __func__vì hiện tại nó đã được ghi lại chính thức (xem phần Thay đổi ngôn ngữ khác của Python mới trong Python 2.7 và tham chiếu đến vấn đề 5982 ). Giải pháp của bạn thậm chí còn dễ mang theo hơn, vì nó có thể cũng sẽ hoạt động trong các phiên bản Python trước 2.6 (khi __func__lần đầu tiên được giới thiệu là từ đồng nghĩa của im_func).
martineau

Đây là giải pháp duy nhất hoạt động với Python 2.6.
benselme

@benselme: Tôi không thể xác minh khiếu nại của bạn vì tôi chưa cài đặt Python 2.6, nhưng nghi ngờ nghiêm trọng đó là duy nhất ...
martineau
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.