ما هو الغرض من الأساليب الصفية؟
-
09-06-2019 - |
سؤال
أنا أقوم بتدريس لغة بايثون بنفسي وكان آخر درس لي هو ذلك بايثون ليست جافا, ، ولذا فقد أمضيت بعض الوقت في تحويل جميع أساليب 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()
طريقة الدرس على الرابط التالي:
يمكنك أن ترى أن 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')
تميل تقنيات مثل هذه إلى تخزين أعضاء مجموعة الحالات في هياكل البيانات المتجذرة في متغيرات الفئة.عليك أن تحرص على تجنب إحباط عملية جمع القمامة بعد ذلك.