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


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

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

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

15 223

15 ответов:

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

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

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

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

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

текущей директории

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

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

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

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

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

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

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

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

поэтому я использовал переменные класса и @classmethod декоратор добиться этого.

С помощью моего простого класса ведения журнала, я мог бы сделать следующее:

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

классический пример-альтернативные конструкторы.

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

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

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

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

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

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

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

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

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

например:

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

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

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

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

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

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

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

я раньше работал с PHP и недавно я спрашивал себя, что происходит с этим classmethod? Руководство 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 decorator и, таким образом, можно иметь выход 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()

умный, да?

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

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

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

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

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

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

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

рассмотрим следующий пример:

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 это ключевое слово.

контраст с Рубином:

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.

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

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

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

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

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

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