Виклик статичного методу класу в тілі класу?


159

Коли я намагаюся використати статичний метод зсередини тіла класу та визначити статичний метод, використовуючи вбудовану staticmethodфункцію в якості декоратора, як це:

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

Я отримую таку помилку:

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

Я розумію, чому це відбувається (прив'язка дескриптора) , і можу обійти це, перетворивши вручну _stat_func()в статичний метод після останнього використання, наприклад:

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

Отже, моє питання:

Чи є кращі, як у більш чистих чи «пітонічних» способах цього досягти?


4
Якщо ви запитуєте про Pythonicity, то стандартну пораду взагалі не використовувати staticmethod. Зазвичай вони є більш корисними як функції на рівні модулів, і в цьому випадку ваша проблема не є проблемою. classmethod, з іншого боку ...
Бенджамін Ходжсон

1
@poorsod: Так, я знаю цю альтернативу. Однак у фактичному коді, де я стикався з цією проблемою, зробити функцію статичним методом, а не ставити її на рівні модуля, має більше сенсу, ніж це робиться в простому прикладі, використаному в моєму запитанні.
мартіно

Відповіді:


179

staticmethodоб'єкти, мабуть, мають __func__атрибут, що зберігає початкову функцію необробленого (має сенс, що вони повинні були). Отже, це спрацює:

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

Як осторонь, хоча я підозрював, що об’єкт статичного методу має якийсь атрибут, що зберігає початкову функцію, я не мав уявлення про специфіку. У дусі вчити когось ловити рибу, а не давати їм рибу, це те, що я зробив, щоб розслідувати та виявити це (C&P з мого сеансу Python):

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

Подібні види копання в інтерактивному сеансі ( dirдуже корисно) часто можуть вирішити подібні питання дуже швидко.


Гарне оновлення ... Я збирався запитати, як ви це знали, оскільки я не бачу цього в документації - це змушує мене нервувати його використання, оскільки це може бути "деталізацією щодо впровадження".
мартіно

Після подальшого читання я бачу, що __func__це лише інша назва im_funcі було додано в Py 2.6 для подальшої сумісності Python 3.
martineau

2
Я бачу, настільки технічно це недокументовано в цьому контексті.
martineau

1
@AkshayHazari __func__Атрибут статичного методу дає вам посилання на оригінальну функцію, точно так, як ніби ви ніколи не використовували staticmethodдекоратор. Тож якщо для вашої функції потрібні аргументи, вам доведеться передавати їх під час виклику __func__. Повідомлення про помилку, яке ви досить звучить, ви не давали йому жодних аргументів. Якби stat_funcу прикладі цієї публікації було два аргументи, ви б використали_ANS = stat_func.__func__(arg1, arg2)
Бен

1
@AkshayHazari Я не впевнений, що розумію. Вони можуть бути змінними, вони просто повинні бути змінними в межах області: або визначені раніше в тій самій області, де визначено клас (часто глобальна), або визначені раніше в межах класу ( stat_funcсама така змінна). Ви мали на увазі, що не можете використовувати атрибути екземплярів класу, який ви визначаєте? Це правда, але не дивно; ми не маємо екземпляр класу, так як ми до сих пір визначають клас! Але в будь-якому випадку, я просто мав на увазі їх як позиції для будь-яких аргументів, які ви хотіли передати; Ви можете там використовувати літерали.
Бен

24

Це я вважаю за краще:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

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

Я вважаю за краще це рішення Klass.stat_funcчерез принцип DRY . Нагадує мені про причину появи новогоsuper() в Python 3 :)

Але я згоден з іншими, зазвичай найкращим вибором є визначення функції рівня модуля.

Наприклад, з @staticmethodфункцією, рекурсія може виглядати не дуже добре (Вам потрібно буде порушити принцип DRY, зателефонувавши Klass.stat_funcвсередину Klass.stat_func). Це тому, що ви не маєте посилання на selfстатичний метод всередині. З функцією рівня модуля все буде виглядати нормально.


Хоча я погоджуюся, що використання self.__class__.stat_func()звичайних методів має переваги (DRY та все це) перед використанням Klass.stat_func(), це насправді не було моєю темою запитання - адже я уникав використання першого, щоб не затьмарити питання невідповідності.
мартіно

1
Це насправді не проблема СУХОСТІ як такої. self.__class__краще, тому що якщо підклас переосмислює stat_func, тоді Subclass.methodвикличе підклас stat_func. Але якщо бути чесним, у цій ситуації набагато краще просто використовувати реальний метод замість статичного.
asmeurer

@asmeurer: Я не можу використовувати реальний метод, оскільки ще не створено жодних примірників класу - їх не може бути, оскільки сам клас ще не був повністю визначений.
мартіно

Я хотів, щоб викликати статичний метод для мого класу (який успадковується; тому батько насправді має функцію), і це здається найкращим на сьогодні (і все одно буде працювати, якщо я його перекрию пізніше).
whitey04

11

Що щодо введення атрибута класу після визначення класу?

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
Це подібно до моїх перших спроб розв’язання, але я вважаю за краще щось, що було всередині класу ... частково, тому що атрибут має провідне підкреслення і є приватним.
мартіно

11

Це пояснюється тим, що staticmethod є дескриптором і вимагає отримання атрибутів на рівні класу для здійснення протоколу дескриптора та отримання справжнього виклику.

Із вихідного коду:

Його можна викликати або на класі (наприклад C.f()), або на екземплярі (наприклад C().f()); екземпляр ігнорується за винятком класу.

Але не безпосередньо зсередини класу під час його визначення.

Але, як зазначав один із коментаторів, це насправді взагалі не "піфонічний" дизайн. Просто використовуйте функцію рівня модуля.


Як я вже говорив у своєму запитанні, я розумію, чому оригінальний код не працював. Чи можете ви пояснити (або надати посилання на те, що робить), чому це вважається нефітонічним?
мартіно

Статичний метод вимагає, щоб сам об’єкт класу працював правильно. Але якщо ви називаєте це всередині класу на рівні класу, то клас не справді повністю визначений у цій точці. Тому ви ще не можете посилатися на нього. Добре називати статичний метод поза класом, після його визначення. Але " піфонічний " насправді не є чітко визначеним, як і естетика. Я можу сказати вам, що моє власне перше враження від цього коду було не сприятливим.
Кіт

Враження якої версії (або обох)? І чому конкретно?
мартіно

Я можу лише здогадуватися, яка ваша кінцева мета, але мені здається, що ви хочете динамічний атрибут на рівні класу. Це виглядає як робота для метакласів. Але знову ж таки може бути більш простий спосіб, або інший спосіб подивитися на дизайн, який усуває та спрощує, не приносячи шкоди функціональності.
Кіт

3
Ні, те, що я хочу зробити, насправді досить просте - це виділити якийсь загальний код і повторно використовувати його, як створити атрибут приватного класу під час створення класу, так і пізніше в межах одного або декількох методів класу. Цей загальний код не використовує поза класом, тому я, природно, хочу, щоб він був його частиною. Використання метакласу (або декоратора класу) спрацювало б, але це здається зайвим для чогось, що повинно бути легко, IMHO.
мартіно

8

Що з цим рішенням? Це не покладається на знання виконання @staticmethodдекораторів. Внутрішній клас StaticMethod виступає як контейнер функцій статичної ініціалізації.

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 для творчості, але я більше не переживаю за використання, __func__оскільки це зараз офіційно задокументовано (див. Розділ Інші зміни мови що нового у Python 2.7 та його посилання на випуск 5982 ). Ваше рішення є ще більш портативним, оскільки воно, ймовірно, також буде працювати у версіях Python до 2.6 (коли __func__вперше було введено як синонім im_func).
мартіно

Це єдине рішення, яке працює з Python 2.6.
benselme

@benselme: Я не можу підтвердити вашу претензію, оскільки у мене не встановлений Python 2.6, але я серйозно сумніваюся, що це єдиний ...
martineau
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.