Какова цель методов класса?


250

Я учу себя Python, и мой последний урок состоял в том, что Python - это не Java , и поэтому я просто потратил некоторое время, превращая все свои методы Class в функции.

Теперь я понимаю, что мне не нужно использовать методы класса для того, что я делал бы с staticметодами в Java, но теперь я не уверен, когда я буду их использовать. Все советы, которые я могу найти о методах класса Python, должны быть такими же, как у новичков, таких как я, и стандартная документация наиболее непрозрачна при их обсуждении.

У кого-нибудь есть хороший пример использования метода Class в Python или, по крайней мере, кто-нибудь может сказать мне, когда методы Class могут быть разумно использованы?

Ответы:


178

Методы класса предназначены для случаев, когда вам нужно иметь методы, которые не являются специфичными для какого-либо конкретного экземпляра, но все же включают класс в некотором роде. Самое интересное в них - то, что они могут быть переопределены подклассами, что просто невозможно в статических методах Java или функциях уровня модуля Python.

Если у вас есть класс MyClassи функция уровня модуля, которая работает с MyClass (фабрика, заглушка внедрения зависимостей и т. Д.), Сделайте его a classmethod. Тогда это будет доступно для подклассов.


Понимаю. Но как насчет того, если я просто хочу классифицировать метод, который не нуждается в MyClass, но все же в некотором смысле имеет смысл, если я группирую по другому MyClass? Я могу думать о том, чтобы переместить его в модуль помощника, хотя ..
swdev

У меня есть вопрос, связанный с оригиналом, может быть, вы могли бы ответить на него в то же время? Какой способ вызова методов класса объекта является «лучшим» или «более идиоматическим»: obj.cls_mthd(...)или type(obj).cls_mthd(...)?
Алексей

63

Фабричные методы (альтернативные конструкторы) действительно являются классическим примером методов класса.

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

Как пример, в отличном модуле unipath :

Текущий каталог

  • Path.cwd()
    • Вернуть актуальный текущий каталог; например, Path("/tmp/my_temp_dir"). Это метод класса.
  • .chdir()
    • Сделать себе текущий каталог.

Поскольку текущий каталог является широким процессом, у cwdметода нет конкретного экземпляра, с которым он должен быть связан. Однако изменение cwdкаталога данного Pathэкземпляра действительно должно быть методом экземпляра.

Хм ... как на Path.cwd()самом деле возвращает Pathэкземпляр, я думаю, это можно считать фабричным методом ...


1
Да ... первое, что пришло в голову, - это фабричные методы. Также по этим направлениям есть вещи, которые будут реализовывать шаблон Singleton, например: getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
В Python это статические методы, а не методы класса.
Джайкуль

46

Подумайте об этом так: обычные методы полезны, чтобы скрыть детали диспетчеризации: вы можете печатать, myobj.foo()не беспокоясь о том foo(), реализован ли метод myobjклассом объекта или одним из его родительских классов. Методы класса в точности аналогичны этому, но вместо этого они используют объект класса: они позволяют вам вызывать, MyClass.foo()не беспокоясь о том foo(), реализован ли он специально, MyClassпотому что ему нужна собственная специализированная версия, или он разрешает своему родительскому классу обрабатывать вызов.

Методы класса необходимы, когда вы выполняете настройку или вычисление, которое предшествует созданию фактического экземпляра, потому что, пока экземпляр не существует, вы, очевидно, не можете использовать экземпляр в качестве точки отправки для вызовов вашего метода. Хороший пример можно посмотреть в исходном коде SQLAlchemy; взгляните на dbapi()метод класса по следующей ссылке:

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

Вы можете видеть, что dbapi()метод, который серверная часть базы данных использует для импорта библиотеки базы данных конкретного поставщика, которая ему нужна по требованию, является методом класса, потому что он должен быть запущен до того, как экземпляры определенного соединения с базой данных начнут создаваться - но он не может быть простой функцией или статической функцией, потому что они хотят, чтобы она могла вызывать другие, поддерживающие методы, которые аналогичным образом должны быть написаны более конкретно в подклассах, чем в их родительском классе. А если вы отправляете функцию или статический класс, то вы «забываете» и теряете знания о том, какой класс выполняет инициализацию.



28

Недавно я хотел получить очень легкий класс журналирования, который бы выводил различное количество выходных данных в зависимости от уровня ведения журнала, который мог бы быть установлен программно. Но я не хотел создавать экземпляр класса каждый раз, когда хотел вывести отладочное сообщение или ошибку или предупреждение. Но я также хотел инкапсулировать функционирование этого средства регистрации и сделать его многоразовым без объявления каких-либо глобальных переменных.

Поэтому я использовал переменные класса и @classmethodдекоратор для достижения этой цели.

С моим простым классом Logging я мог сделать следующее:

Logger._level = Logger.DEBUG

Затем, в моем коде, если я хотел выпустить кучу отладочной информации, мне просто нужно было кодировать

Logger.debug( "this is some annoying message I only want to see while debugging" )

Ошибки могут быть выведены с

Logger.error( "Wow, something really awful happened." )

В «производственной» среде я могу указать

Logger._level = Logger.ERROR

и теперь выводится только сообщение об ошибке. Отладочное сообщение не будет напечатано.

Вот мой класс:

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

И некоторый код, который проверяет это только немного:

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()

10
Это не кажется мне хорошим примером метода класса, который хорош, потому что все это можно было бы сделать менее сложным способом, просто сделав все методы уровня класса функциями уровня модуля и полностью избавившись от класса.
Мартино

10
О, что еще более важно, Python предоставляет очень простой в использовании класс ведения логов. Так что хуже, чем создание неоптимального решения, я заново изобрел колесо. Двойной шлепок. Но это как минимум рабочий пример. Не уверен, что согласен с тем, чтобы избавиться от класса на концептуальном уровне. Иногда вы хотите инкапсулировать код для легкого повторного использования, и методы журналирования являются отличной целью для повторного использования.
Марво

3
Почему бы вам не использовать статический метод?
bdforbes


11

Когда пользователь входит на мой сайт, объект User () создается из имени пользователя и пароля.

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

У меня есть методы класса, чтобы захватить пользовательский объект.

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()

9

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

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

Вышеупомянутый процедурный код не очень хорошо организован, как вы можете видеть, что после всего 3 модулей он сбивает с толку, что делает каждый метод? Вы можете использовать длинные описательные имена для функций (как в java), но ваш код очень быстро становится неуправляемым.

Объектно-ориентированный способ состоит в том, чтобы разбить ваш код на управляемые блоки, т.е. классы, объекты и функции могут быть связаны с экземплярами объектов или с классами.

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

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

Этот способ является более гибким и более обслуживаемым, вы группируете функции вместе, и это более очевидно для каждой функции. Также вы предотвращаете конфликты имен, например, функция копирования может существовать в другом импортированном модуле (например, сетевая копия), который вы используете в своем коде, поэтому, когда вы используете полное имя FileUtil.copy (), вы устраняете проблему и обе функции копирования. можно использовать бок о бок.


1
Чтобы использовать f1 (), f2 () и т. Д., Как и вы, не следует ли использовать из импорта модуля * и из импорта использования *?
Jblasco

@Jblasco Я исправил операторы импорта, чтобы устранить путаницу, да, если вы импортируете модуль, вы должны добавить функции к имени модуля. т.е. модуль импорта -> module.f1 () и т. д.
firephil

Я не согласен с этим ответом; Python не является Java. Проблема неуправляемого кода, по-видимому, возникает только из-за преднамеренного выбора плохих имен в примере. Если бы вместо этого вы реплицировали FileUtilметоды класса как функции file_utilмодуля, это было бы сопоставимо и не использовало бы ООП, если на самом деле не существует никаких объектов (на самом деле вы могли бы утверждать, что это предпочтительнее, потому что вы не получаете в итоге from file_util import FileUtilили другого многословия). Аналогичным образом можно избежать конфликтов имен в процедурном коде, выполнив import file_utilвместо from file_util import ....
Забытый

7

Честно? Я никогда не находил применение статическому или классному методу. Я еще не видел операцию, которую нельзя выполнить с помощью глобальной функции или метода экземпляра.

Было бы по-другому, если бы Python использовал закрытые и защищенные члены, как Java. В Java мне нужен статический метод, чтобы иметь возможность доступа к закрытым членам экземпляра для выполнения каких-либо задач. В Python это редко необходимо.

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


1
Личное: _variable_name и Защищено: __variable_name
Брэдли

1
Одним из обязательных применений являются setUpClass и tearDownClass от unittest. Вы используете юнит-тесты, верно? :)
д.б.н.

7

Это позволяет вам писать универсальные методы класса, которые вы можете использовать с любым совместимым классом.

Например:

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

Если вы не используете, @classmethodвы можете сделать это с помощью ключевого слова self, но для этого нужен экземпляр Class:

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C

5

Раньше я работал с PHP, и недавно я спрашивал себя, что происходит с этим методом класса? Руководство по Python очень техническое и очень короткое на словах, поэтому оно не поможет понять эту функцию. Я гуглил и гуглял и нашел ответ -> http://code.anjanesh.net/2007/12/python-classmethods.html .

Если вам лень кликнуть. Мое объяснение короче и ниже. :)

в PHP (может быть, не все из вас знают PHP, но этот язык настолько прост, что каждый должен понимать, о чем я говорю), у нас есть статические переменные, подобные этой:


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

Выход будет в обоих случаях 20.

Однако в python мы можем добавить декоратор @classmethod и, таким образом, можно получить выходные данные 10 и 20 соответственно. Пример:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()

Умный, не так ли?


Одна потенциальная проблема с вашим примером Python состоит в том, что если B.setInnerVar(20)его 10опустить , он напечатает дважды (вместо того, чтобы выдавать ошибку при втором вызове echoInnerBar (), который не inner_varбыл определен)
martineau

5

Методы класса обеспечивают «семантический сахар» (не знаю, широко ли используется этот термин) или «семантическое удобство».

Пример: вы получили набор классов, представляющих объекты. Возможно, вы захотите иметь метод класса all()или find()написать User.all()или User.find(firstname='Guido'). Это может быть сделано с использованием функций уровня модуля, конечно ...


В вашем примере, конечно, предполагается, что класс отслеживает все свои экземпляры и может получить к ним доступ после того, как они созданы - что-то не делается автоматически.
Мартино

1
«семантический сахар» звучит как хороший матч с «синтаксическим сахаром».
XTL

3

Что меня только поразило, так это то, что так называемый метод класса и так называемый метод экземпляра - это просто функция с семантическим значением, примененная к ее первому параметру, который молча передается, когда функция вызывается как метод объект (то есть obj.meth()).

Обычно этот объект должен быть экземпляром, но @classmethod декоратор метода изменяет правила для передачи класса. Вы можете вызвать метод класса в экземпляре (это просто функция) - первым аргументом будет его класс.

Поскольку это просто функция , она может быть объявлена ​​только один раз в любой заданной области (то есть в classопределении). Следовательно, если в качестве сюрприза для Rubyist следует, что у вас не может быть метода класса и метода экземпляра с одинаковым именем .

Учти это:

class Foo():
  def foo(x):
    print(x)

Вы можете позвонить fooна экземпляр

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

Но не в классе:

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

Теперь добавьте @classmethod:

class Foo():
  @classmethod
  def foo(x):
    print(x)

Вызов экземпляра теперь передает его класс:

Foo().foo()
__main__.Foo

как и при вызове класса:

Foo.foo()
__main__.Foo

Это единственное соглашение, которое предписывает использовать selfэтот первый аргумент в методе экземпляра и clsв методе класса. Я не использовал ни того, ни другого, чтобы проиллюстрировать, что это просто аргумент. В Ruby selfэто ключевое слово.

Контраст с Ruby:

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

Метод класса Python - это просто декорированная функция, и вы можете использовать те же методы для создания собственных декораторов . Декорированный метод оборачивает реальный метод (в случае @classmethodего передачи дополнительный аргумент класса). Основной метод все еще там, скрытый, но все еще доступный .


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


«который молча передается, когда функция вызывается как метод объекта» - я считаю, что это не точно. АФИКТ, ничего не «тихо передается» в Python. ИМО, Python гораздо более разумный, чем Ruby в этом смысле (кроме super()). AFICT, «магия» происходит, когда атрибут установлен или прочитан. Вызов obj.meth(arg)в Python (в отличие от Ruby) означает просто (obj.meth)(arg). Ничто молча нигде не передается, obj.methэто просто вызываемый объект, который принимает на один аргумент меньше, чем функция, из которой он был создан.
Алексей

obj.methВ свою очередь означает просто getattr(obj, "meth").
Алексей

Имеет смысл иметь ответ специально для людей, пришедших с других распространенных языков. Однако в этом разговоре отсутствует одна вещь, которая связывает его вместе, это понятие дескриптора Python . Функции являются дескрипторами , и именно так первый аргумент «автоматически» передается экземпляру, когда функция является атрибутом класса. Это также, как реализованы методы классов, и в разделе HOWTO I приведена чистая реализация на python. Тот факт, что он также является декоратором, является своего рода вспомогательным
juanpa.arrivillaga

2

Это интересная тема. Я предполагаю, что метод класса Python работает как синглтон, а не как фабрика (которая возвращает произведенный экземпляр класса). Причина, по которой он является единичным, состоит в том, что существует общий объект, который создается (словарь), но только один раз для класса, но используется всеми экземплярами.

Чтобы проиллюстрировать это, вот пример. Обратите внимание, что все экземпляры имеют ссылку на один словарь. Это не Фабричный образец, насколько я понимаю. Это, вероятно, очень уникально для Python.

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5

2

Я задавал себе один и тот же вопрос несколько раз. И хотя ребята изо всех сил пытались это объяснить, ИМХО лучший (и самый простой) ответ, который я нашел, это описание метода Class в документации по Python.

Также есть ссылка на статический метод. И в случае, если кто-то уже знает методы экземпляра (что я предполагаю), этот ответ может быть последней частью, чтобы собрать все вместе ...

Более подробную информацию по этой теме можно найти также в документации: Стандартная иерархия типов (прокрутите вниз до раздела Методы экземпляра ).


1

@classmethodможет быть полезен для простого создания экземпляров объектов этого класса из внешних ресурсов. Учтите следующее:

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

Тогда в другом файле:

from some_package import SomeClass

inst = SomeClass.from_settings()

Доступ к inst.x даст то же значение, что и настройки ['x'].


0

Конечно, класс определяет набор экземпляров. И методы класса работают на отдельных экземплярах. Методы класса (и переменные) - это место, где можно повесить другую информацию, связанную с множеством экземпляров.

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

Вы также можете использовать методы класса для определения инструментов для работы со всем набором. Например, Student.all_of_em () может вернуть всех известных студентов. Очевидно, что если ваш набор экземпляров имеет больше структуры, чем просто набор, вы можете предоставить методы класса, чтобы узнать об этой структуре. Students.all_of_em (класс = 'юниоры')

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.