Какова цель методов класса? - PullRequest
238 голосов
/ 01 сентября 2008

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

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

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

Ответы [ 15 ]

171 голосов
/ 01 сентября 2008

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

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

63 голосов
/ 01 сентября 2008

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

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

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

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

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

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

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

46 голосов
/ 19 августа 2010

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

27 голосов
/ 11 мая 2010

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

Поэтому для этого я использовал переменные класса и декоратор @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()
23 голосов
/ 01 сентября 2008

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

10 голосов
/ 14 мая 2011

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

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

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

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

9 голосов
/ 26 марта 2012

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

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

Например:

@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
6 голосов
/ 01 сентября 2008

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

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

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

5 голосов
/ 22 февраля 2010

Раньше я работал с 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()

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

...