Должен ли этот метод быть методом класса и почему он не может получить доступ к vars?

Я родом из Java, поэтому я немного запутался.

Рассмотрим фрагмент кода ниже:

class A():
    def __init__(self, **kwargs):
        self.obj_var = "I am obj var"

    @classmethod
    def class_method(cls):
        print cls.obj_var   # this line is in question here
        cls.cls_obj = "I m class object"
        return cls.cls_obj

Это вызывает ошибку:

In [30]: a = A()

In [31]: a.obj_var
Out[31]: 'I am obj var'

In [32]: a.class_method()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-32-3dcd9d512548> in <module>()
----> 1 a.class_method()

<ipython-input-29-9c0d341ad75f> in class_method(cls)
      8     @classmethod
      9     def class_method(cls):
---> 10         print cls.obj_var
     11         cls.cls_obj = "I m class object"
     12         return cls.cls_obj

AttributeError: class A has no attribute 'obj_var'

iЕсли я закомментирую @classmethod, это будет работать нормально.

Мое понимание (верно неверно):

Когда я делаю a = A(), то все переменные (obj_var), созданные в __init__, могут быть доступны через этот a при передаче в любой другой classmethod того же класса. Очевидно, это не так.

Вопрос(ы)

  • почему я не могу получить доступ к __init__ vars в class_method, когда в методе упоминается декоратор @classmethod, но при удалении декоратора он работает нормально?

  • как python внутренне обрабатывает этот конкретный класс при компиляции?

  • Могу ли я каким-либо образом использовать переменные @classmethod и __init__ в одном методе?


person NoobEditor    schedule 15.01.2016    source источник
comment
Почему вы используете classmethod, если вам нужен метод экземпляра?   -  person user2357112 supports Monica    schedule 15.01.2016
comment
То, что вы называете __init__ vars, является атрибутами экземпляра. Причина, по которой метод класса называется методом класса, заключается в том, что он имеет доступ только к классу, а не к экземпляру. Почему вы вообще используете classmethod?   -  person BrenBarn    schedule 15.01.2016
comment
@BrenBarn: я использую __init__ в качестве конструктора, в котором после установки значения могут использоваться где угодно в методах класса...   -  person NoobEditor    schedule 15.01.2016
comment
@NoobEditor: Почему вы используете classmethod? Как вы думаете, какова цель classmethod? Похоже, вам следует прочитать руководство по Python, чтобы понять, как использовать классы в Python.   -  person BrenBarn    schedule 15.01.2016
comment
@ user2357112: может показаться супер глупым ... но в чем разница между методами classmethod и instance? Доступ к методу через object является экземпляром, а выполнение A(). class_method() является методом класса?   -  person NoobEditor    schedule 15.01.2016
comment
@BrenBarn: я просмотрел документацию, приятель, это всегда Шаг 1, то, что я пытаюсь сделать, это выйти из java shell, в java a=A() a.method() A().method() больше или меньше того же, но, видимо, не в питоне... это то, что меня смутило.... имеет смысл?   -  person NoobEditor    schedule 15.01.2016
comment
@NoobEditor: они более или менее одинаковы, но опять же, вы никоим образом не объяснили, почему используете classmethod. Кажется, вы думаете, что метод класса — это просто любой метод, который вы определяете в классе, но это не так. Возможно, вы захотите просмотреть этот вопрос и этот вопрос. Если вам нужен доступ к переменным экземпляра, не используйте classmethod. Вот и все.   -  person BrenBarn    schedule 15.01.2016
comment
Метод класса Python похож на статический метод Java.   -  person David Heffernan    schedule 15.01.2016
comment
@DavidHeffernan: Нет. Статические методы Java не участвуют в полиморфизме. Они не могут переопределяться или переопределяться другими методами, статическими или иными, и не могут полиморфно вызываться во время выполнения без большого количества размышлений. Python не имеет ни одного из этих ограничений.   -  person Kevin    schedule 15.01.2016
comment
@ Кевин, я сказал, похоже, а не то же самое. Помните, что спрашивающий — новичок, который думает, что classmethod — это метод экземпляра. С точки зрения простой и полезной аналогии, мой комментарий был разумным. У новичка нет шансов понять смысл того, что вы только что сказали. Хотя я знаю все, что вы сказали, вы думаете, что спрашивающий уже может это понять? Подумайте о процессе обучения. Ваш ответ находится на совершенно неправильном уровне для спрашивающего.   -  person David Heffernan    schedule 15.01.2016
comment
Потому что вы не можете просто получить доступ к переменным экземпляра, как если бы они были переменными класса; в противном случае, если бы у вас было 2+ экземпляра, как бы класс узнал, на какой экземпляр вы хотели сослаться?! б) Вы также не можете сделать это в методе класса Java. c) Запах кода: поэтому, если вы не можете реализовать метод как метод класса, то это метод экземпляра, а не метод класса...   -  person smci    schedule 15.12.2020


Ответы (2)


Давайте поговорим о том, как на самом деле работают методы Python.

Вы могли заметить, что методы Python объявляются точно так же, как автономные функции, но внутри класса. Это потому, что методы Python на самом деле являются автономными функциями, которые находятся внутри класса. Аргумент self/cls не является особым. Это просто первый аргумент функции.

Прежде чем мы пойдем дальше, я хотел бы отметить, что вы, похоже, не наследуете явно от object. Если вы работаете в Python 2.x, в корне графа нет object, если только вы не наследуете его явно. Это плохо, и вы должны прямо или косвенно наследоваться от object, когда это возможно в новом коде. Наследование от object в Python 3 является законным и безвредным, но ненужным. Остальная часть этого обсуждения предполагает, что вы либо работаете в 3.x, либо исправили это.

Когда вы получаете доступ к переменной, функции, методу или объекту любого другого типа с помощью foo.bar, вызываются определенные ловушки, известные как «протокол дескриптора». Вам не нужно знать подробности этого, чтобы понять, как работают функции. Все, что вам нужно знать, это:

  1. Во-первых, если есть переменная экземпляра bar, напрямую связанная с foofoo не является классом), мы просто возвращаем ее напрямую.(*)
  2. Если foo — это класс, а bar — это @classmethod функция (**), объявленная либо в foo, либо в одном из его суперклассов, то перед возвратом первый аргумент устанавливается равным foo. В противном случае он возвращается без изменений.(***) Если мы что-то вернули, мы останавливаемся здесь.
  3. Мы ищем «порядок разрешения метода» foo. Он состоит из класса foo (известного как type(foo)), суперкласса этого класса и так далее, пока мы не дойдем до object. В случае множественного наследования это становится немного сложнее, но опять же, вам не нужно этого знать.
  4. Возьмите переменную bar из первого класса, в котором она есть (назовите ее Baz).
  5. Если Baz.bar является обычной, недекорированной функцией, установите ее первый аргумент равным foo и верните его.
  6. В противном случае, если Baz.bar является функцией @classmethod, установите ее первый аргумент равным type(foo) и верните его.
  7. В противном случае, если Baz.bar является функцией @staticmethod или вообще не является функцией (**), верните ее без изменений.

Как видите, если метод объявлен @classmethod, первым аргументом всегда является класс, а не экземпляр, независимо от того, как вызывается функция. Это означает, что у вас нет доступа к переменным экземпляра foo, поскольку у вас нет доступа к самому foo. Любые переменные, установленные в __init__(), являются переменными экземпляра, поэтому здесь они не видны.

И вот все, о чем я солгал:

(*): Python фактически сначала выполняет остальную часть работы, а затем возвращается к этому шагу. Но это имеет значение только для таких вещей, как @property, которые фактически могут переопределять локальные переменные экземпляра. Обычными методами нельзя.

(**): Это ложь. В этом случае Python также выполняет специальную обработку @property функций и всего остального, что реализует определенные специальные методы.

(***): Я также игнорирую несвязанные методы в Python 2.x, потому что, за исключением некоторых действительно странных случаев (когда вы пытаетесь передать объект неправильного типа в качестве первого аргумента вручную), они не имеют никакого значения. . Их нет в Python 3.

person Kevin    schedule 15.01.2016
comment
поздно... я знаю!! - person NoobEditor; 19.01.2019
comment
Я бы предварил это однострочником, потому что вы не можете просто получить доступ к переменным экземпляра как к переменным класса; в противном случае, если бы у вас было 2+ экземпляра, как классы узнали бы, на какой экземпляр вы хотели сослаться?!. В любом случае, этот ответ является излишним, предостережение в Python 2.x, классы могут наследоваться от object, вы должны перенести это в сноску. И длинная часть о MRO - это излишество. OP просто смущен тем, что они не могут получить доступ к переменным экземпляра, как если бы они были переменными класса. - person smci; 15.12.2020

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

class A():
def __init__(self, **kwargs):
    self.obj_var = "I am obj var"

@classmethod
def class_method(cls):
    self = cls() # Here you are creating an instance of the class (in this case it is class A)
    print(self.obj_var) 
    cls.cls_obj = "I m class object"
    return cls.cls_obj

 print(A.class_method())
person Sandeep Suryaprasad    schedule 28.09.2019