จุดประสงค์ของวิธีการเรียนคืออะไร?


250

ฉันสอนตัวเอง Python และบทเรียนล่าสุดของฉันคือPython ไม่ใช่ Javaดังนั้นฉันจึงใช้เวลาสักครู่เพื่อเปลี่ยนวิธีการเรียนทั้งหมดของฉันให้เป็นฟังก์ชั่น

ตอนนี้ฉันรู้ว่าฉันไม่จำเป็นต้องใช้วิธีการแบบคลาสสำหรับสิ่งที่ฉันจะทำกับstaticวิธีการใน Java แต่ตอนนี้ฉันไม่แน่ใจว่าจะใช้พวกเขาเมื่อใด คำแนะนำทั้งหมดที่ฉันสามารถค้นหาเกี่ยวกับวิธีการเรียนของ Python นั้นเป็นไปตามแนวทางของมือใหม่อย่างที่ฉันควรหลีกเลี่ยงและเอกสารมาตรฐานนั้นทึบแสงที่สุดเมื่อพูดถึงพวกเขา

มีใครบ้างที่เป็นตัวอย่างที่ดีของการใช้วิธีการเรียนแบบชั้นเรียนใน Python หรืออย่างน้อยก็มีคนบอกฉันได้ว่าวิธีการเรียนแบบชั้นเรียนนั้นสามารถใช้อย่างสมเหตุสมผลได้หรือไม่?

คำตอบ:


178

เมธอดของคลาสนั้นใช้สำหรับเมื่อคุณจำเป็นต้องมีเมธอดที่ไม่เฉพาะเจาะจงกับอินสแตนซ์ใด ๆ โดยเฉพาะ แต่ยังคงเกี่ยวข้องกับคลาสในบางวิธี สิ่งที่น่าสนใจที่สุดเกี่ยวกับพวกเขาก็คือพวกเขาสามารถถูกแทนที่ด้วยคลาสย่อยสิ่งที่ไม่สามารถทำได้ในวิธีการคงที่ของ Java หรือฟังก์ชั่นระดับโมดูลของ Python

หากคุณมีระดับMyClassและฟังก์ชั่นระดับโมดูลที่ทำงานบน MyClass (โรงงานพึ่งพาต้นขั้วฉีด ฯลฯ ) classmethodทำให้มันเป็น จากนั้นมันจะพร้อมใช้งานกับคลาสย่อย


ฉันเห็น. แต่ถ้าฉันต้องการจัดหมวดหมู่วิธีที่ไม่จำเป็นต้องใช้ MyClass แต่ยังคงมีเหตุผลถ้าฉันจัดกลุ่มใน MyClass อื่น ๆ ฉันคิดว่าจะย้ายไปที่โมดูลตัวช่วยได้เลย ..
swdev

ฉันมีคำถามที่เกี่ยวข้องกับคำถามดั้งเดิมคุณอาจตอบในเวลาเดียวกันได้ไหม ซึ่งวิธีการเรียกวิธีการเรียนของวัตถุคือ "ดี" หรือ "สำนวนมากขึ้น" obj.cls_mthd(...)หรือtype(obj).cls_mthd(...)?
Alexey

63

วิธีการในโรงงาน (ตัวสร้างทางเลือก) เป็นตัวอย่างคลาสสิกของวิธีการเรียน

โดยทั่วไปวิธีการเรียนจะเหมาะสมเมื่อใดก็ตามที่คุณต้องการมีวิธีการที่เหมาะสมกับเนมสเปซของชั้นเรียน แต่ไม่เกี่ยวข้องกับตัวอย่างเฉพาะของชั้นเรียน

เป็นตัวอย่างในโมดูลunipath ที่ยอดเยี่ยม:

ไดเรกทอรีปัจจุบัน

  • Path.cwd()
    • ส่งคืนไดเรกทอรีปัจจุบันจริง เช่นPath("/tmp/my_temp_dir"). นี่เป็นวิธีการเรียน
  • .chdir()
    • ทำให้ตนเองเป็นไดเรกทอรีปัจจุบัน

เนื่องจากไดเรกทอรีปัจจุบันเป็นกระบวนการกว้างcwdวิธีการจึงไม่มีอินสแตนซ์เฉพาะที่ควรเชื่อมโยง อย่างไรก็ตามการเปลี่ยนเป็นcwdไดเรกทอรีของPathอินสแตนซ์ที่กำหนดควรเป็นวิธีการอินสแตนซ์

อืม ... เช่นเดียวกับที่Path.cwd()ส่งคืนPathอินสแตนซ์ฉันคิดว่ามันอาจจะเป็นวิธีการของโรงงาน ...


1
ใช่ ... วิธีการของโรงงานเป็นสิ่งแรกที่นึกถึง นอกจากนี้ตามแนวเหล่านั้นเป็นสิ่งที่จะใช้รูปแบบซิงเกิลเช่น: getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
ในไพ ธ อนนั้นเป็นวิธีการคงที่ไม่ใช่วิธีการเรียน
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()วิธีการที่แบ็กเอนด์ฐานข้อมูลใช้เพื่อนำเข้าไลบรารีฐานข้อมูลเฉพาะผู้จำหน่ายที่ต้องการตามความต้องการเป็นวิธีการเรียนเพราะมันต้องทำงานก่อนที่อินสแตนซ์ของการเชื่อมต่อฐานข้อมูลที่เฉพาะเจาะจงเริ่มสร้าง - แต่มันไม่สามารถ เป็นฟังก์ชั่นที่เรียบง่ายหรือฟังก์ชั่นคงที่เพราะพวกเขาต้องการที่จะสามารถเรียกคนอื่น ๆ วิธีการสนับสนุนที่อาจจำเป็นต้องมีการเขียนเฉพาะในคลาสย่อยมากกว่าในชั้นผู้ปกครองของพวกเขา และถ้าคุณส่งไปยังฟังก์ชั่นหรือระดับคงที่จากนั้นคุณ "ลืม" และสูญเสียความรู้เกี่ยวกับชั้นเรียนที่กำลังเริ่มต้น


ลิงก์เสียโปรดปรับ
piertoni

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
ดูเหมือนว่าฉันจะไม่ชอบตัวอย่างวิธีการเรียนที่ดีเพราะมันสามารถทำได้ทั้งหมดในวิธีที่ซับซ้อนน้อยกว่าโดยเพียงแค่การทำฟังก์ชั่นระดับโมดูลวิธีการเรียนทั้งหมดและทำไปกับคลาสโดยสิ้นเชิง
martineau

10
โอ้สำคัญกว่านั้น Python ให้คลาสการบันทึกที่ง่ายมาก เลวร้ายยิ่งกว่าการสร้างทางออกที่ดีกว่าฉันได้คิดค้นล้อใหม่ ตบมือสอง แต่มันก็เป็นตัวอย่างการทำงานอย่างน้อย ไม่แน่ใจว่าฉันเห็นด้วยกับการกำจัดชั้นเรียน แต่ในระดับแนวคิด บางครั้งคุณต้องการห่อหุ้มรหัสเพื่อให้สามารถนำกลับมาใช้ใหม่ได้ง่ายและวิธีการบันทึกเป็นเป้าหมายที่ยอดเยี่ยมสำหรับการใช้ซ้ำ
Marvo

3
ทำไมคุณไม่ใช้ staticmethod?
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โมดูลมันจะเทียบเคียงกันได้และไม่ใช้ OOP ในทางที่ผิดเมื่อไม่มีวัตถุใด ๆ เกิดขึ้นจริง ๆ แล้วคุณสามารถโต้แย้งว่ามันเป็นสิ่งที่ดีกว่าเพราะคุณไม่ได้จบลงด้วยการใช้from file_util import FileUtilคำฟุ่มเฟือย ความขัดแย้งชื่อสามารถหลีกเลี่ยงได้ในทำนองเดียวกันในรหัสขั้นตอนโดยการทำแทนimport file_util from file_util import ...
ForgottenUmbrella

7

สุจริต? ฉันไม่เคยพบวิธีใช้สำหรับ staticmethod หรือ classmethod ฉันยังไม่เห็นการดำเนินการที่ไม่สามารถทำได้โดยใช้ฟังก์ชันส่วนกลางหรือวิธีการอินสแตนซ์

มันจะแตกต่างกันถ้างูใหญ่ใช้สมาชิกส่วนบุคคลและได้รับการป้องกันมากกว่าอย่างที่ Java ทำ ใน Java ฉันต้องการวิธีการแบบคงที่เพื่อให้สามารถเข้าถึงสมาชิกส่วนตัวของอินสแตนซ์เพื่อทำสิ่งต่างๆ ใน Python นั้นไม่ค่อยจำเป็น

โดยปกติแล้วฉันเห็นคนใช้ staticmethods และ classmethods เมื่อสิ่งที่พวกเขาต้องทำคือใช้ namespaces ระดับโมดูลของ python ที่ดีกว่า


1
ส่วนตัว: _variable_name และได้รับการป้องกัน: __variable_name
Bradley Kreider

1
การใช้งานอย่างหนึ่งที่ขาดไม่ได้คือsetUpClass และ tearDownClass ของ unittest คุณใช้ unittests ใช่ไหม? :)
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คุณสามารถทำได้ด้วยคำหลักของตัวเอง แต่ต้องการอินสแตนซ์ของ 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 นั้นเป็นศัพท์เทคนิคและสั้นมากดังนั้นจึงไม่ช่วยให้เข้าใจคุณลักษณะนั้น ฉันถูก 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

อย่างไรก็ตามในไพ ธ อนเราสามารถเพิ่ม @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')สามารถทำได้โดยใช้ฟังก์ชั่นระดับโมดูลแน่นอน ...


ตัวอย่างคุณสมมติว่าคลาสกำลังติดตามอินสแตนซ์ทั้งหมดของมันและสามารถเข้าถึงได้หลังจากที่สร้างขึ้น - สิ่งที่ไม่ได้ทำโดยอัตโนมัติ
martineau

1
"semantic sugar" ฟังดูเข้ากันเลยกับ "syntactic sugar"
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เป็นคำหลัก

ตัดกับทับทิม:

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>

วิธีการระดับหลามเป็นเพียงฟังก์ชั่นการตกแต่งและคุณสามารถใช้เทคนิคเดียวกันเพื่อสร้างตกแต่งของคุณเอง วิธีการตกแต่งล้อมวิธีจริง (ในกรณีที่@classmethodมันผ่านอาร์กิวเมนต์ระดับเพิ่มเติม) วิธีการพื้นฐานยังคงมีซ่อนอยู่แต่ยังคงสามารถเข้าถึงได้


เชิงอรรถ: ฉันเขียนสิ่งนี้หลังจากการปะทะกันของชื่อระหว่างวิธีการเรียนและอินสแตนซ์ทำให้ความอยากรู้ของฉันป่องๆ ฉันยังห่างไกลจากผู้เชี่ยวชาญ Python และต้องการแสดงความคิดเห็นหากสิ่งนี้ผิด


"ซึ่งถูกส่งผ่านอย่างเงียบ ๆ เมื่อฟังก์ชันถูกเรียกว่าเป็นวิธีการของวัตถุ" - ฉันเชื่อว่านี่ไม่แน่นอน AFICT ไม่มีอะไรที่ "ผ่านอย่างเงียบ ๆ " ใน Python IMO, Python เหมาะสมกว่า Ruby ในแง่นี้มาก (ยกเว้นsuper()) AFICT "เวทมนตร์" จะเกิดขึ้นเมื่อมีการตั้งค่าหรืออ่านแอตทริบิวต์ โทรobj.meth(arg)ในหลาม (เหมือนทับทิม) (obj.meth)(arg)หมายถึงเพียงแค่ ไม่มีอะไรถูกส่งผ่านอย่างเงียบ ๆ ทุกที่obj.methเป็นเพียงวัตถุที่เรียกได้ซึ่งยอมรับอาร์กิวเมนต์หนึ่งตัวที่น้อยกว่าฟังก์ชันที่มันถูกสร้างขึ้น
Alexey

obj.methgetattr(obj, "meth")ในทางกลับกันก็หมายความว่า
Alexey

ทำให้รู้สึกถึงคำตอบที่เหมาะสำหรับผู้ที่มาจากภาษาทั่วไปอื่น ๆ แต่สิ่งหนึ่งที่ขาดหายไปจากการสนทนานี้ที่ความสัมพันธ์มันเข้าด้วยกันเป็นความคิดของที่บ่งหลาม ฟังก์ชั่นเป็นตัวอธิบายและนั่นคือวิธีที่อาร์กิวเมนต์แรก "automagically" ได้รับการส่งผ่านอินสแตนซ์เมื่อฟังก์ชั่นเป็นคุณลักษณะที่ชั้น นอกจากนี้ยังเป็นวิธีการใช้งานของคลาสและการใช้งาน Python อย่างแท้จริงมีให้ใน HOWTO I ที่เชื่อมโยง ความจริงที่ว่ามันยังเป็นมัณฑนากรเป็นประเภทเสริม
juanpa.arrivillaga

2

นี่เป็นหัวข้อที่น่าสนใจ สิ่งที่ฉันทำในตอนนั้นคือคลาสของ python ทำงานเหมือน singleton แทนที่จะเป็นโรงงาน (ซึ่งคืนค่าอินสแตนซ์ของคลาสที่ผลิต) เหตุผลที่เป็น singleton ก็คือมีวัตถุทั่วไปที่ผลิต (พจนานุกรม) แต่เพียงครั้งเดียวสำหรับชั้นเรียน แต่ใช้ร่วมกันโดยทุกกรณี

เพื่อแสดงตัวอย่างนี่คือตัวอย่าง โปรดทราบว่าอินสแตนซ์ทั้งหมดมีการอ้างอิงไปยังพจนานุกรมเดียว นี่ไม่ใช่รูปแบบของโรงงานตามที่ฉันเข้าใจ นี่อาจเป็นเอกลักษณ์ของงูหลามมาก

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

ฉันถามตัวเองด้วยคำถามเดียวกันสองสามครั้ง และถึงแม้ว่าคนที่นี่พยายามจะอธิบายอย่างหนัก แต่ IMHO เป็นคำตอบที่ดีที่สุด (และง่ายที่สุด) ที่ฉันได้พบคือคำอธิบายของวิธีการเรียนในเอกสาร 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 (เกรด = 'จูเนียร์')

เทคนิคเช่นนี้มักจะนำไปสู่การจัดเก็บสมาชิกของชุดอินสแตนซ์ลงในโครงสร้างข้อมูลที่ถูกฝังในตัวแปรคลาส คุณต้องดูแลเพื่อหลีกเลี่ยงการเก็บรวบรวมขยะในขณะนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.