الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

Reflection And Instrospection في البايثون

بدأه hasan_aljudy في 22 أكتوبر 2008 · 6 رد · 1,614 مشاهدة · في لغة Python
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

لن اكتب الكثير عن الرفلكشن في هذا الموضوع لاني الان لست في المزاج المناسب لعمل ذلك .. لهذا يمكن اعتبار هذا الموضوع من قبيل المقبلات او المشهيات لعل البعض تنفتح شهيته و يكتب عن الموضوع (ان كان ملما به) او يبدأ بالبحث اكثر فيه ان كان يراه لاول مرة او كانت معلوماته عن الموضوع قليلة ..

الرفلكشن reflection مصطلح يطلق و يقصد به قدرة الكود على اكتشاف خصائص كود اخر و تغييره ..

مثلا, اكتشاف الـ functions الموجودة في module او class محدد .. و اضافة حقول او فنكشنز جديدة .. الخ

اعطيكم هذا الكود اللذي كتبته مؤخرا (و اخذت الفكرة حين كنت اقرا عن optparser اللذي هو جزء من الـ library في البايثون)

  1.  
  2. class Option:
  3. def __init__(self, name = '', type = str, default = None, validator = lambda x:x, option_string = None):
  4. self.name = name
  5. self.type = type
  6. self.default = default
  7. self.validator = validator
  8. self.option_string = option_string or name
  9.  
  10. class ParsedOptions: pass
  11.  
  12. class Options:
  13. def __init__(self, option_list=[]):
  14. self.option_list = option_list
  15.  
  16. def add_option( self, **kwargs ):
  17. self.option_list.append( Option( **kwargs ) )
  18.  
  19. def parse( self, getdict ):
  20. options = ParsedOptions()
  21. for opt in self.option_list:
  22. val = getdict.get(opt.option_string, opt.default)
  23. if opt.type: val = opt.type(val) #convert to base type ..
  24. if opt.validator: val = opt.validator(val)
  25. setattr( options, opt.name, val )
  26. return options
  27.  
  28.  
  29.  

الفكرة من هذا الكود هو ان يستخدم في تطبيقات الوب, لتسهيل قرائة الـ GET و ما شابهه ..

على فرض انك تعمل ضمن فريمورك معين لبرمجة الوب في البايثون و ان الفريموورك قام بوضع جميع الـ get arguments في دكشنري و ليكن اسمه GET

بدل من ان تقوم بتحليل كل بارامتر على حدة و تحويله للنوع المناسب ..

   page = int( GET.get('page',1) )

و هو امر يصبح مزعجا بعد فترة من الزمن ..

يمكننا عن طريق الرفلكشن ان نقرا كل البارامترات اللتي نريد و نحولها الى fields ضمن اوبجكت محدد ...

يعني تخيل معي ان تكتب

   options = parse_options( GET );

ليصبح عندك بعدها options.page و هو متغير تم انشائه ديناميكيا (اثناء عمل البرنامج) و يحمل القيمة المخزنة في

   GET['page']

على كل حال, حتى لو لم يكن هذا التطبيق مفيدا جدا ... فهو مثال على الاقل لما يمكن ان يفعله الرفلكشن

على كل حال, هناك تتمة للكود,ساضعها و اضع معها الـ test cases لعلها توضح بعض الطرق اللتي يمكن بها استخدام الكود

و الفكرة من الكود حتى لو لم تكن مفيدة عمليا لكن كما قلت هي توضح ما يمكن ان يفعله الرفلكشن

  1.  
  2. class Option:
  3. def __init__(self, name = '', type = str, default = None, validator = lambda x:x, option_stri
    ng = None):
  4. self.name = name
  5. self.type = type
  6. self.default = default
  7. self.validator = validator
  8. self.option_string = option_string or name
  9.  
  10. class ParsedOptions: pass
  11.  
  12. class Options:
  13. def __init__(self, option_list=[]):
  14. self.option_list = option_list
  15.  
  16. def add_option( self, **kwargs ):
  17. self.option_list.append( Option( **kwargs ) )
  18.  
  19. def parse( self, getdict ):
  20. options = ParsedOptions()
  21. for opt in self.option_list:
  22. val = getdict.get(opt.option_string, opt.default)
  23. if opt.type: val = opt.type(val) #convert to base type ..
  24. if opt.validator: val = opt.validator(val)
  25. setattr( options, opt.name, val )
  26. return options
  27.  
  28. def parse_options( option_list, getdict ):
  29. return Options( option_list ).parse( getdict )
  30.  
  31.  
  32. def get_class_options( klass ):
  33. option_list = []
  34. for attr_name in dir(klass):
  35. attr = getattr(klass,attr_name)
  36. if isinstance( attr, Option ):
  37. option = attr
  38. if not option.name:
  39. option.name = attr_name
  40. option.option_string = option.option_string or option.name #warning: dup code
  41. option_list.append(option)
  42. return option_list
  43.  
  44.  
  45. def parse_class_options( klass, getdict ):
  46. return parse_options( get_class_options( klass ), getdict )
  47.  
  48. import unittest
  49.  
  50. class OptionsTest( unittest.TestCase ):
  51. def test_options(self):
  52. opts = Options()
  53. opts.add_option( name = "page", type = int, default = 1 )
  54. opts.add_option( name = "count", type = int, default = 25 )
  55. opts.add_option( name = "slug", type = str, default = "" )
  56. gdict = {}
  57. parsed_options = opts.parse( gdict )
  58. self.assertEqual( 1, parsed_options.page )
  59. self.assertEqual( 25, parsed_options.count )
  60. self.assertEqual( "", parsed_options.slug )
  61. gdict['page'] = '4'
  62. parsed_options = opts.parse( gdict )
  63. self.assertEqual( 4, parsed_options.page )
  64. self.assertEqual( 25, parsed_options.count )
  65. self.assertEqual( "", parsed_options.slug )
  66. gdict['slug'] = 'main'
  67. parsed_options = opts.parse( gdict )
  68. self.assertEqual( 4, parsed_options.page )
  69. self.assertEqual( 25, parsed_options.count )
  70. self.assertEqual( "main", parsed_options.slug )
  71. gdict['count'] = '35'
  72. parsed_options = opts.parse( gdict )
  73. self.assertEqual( 4, parsed_options.page )
  74. self.assertEqual( 35, parsed_options.count )
  75. self.assertEqual( "main", parsed_options.slug )
  76. del gdict['page']
  77. parsed_options = opts.parse( gdict )
  78. self.assertEqual( 1, parsed_options.page )
  79. self.assertEqual( 35, parsed_options.count )
  80. self.assertEqual( "main", parsed_options.slug )
  81.  
  82. def test_klass_options( self ):
  83. class PageOptions:
  84. page = Option( type=int, default=1 )
  85. count = Option( type=int, default = 14 )
  86. slug = Option( default = "" )
  87. gdict = {}
  88. gdict['page'] = '5'
  89. parsed_options = parse_class_options( PageOptions, gdict )
  90. self.assertEqual( 5, parsed_options.page )
  91. self.assertEqual( 14, parsed_options.count )
  92. self.assertEqual( "", parsed_options.slug )
  93. gdict['count'] = '50'
  94. gdict['slug'] = 'all'
  95. parsed_options = parse_class_options( PageOptions, gdict )
  96. self.assertEqual( 5, parsed_options.page )
  97. self.assertEqual( 50, parsed_options.count )
  98. self.assertEqual( "all", parsed_options.slug )
  99.  
  100.  
  101. if __name__ == '__main__':
  102. unittest.main()
  103.  
  104.  
  105.  

اعتذر ان كان الموضوع مخربط او غير مرتب جيدا لكني اردت فقط ان اطرح بعض المواضيع لتحريك المياه في القسم الجديد لعله ينال حظه من الانتباه و يصبح قسم حقيقي

#2

السلام عليكم و رحمة الله

بداية جميلة لمواضيع القسم أخي حسن و الموضوع جديد بالنسبة لي

ننتظر منك و من أحمد يوسف الكثير لإثراء القسم و معك أتمنى لو تم تثبيت هذا القسم و يكفي الإخوة المبرمجين أن يعرفوا أن أكثر من 30% من أكواد شركة جوجل مكتوبة بالبايثون لإدراك أهمية هذه اللغة.

http://panela.blog-city.com/python_at_goog...in__sdforum.htm

سبحانك اللهم و بحمدك ، أشهد أن لا إله إلا أنت ، أستغفرك ربي و أتوب إليك

مدونتي

http://www.facebook.com/brmjt

#3

و عليكم السلام و رحمة الله,

شكرا لمرورك اخ محمد سمير ..

و هذا توتوريل جيد حول الموضوع:

http://diveintopython.org/power_of_introspection/index.html

#4

اكتشفت مشكلة في الكود .. في الـ default arguments يبدو ان هناك قضية شائكة نوعا ما ..

http://www.network-theory.co.uk/docs/pytut...mentValues.html

Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:

This will print


print f(1) print f(2) print f(3)
	 def f(a, L=[]):		 L.append(a)		 return L

	 [1]	 [1, 2]	 [1, 2, 3]

If you don't want the default to be shared between subsequent calls, you can write the function like this instead:


return L
  def f(a, L=None):		 if L is None:			 L = []		 L.append(a)

لذلك قمت بتغيير في كونستركتر الـ Options

  1.  
  2. class Options:
  3. def __init__(self, option_list=None):
  4. self.option_list = option_list or []
  5.  

#5

حسن الجودي قمت بتحرير الرد الأول والأخير لتغيير [ code ] إلى الواسم الأفضل [ GCode=python ] ويغلق في النهاية بشكل عادي ب [ /GCode]

وتشكر على الموضوع :)

banner_60_468.gif

NOTHING IS IMPOSSIBLE

#6

شكرا استاذ HGB .. تعبناك شوية :)

تم تعديل هذه المشاركة بواسطة hasan_aljudy في 23 أكتوبر 2008 في 21:28

#7

الية الرفلكشن هنا تعتمد اساسا على ثلاث فنكشنات (فنكشنات .. علساس يعني جمع فنكشن!)

اول شي dir و هو يعطي قائمة list باسماء الـ attributes في اي اوبجكت, و الـ attribute هي اي شي في الاوبجكت, سواءا كان field او method

الشيء اللذي يجب ملاحظته هنا هو ان الـ return value هي list of strings, يعني اسم الاتربيوت و ليس الاتربيوت نفسها

مثال على الانتربتر:

  1.  
  2. >>> a = range(1,5)
  3. >>> a
  4. [1, 2, 3, 4]
  5. >>> dir(a)
  6. ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__str__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
  7.  

هنا a هي متغير من نوع list

و من الاتربيوتس اللتي فيه مثلا append, count, extend .. الخ, كلها اسمائها موجودة ضمن اللستة اللتي يعطينا اياها dir

الفنكشن الثاني هو getattr و هو يعطيك الاتربيوت الفعلية من الاوبجكت بناءا على اسمها ..

ساشرح بمثال:

  1.  
  2.  
  3. >>> class Test: pass
  4. ...
  5. >>> c = Test()
  6. >>> c.a = 10
  7. >>> c.a
  8. 10
  9. >>> getattr( c, 'a' )
  10. 10
  11. >>>
  12.  

هنا وضعت اتربيوت جديدة للاوبجكت c بطريقة بايثونية (في بايثون يمكن اضافة اي اتربيوت (يعني حقل او فنكشن) لاي اوبجكت بشكل عادي)

الان اصبح هناك اتربيوت في الاوبجكت c و هذه الاتربيوت اسمها a

اذا استدعيت gettattr على c و مررت اسم الاتربيوت 'a' داخل اقواس, فهذا الفنكشن سيعطيني الاتربيوت نفسها ...

اذا كانت الاتربيوت عبارة عن فنكشن function فانه سيعطيني الفنكشن نفسه (و ليس قيمته) بحيث يمكنني بعد ذلك استدعائه ..

  1.  
  2. >>> r = getattr( a, 'reverse' )
  3. >>> r
  4. <built-in method reverse of list object at 0x01B44990>
  5. >>> r()
  6. >>> a
  7. [4, 3, 2, 1]
  8. >>> r()
  9. >>> a
  10. [1, 2, 3, 4]
  11. >>>
  12.  

الفنكشن الثالث setattr مثل سابقه .. تعطيه الاوبجكت و اسم الاتربيوت, اضافة الى القيمة اللتي تريد ان تعطيها للاتربيوت

و يمكن استخدام هذا الفنكشن لاضافة اتربيوت جديدة لم تكن موجودة ..

  1.  
  2. >>> setattr( c, 'h', 'magic' )
  3. >>> c.h
  4. 'magic'
  5. >>> dir(c)
  6. ['__doc__', '__module__', 'a', 'h']
  7. >>>
  8.  

كيف نستفيد من كل هذا؟؟

* يمكن استكشاف جميع الاتربيوتس ضمن اوبجكت معين و اتخاذ قرارات بناءا على ذلك

* يمكن اضافة حقول جديدة الى اي اوبجكت بحيث تكون اسماء هذه الحقول ليست محددة مسبقا

و طبعا فوائد اخرى ..

و مرة اخرى اعتذر على الخربطة او الارتباك في الموضوع لاني اكتب من دون تحضير مسبق :lol:

مواضيع مشابهة