سؤال

أنا أقوم بتدريس لغة بايثون بنفسي وكان آخر درس لي هو ذلك بايثون ليست جافا, ، ولذا فقد أمضيت بعض الوقت في تحويل جميع أساليب Class الخاصة بي إلى وظائف.

أدرك الآن أنني لست بحاجة إلى استخدام أساليب الفصل فيما سأفعله static الأساليب في جافا، ولكن الآن لست متأكدا متى سأستخدمها.كل النصائح التي يمكنني العثور عليها حول أساليب Python Class هي على غرار المبتدئين مثلي الذين يجب عليهم الابتعاد عنها، والوثائق القياسية في أكثر حالاتها غموضًا عند مناقشتها.

هل لدى أي شخص مثال جيد لاستخدام طريقة Class في Python أو على الأقل يمكن لأي شخص أن يخبرني متى يمكن استخدام أساليب Class بشكل معقول؟

هل كانت مفيدة؟

المحلول

تُستخدم أساليب الفصل عندما تحتاج إلى أساليب غير خاصة بأي مثيل معين، ولكنها لا تزال تتضمن الفصل بطريقة ما.الشيء الأكثر إثارة للاهتمام بشأنها هو أنه يمكن تجاوزها بواسطة الفئات الفرعية، وهو أمر غير ممكن ببساطة في أساليب Java الثابتة أو وظائف مستوى الوحدة النمطية في Python.

إذا كان لديك فئة MyClass, ، والوظيفة على مستوى الوحدة التي تعمل على MyClass (المصنع، كعب حقن التبعية، وما إلى ذلك)، تجعلها classmethod.وبعد ذلك سوف تكون متاحة للفئات الفرعية.

نصائح أخرى

تعد أساليب المصنع (المنشئات البديلة) بالفعل مثالًا كلاسيكيًا على الأساليب الطبقية.

في الأساس، تعد أساليب الفصل مناسبة في أي وقت ترغب فيه في الحصول على طريقة تتلاءم بشكل طبيعي مع مساحة اسم الفئة، ولكنها غير مرتبطة بمثيل معين من الفئة.

على سبيل المثال، في ممتازة com.unipath وحدة:

الدليل الحالي

  • Path.cwd()
    • إرجاع الدليل الحالي الفعلي؛على سبيل المثال، Path("/tmp/my_temp_dir").هذه طريقة طبقية.
  • .chdir()
    • اجعل نفسك الدليل الحالي.

بما أن الدليل الحالي واسع النطاق، فإن cwd لا تحتوي الطريقة على مثيل معين يجب أن ترتبط به.ومع ذلك، تغيير cwd إلى دليل معين Path يجب أن يكون المثيل بالفعل طريقة مثيل.

أمم...مثل Path.cwd() يعود بالفعل أ Path على سبيل المثال، أعتقد أنه يمكن اعتبارها طريقة مصنع ...

فكر في الأمر بهذه الطريقة:الطرق العادية مفيدة لإخفاء تفاصيل الإرسال:يمكنك كتابة 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() الطريقة، التي تستخدمها الواجهة الخلفية لقاعدة البيانات لاستيراد مكتبة قاعدة البيانات الخاصة بالبائع والتي تحتاجها عند الطلب، هي طريقة فئة لأنها تحتاج إلى التشغيل قبل يبدأ إنشاء مثيلات اتصال قاعدة بيانات معينة - ولكن لا يمكن أن تكون وظيفة بسيطة أو وظيفة ثابتة، لأنهم يريدون أن تكون قادرة على استدعاء أساليب دعم أخرى قد تحتاج بالمثل إلى كتابتها بشكل أكثر تحديدًا في الفئات الفرعية أكثر من تلك الموجودة في الأصل فصل.وإذا قمت بالإرسال إلى وظيفة أو فئة ثابتة، فإنك "تنسى" وتفقد المعرفة حول الفئة التي تقوم بالتهيئة.

كنت أرغب مؤخرًا في الحصول على فئة تسجيل خفيفة الوزن جدًا يمكنها إنتاج كميات متفاوتة من المخرجات اعتمادًا على مستوى التسجيل الذي يمكن تعيينه برمجيًا.لكنني لم أرغب في إنشاء مثيل للفصل في كل مرة أردت فيها إخراج رسالة تصحيح أخطاء أو خطأ أو تحذير.لكنني أردت أيضًا تلخيص عمل منشأة التسجيل هذه وجعلها قابلة لإعادة الاستخدام دون الإعلان عن أي عالميات.

لذلك استخدمت متغيرات الفئة و @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()

المنشئون البديلون هم المثال الكلاسيكي.

أعتقد أن الإجابة الأكثر وضوحا هي أمانكو واحد.يتلخص الأمر في الطريقة التي تريد بها تنظيم التعليمات البرمجية الخاصة بك.يمكنك كتابة كل شيء كوظائف على مستوى الوحدة والتي يتم تضمينها في مساحة اسم الوحدة، أي

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() فإنك تزيل المشكلة وكلا وظيفتي النسخ يمكن استخدامها جنبا إلى جنب.

عندما يقوم مستخدم بتسجيل الدخول على موقع الويب الخاص بي، يتم إنشاء كائن 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()

بصدق؟لم أجد أبدًا استخدامًا للطريقة الثابتة أو طريقة الفصل.لم أر بعد عملية لا يمكن إجراؤها باستخدام وظيفة عامة أو طريقة مثيل.

سيكون الأمر مختلفًا إذا استخدمت لغة python أعضاءً خاصين ومحميين مثلما تفعل Java.في Java، أحتاج إلى طريقة ثابتة حتى أتمكن من الوصول إلى الأعضاء الخاصين في المثيل للقيام بالأشياء.في بايثون، نادرًا ما يكون ذلك ضروريًا.

عادةً ما أرى أشخاصًا يستخدمون الأساليب الثابتة وأساليب الفصل عندما يكون كل ما يحتاجون إليه حقًا هو استخدام مساحات الأسماء على مستوى الوحدة النمطية في بايثون بشكل أفضل.

يسمح لك بكتابة أساليب الفصل العامة التي يمكنك استخدامها مع أي فصل دراسي متوافق.

على سبيل المثال:

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

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

إذا كنت لا تستخدم @classmethod يمكنك القيام بذلك باستخدام الكلمة الأساسية self ولكنها تحتاج إلى مثيل 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

كنت أعمل مع PHP ومؤخرًا كنت أسأل نفسي، ما الذي يحدث مع طريقة الفصل هذه؟يعد دليل Python تقنيًا للغاية وقصيرًا جدًا في الكلمات، لذا لن يساعد في فهم هذه الميزة.لقد كنت أبحث في Google وGoogle ووجدت الإجابة -> 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()

ذكية، أليس كذلك؟

توفر الأساليب الطبقية "سكرًا دلاليًا" (لا أعرف ما إذا كان هذا المصطلح مستخدمًا على نطاق واسع) - أو "ملاءمة دلالية".

مثال:لقد حصلت على مجموعة من الفئات التي تمثل الكائنات.قد ترغب في الحصول على طريقة الفصل all() أو find() لأكتب User.all() أو User.find(firstname='Guido').يمكن القيام بذلك باستخدام وظائف مستوى الوحدة بالطبع ...

ما صدمني للتو، قادمًا من روبي، هو أن ما يسمى ب فصل طريقة وما يسمى مثال الطريقة هي مجرد دالة ذات معنى دلالي مطبق على المعلمة الأولى الخاصة بها، والتي يتم تمريرها بصمت عندما يكون وظيفة ويسمى كوسيلة ل هدف (أي. obj.meth()).

عادة ذلك هدف يجب أن يكون مثالا ولكن @classmethod مصمم طريقة يغير القواعد لاجتياز الفصل الدراسي.يمكنك استدعاء أسلوب فئة على مثيل (إنها مجرد وظيفة) - ستكون الوسيطة الأولى هي فئتها.

لأنه مجرد أ وظيفة, ، لا يمكن الإعلان عنها إلا مرة واحدة في أي نطاق محدد (أي. 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 على طريقة الطبقة.لم أستخدم أيًا منهما هنا لتوضيح أنها مجرد حجة.في روبي، 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 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

كنت أسأل نفسي نفس السؤال عدة مرات.وعلى الرغم من أن الرجال هنا حاولوا جاهدين شرح ذلك، فإن أفضل إجابة (وأبسط) إجابة وجدتها هي IMHO وصف طريقة الفصل في وثائق بايثون.

هناك أيضًا إشارة إلى الطريقة الثابتة.وفي حال كان شخص ما يعرف بالفعل طرق المثيل (التي أفترضها)، فقد تكون هذه الإجابة هي الجزء الأخير لتجميعها معًا...

يمكن العثور على مزيد من التفاصيل والأعمق حول هذا الموضوع أيضًا في الوثائق:التسلسل الهرمي للنوع القياسي (انتقل للأسفل إلى طرق المثيل قسم)

يحدد الفصل مجموعة من الحالات بالطبع.وأساليب العمل الفصلي على الحالات الفردية.تعتبر أساليب الفصل (والمتغيرات) مكانًا لتعليق المعلومات الأخرى المتعلقة بمجموعة المثيلات على الإطلاق.

على سبيل المثال، إذا كان فصلك يحدد مجموعة من الطلاب، فقد تحتاج إلى متغيرات أو طرق تحدد أشياء مثل مجموعة الدرجات التي يمكن للطلاب أن يكونوا أعضاء فيها.

يمكنك أيضًا استخدام أساليب الفصل لتحديد الأدوات اللازمة للعمل على المجموعة بأكملها.على سبيل المثال، قد يقوم Student.all_of_em() بإرجاع جميع الطلاب المعروفين.من الواضح أنه إذا كانت مجموعة المثيلات الخاصة بك تحتوي على بنية أكثر من مجرد مجموعة، فيمكنك توفير أساليب فئة للتعرف على تلك البنية.Student.all_of_em(grade='juniors')

تميل تقنيات مثل هذه إلى تخزين أعضاء مجموعة الحالات في هياكل البيانات المتجذرة في متغيرات الفئة.عليك أن تحرص على تجنب إحباط عملية جمع القمامة بعد ذلك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top