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

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

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

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

Ответы

Ответ 1

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

Если у вас есть класс MyClass и функция уровня модуля, работающая на MyClass (factory, заглушка зависимостей и т.д.), сделайте его classmethod. Затем он будет доступен для подклассов.

Ответ 2

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

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

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

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

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

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

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

Ответ 3

Подумайте об этом так: обычные методы помогают скрыть детали отправки: вы можете набрать 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(), используемый бэкэндом базы данных для импорта библиотеки баз данных, специфичной для поставщика, которая требуется по запросу, является методом класса, потому что она должна выполняться до того, как будут запущены экземпляры конкретного подключения к базе данных но это не может быть простой функцией или статической функцией, потому что они хотят, чтобы она могла вызвать другие поддерживающие методы, которые аналогичным образом должны быть написаны более конкретно в подклассах, чем в их родительском классе. И если вы отправляете функцию или статический класс, тогда вы "забываете" и теряете знания о том, какой класс выполняет инициализацию.

Ответ 4

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

Итак, для этого я использовал переменные класса и декоратор @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." )

В среде "production" я могу указать

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

Ответ 5

Альтернативные конструкторы являются классическим примером.

Ответ 6

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

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), но ваш код становится неуправляемым очень быстро.

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

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

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(), вы удаляете проблему и обе функции копирования могут использоваться бок о бок.

Ответ 7

Когда пользователь регистрируется на моем веб-сайте, объект 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()

Ответ 8

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

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

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

Ответ 9

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

Например:

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

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

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

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

Ответ 10

Я работал с PHP, и недавно я спрашивал себя, что происходит с этим классом? Руководство Python очень техничное и очень короткое в словах, поэтому оно не поможет с пониманием этой функции. Я был googling и googling, и я нашел ответ → 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()

Умный, не?

Ответ 11

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

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

Ответ 12

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

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

Поскольку это просто функция, она может быть объявлена только один раз в любой заданной области (т. class определении 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 и хотел бы получить комментарии, если что-то из этого не так.

Ответ 13

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

Чтобы проиллюстрировать это, вот пример. Обратите внимание, что все экземпляры имеют ссылку на один словарь. Это не шаблон factory, как я понимаю. Это, вероятно, очень уникально для 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

Ответ 14

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

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

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

Ответ 15

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

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

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

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