Как я могу получить доступ к методу класса из класса в Python

Я хотел бы создать класс в Python, который управляет всеми статическими членами. Эти члены должны быть инициализированы уже во время определения класса. Из-за того, что позже потребуется повторная инициализация статических членов, я бы поместил этот код в метод класса.

Мой вопрос: как я могу вызвать этот метод класса внутри класса?

class Test():
    # static member
    x = None
    # HERE I WOULD LOVE TO CALL SOMEHOW static_init!

    # initialize static member in classmethod, so that it can be 
    #reinitialized later on again    
    @classmethod
    def static_init(cls):
        cls.x = 10

Любая помощь приветствуется!

Заранее спасибо, Волкер


person Volker    schedule 16.12.2012    source источник
comment
Не обращайте внимания на мою мозговую отрыжку! Есть ли причина, по которой вы хотите сделать это таким образом?   -  person Jon Clements♦    schedule 16.12.2012
comment
Что ж, для меня важно то, что я могу повторить это позже. Конечно, это не имеет смысла только с x = 10, но в реальном приложении. Я загружу некоторые XML-документы, которые можно изменить во время выполнения приложения.   -  person Volker    schedule 16.12.2012


Ответы (6)


В то время, когда в вашем примере выполняется x=10, не существует не только класса, но и метода класса.

Выполнение в Python идет сверху вниз. Если x=10 выше метода класса, вы не сможете получить доступ к методу класса в этой точке, потому что он еще не определен.

Даже если бы вы могли запустить метод класса, это не имело бы значения, потому что класс еще не существует, поэтому метод класса не мог бы ссылаться на него. Класс не создается до тех пор, пока не будет запущен весь блок класса, поэтому, пока вы находитесь внутри блока класса, класса нет.

Если вы хотите исключить некоторую инициализацию класса, чтобы вы могли повторно запустить ее позже так, как вы описываете, используйте декоратор класса. Декоратор класса запускается после создания класса, поэтому он может нормально вызывать метод класса.

>>> def deco(cls):
...     cls.initStuff()
...     return cls
>>> @deco
... class Foo(object):
...     x = 10
...     
...     @classmethod
...     def initStuff(cls):
...         cls.x = 88
>>> Foo.x
88
>>> Foo.x = 10
>>> Foo.x
10
>>> Foo.initStuff() # reinitialize
>>> Foo.x
88
person BrenBarn    schedule 16.12.2012
comment
вау, это элегантное решение! Я начал с функции уровня модуля для решения своей проблемы, но это общий подход! - person Volker; 16.12.2012
comment
@user1907681 user1907681 Я не уверен, что вы хотите пойти по этому пути, но тогда я не знаю вашей точной проблемы, поэтому не могу быть уверен ... Но это определенно самый питонический способ добиться этого (с хорошее объяснение)... +1 - person Jon Clements♦; 16.12.2012
comment
Лучше вызывать initStuff из init, чем полагаться на то, что его вызовет создатель. Это решает исходный постер «# ЗДЕСЬ Я ХОЧУ КАК-ТО ВЫЗВАТЬ static_init!» требование. - person steveayre; 07.12.2016
comment
@steveayre: вызов его из __init__ будет означать, что он вызывается для каждого экземпляра. Он хочет, чтобы его назвали один раз для всего класса. - person BrenBarn; 07.12.2016

Вы вызываете метод класса, добавляя имя класса аналогичным образом:

class.method

В вашем коде должно быть достаточно чего-то вроде этого:

Test.static_init()

Вы также можете сделать это:

static_init(Test)

Чтобы вызвать его внутри класса, сделайте так, чтобы ваш код делал следующее:

Test.static_init()

Мой рабочий код:

class Test(object):

    @classmethod
    def static_method(cls):
        print("Hello")

    def another_method(self):
        Test.static_method()

и Test().another_method() возвращает Hello

person NlightNFotis    schedule 16.12.2012
comment
К сожалению, кажется, что это работает ВНЕ класса. Я только что попробовал: class Test(): # static member x = None Test.static_init() # инициализируйте статический член в classmethod, чтобы его можно было снова #повторно инициализировать позже @classmethod def static_init(cls): cls.x = 10, если name == main: print(Test.x) Результат: Test.static_init() NameError: имя 'Test' не определено# - person Volker; 16.12.2012
comment
Когда я пытаюсь использовать Test.static_init() или static_init(Test) из ВНУТРИ класса и до определения метода класса, я получаю сообщение об ошибке: NameError: имя 'static_init' не определено - person Volker; 16.12.2012
comment
@ user1907681 Да, чувак, это потому, что python следует подходу сверху вниз. Это означает, что если вы попытаетесь выполнить Test.static_init() до того, как на самом деле defнайдёте его, то static_init() не будет известно остальной части вашей программы, поэтому получить Name Error нормально. - person NlightNFotis; 16.12.2012
comment
Ну, я также попытался поместить код ПОСЛЕ определения метода класса. Но у меня та же ошибка NameError: class Test(): # static member x = None # инициализируйте статический член в classmethod, чтобы его можно было снова #переинициализировать позже @classmethod def static_init(cls): cls.x = 10 Тест.static_init() - person Volker; 16.12.2012

Вы не можете вызвать classmethod в определении class, потому что класс еще не определен полностью, поэтому нет ничего, что можно было бы передать методу в качестве его первого аргумента cls... классическая проблема курицы и яйца. Однако вы можете обойти это ограничение, перегрузив метод __new__() в метаклассе и вызвав оттуда метод класса после создания класса, как показано ниже:

class Test(object):
    # nested metaclass definition
    class __metaclass__(type):
        def __new__(mcl, classname, bases, classdict):
            cls = type.__new__(mcl, classname, bases, classdict)  # creates class
            cls.static_init()  # call the classmethod
            return cls

    x = None

    @classmethod
    def static_init(cls):  # called by metaclass when class is defined
        print("Hello")
        cls.x = 10

print Test.x

Выход:

Hello
10
person martineau    schedule 16.12.2012

Перечитав ваш вопрос внимательно на этот раз, я могу придумать два решения. Первый — применить шаблон проектирования Борга. Второй — отказаться от метода класса и вместо этого использовать функцию уровня модуля. Похоже, это решает вашу проблему:

def _test_static_init(value):
    return value, value * 2

class Test:
    x, y = _test_static_init(20)

if __name__ == "__main__":
    print Test.x, Test.y

Старый, неправильный ответ:

Вот пример, надеюсь поможет:

class Test:
    x = None

    @classmethod
    def set_x_class(cls, value):
        Test.x = value

    def set_x_self(self):
        self.__class__.set_x_class(10)

if __name__ == "__main__":
    obj = Test()
    print Test.x
    obj.set_x_self()
    print Test.x
    obj.__class__.set_x_class(15)
    print Test.x

В любом случае, ответ NlightNFotis лучше: используйте имя класса при доступе к методам класса. Это делает ваш код менее неясным.

person ZalewaPL    schedule 16.12.2012
comment
К сожалению, это не решает мою проблему. Я знаю, как получить доступ к методу класса из ВНЕШНЕГО класса. Но я хотел бы иметь что-то похожее на статический инициализатор в Java, который вызывает статический метод, который можно будет вызвать позже. - person Volker; 16.12.2012
comment
Посмотрите на шаблон проектирования Борга. - person ZalewaPL; 16.12.2012
comment
Да, шаблон проектирования Borg или другое решение шаблона Singleton также решит мою проблему. - person Volker; 16.12.2012
comment
«Это делает ваш код менее непонятным». Это также нарушает создание подклассов, чтобы переопределить метод класса. - person steveayre; 07.12.2016

Это кажется разумным решением:

from __future__ import annotations
from typing import ClassVar, Dict
import abc
import string


class Cipher(abc.ABC):
    @abc.abstractmethod
    def encrypt(self, plaintext: str) -> str:
        pass

    @abc.abstractmethod
    def decrypt(self, ciphertext: str) -> str:
        pass


class RotateCipher(Cipher, abc.ABC):
    @staticmethod
    def rotate(n: int) -> str:
        return string.ascii_uppercase[n:] + string.ascii_uppercase[:n]


class VigenereCipher(RotateCipher):
    _TABLE: ClassVar[Dict[str, str]] = dict({(chr(i + ord("A")), RotateCipher.rotate(i)) for i in range(26)})

    def encrypt(self, plaintext: str) -> str:
        pass

    def decrypt(self, plaintext: str) -> str:
        pass


vc = VigenereCipher()

Метод теперь является статическим методом шифра, ни на что вне классов не ссылается. Вместо этого вы можете выбрать имя RotateCipher _RotateCipher, если не хотите, чтобы люди использовали его отдельно.

Примечание. Я удалил Final, так как запускал это на 3.7, но после прочтения документации по Final я не думаю, что это повлияет на решение? Также добавлен импорт для string, в котором отсутствовал вопрос. И, наконец, добавлена реализация для абстрактных методов, в качестве альтернативы можно было бы позволить VigenereCipher также наследоваться от abc.ABC.

person Grismar    schedule 25.12.2019

класс человека с add() @classmethod

class Person:
    def __init__(self,a,b):
            self.first = a
            self.second = b

    @classmethod
    def add(cls,a,b):
        return a+b
    
    def __repr__(self):
        return ('{}'.format(Person.add(self.first,self.second)))
    
p = Person(10,20)
print(p)

o/p : 30
person Sandeep Agrawal    schedule 22.11.2020