Яке призначення методів занять?


250

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

Тепер я усвідомлюю, що мені не потрібно використовувати методи 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, наприклад: getAndOptionslyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
У python це статичні методи, а не класичні методи.
Jaykul

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 декоратор, щоб досягти цього.

З моїм простим класом ведення журналу я міг зробити наступне:

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


10

Коли користувач входить на свій веб-сайт, об'єкт 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модуля, це було б порівняно і не зловживати OOP, коли жодних об'єктів насправді не існує (насправді ви можете стверджувати, що це краще, оскільки ви не закінчуєтесь from file_util import FileUtilчи іншим багатослів’ям). Так само можна уникнути конфліктів імен у процесуальному кодексі, роблячи import file_utilзамість цього from file_util import ....
ForgottenUmbrella

7

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

Було б інакше, якби python використовував приватні та захищені члени більше, як це робить Java. У Java мені потрібен статичний метод, щоб мати доступ до приватних членів екземпляра, щоб робити речі. У Python це рідко необхідно.

Зазвичай я бачу людей, що використовують staticmethods та classmethods, коли все, що їм потрібно зробити, це краще використовувати простори імен рівня модулів python.


1
Приватне: ім'я ім'я змінної та захищене: __варіабне ім'я
Бредлі

1
Один незамінний використання UnitTest - х setUpClass і tearDownClass. Ви використовуєте одиничні тести, правда? :)
dbn

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, але йому потрібен екземпляр класу:

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

Розумний, чи не так?


Однією з потенційних проблем у вашому прикладі 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 у цьому сенсі набагато більш розумний, ніж Рубі (за винятком super()). ПОЛУЧЕНО, "магія" відбувається, коли атрибут встановлений або прочитаний. Виклик obj.meth(arg)в Python (на відміну від Ruby) означає просто (obj.meth)(arg). Ніщо нікуди не передається мовчки, obj.methце лише об'єкт, що викликається, який приймає на один аргумент менше функції, з якої він був створений.
Олексій

obj.meth, у свою чергу, означає просто getattr(obj, "meth").
Олексій

Має сенс відповісти, розроблену для людей, які походять з інших поширених мов. Однак одне, чого не вистачає в цій розмові, що пов’язує її разом, - це поняття дескриптора Python . Функції є дескрипторами , і саме тому перший аргумент "автоматично" передається екземпляру, коли функція є атрибутом класу. Це також, як реалізуються методи методів, і чиста реалізація пітону передбачена у зв’язаному HOWTO I. Те, що воно також є декоратором, є своєрідним допоміжним
засобом

2

Це цікава тема. Я вважаю, що python classmethod працює як сингл, а не завод (який повертає створений екземпляр класу). Причина того, що це синглтон, полягає в тому, що існує спільний об'єкт, який виробляється (словник), але лише один раз для класу, але загальним для всіх примірників.

Для ілюстрації цього наводимо приклад. Зауважте, що всі екземпляри мають посилання на єдиний словник. Це не заводський зразок, як я його розумію. Це, мабуть, дуже унікально для пітона.

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 () може повернути всіх відомих студентів. Очевидно, якщо ваш набір екземплярів має більшу структуру, ніж просто набір, ви можете надати методи класу, щоб знати про цю структуру. Студенти.all_of_em (клас = 'юніори')

Такі методи, як правило, призводять до зберігання членів набору екземплярів у структурах даних, корінням яких є змінні класу. Потрібно подбати про те, щоб потім не зірвати сміття.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.