استكشاف وفهم لغة بايثون من خلال مقتطفات مفاجئة.
الترجمات: الصينية 中文 | الفيتنامية تينغ فيت | الاسبانية الاسبانية | الكورية 한국어 | الروسية Русский | الألمانية الألمانية | أضف الترجمة
أوضاع أخرى: موقع تفاعلي | دفتر تفاعلي
بايثون، كونها لغة برمجة عالية المستوى ومصممة بشكل جميل ومعتمدة على المترجم الفوري، توفر لنا العديد من الميزات لراحة المبرمج. لكن في بعض الأحيان، قد لا تبدو نتائج مقتطف بايثون واضحة للوهلة الأولى.
إليك مشروعًا ممتعًا يحاول شرح ما يحدث بالضبط تحت الغطاء لبعض المقتطفات غير البديهية والميزات الأقل شهرة في بايثون.
في حين أن بعض الأمثلة التي تراها أدناه قد لا تكون WTFs بالمعنى الحقيقي، إلا أنها ستكشف عن بعض الأجزاء المثيرة للاهتمام في Python والتي قد لا تكون على علم بها. أجدها طريقة رائعة لتعلم الأجزاء الداخلية من لغة البرمجة، وأعتقد أنك ستجدها مثيرة للاهتمام أيضًا!
إذا كنت من مبرمجي لغة بايثون ذوي الخبرة، فيمكنك اعتبار الأمر تحديًا لإنجاز معظم المهام بشكل صحيح من المحاولة الأولى. ربما تكون قد واجهت بعضًا منها من قبل، وقد أكون قادرًا على إحياء ذكرياتك القديمة الجميلة! ؟
ملاحظة: إذا كنت من القراء العائدين، يمكنك التعرف على التعديلات الجديدة هنا (الأمثلة التي تحمل علامة النجمة هي تلك التي تمت إضافتها في أحدث مراجعة رئيسية).
إذن، ها نحن ذا...
is
المشغلis not ...
ليس is (not ...)
del
goto
، ولكن لماذا؟+=
أسرعdict
*dict
s *جميع الأمثلة منظمة كما يلي:
◀ بعض العناوين الفاخرة
# Set up the code. # Preparation for the magic...الإخراج (إصدار (إصدارات) بايثون):
> >> triggering_statement Some unexpected output(اختياري): سطر واحد يصف المخرجات غير المتوقعة.
توضيح:
- شرح موجز لما يحدث ولماذا يحدث.
# Set up code # More examples for further clarification (if necessary)الإخراج (إصدار (إصدارات) بايثون):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
ملاحظة: تم اختبار جميع الأمثلة على مترجم Python 3.5.2 التفاعلي، ويجب أن تعمل مع جميع إصدارات Python ما لم يتم تحديدها صراحةً قبل الإخراج.
الطريقة الجيدة للحصول على أقصى استفادة من هذه الأمثلة، في رأيي، هي قراءتها بالترتيب التسلسلي، ولكل مثال:
لسبب ما، أصبح عامل التشغيل "Walrus" ( :=
) في Python 3.8 شائعًا جدًا. دعونا التحقق من ذلك،
1.
# Python version 3.8+
> >> a = "wtf_walrus"
> >> a
'wtf_walrus'
> >> a := "wtf_walrus"
File "<stdin>" , line 1
a := "wtf_walrus"
^
SyntaxError : invalid syntax
>> > ( a := "wtf_walrus" ) # This works though
'wtf_walrus'
> >> a
'wtf_walrus'
2 .
# Python version 3.8+
> >> a = 6 , 9
> >> a
( 6 , 9 )
> >> ( a := 6 , 9 )
( 6 , 9 )
> >> a
6
> >> a , b = 6 , 9 # Typical unpacking
> >> a , b
( 6 , 9 )
>> > ( a , b = 16 , 19 ) # Oops
File "<stdin>" , line 1
( a , b = 16 , 19 )
^
SyntaxError : invalid syntax
>> > ( a , b := 16 , 19 ) # This prints out a weird 3-tuple
( 6 , 16 , 19 )
>> > a # a is still unchanged?
6
>> > b
16
تجديد سريع لمشغل الفظ
تم تقديم عامل Walrus ( :=
) في Python 3.8، ويمكن أن يكون مفيدًا في المواقف التي تريد فيها تعيين قيم للمتغيرات داخل التعبير.
def some_func ():
# Assume some expensive computation here
# time.sleep(1000)
return 5
# So instead of,
if some_func ():
print ( some_func ()) # Which is bad practice since computation is happening twice
# or
a = some_func ()
if a :
print ( a )
# Now you can concisely write
if a := some_func ():
print ( a )
الإخراج (> 3.8):
5
5
5
أدى هذا إلى حفظ سطر واحد من التعليمات البرمجية، ومنع ضمنيًا استدعاء some_func
مرتين.
"تعبير المهمة" غير المقيد (استخدام عامل الفظ)، مقيد في المستوى الأعلى، ومن هنا خطأ SyntaxError
في عبارة a := "wtf_walrus"
للمقتطف الأول. عملت الأقواس كما هو متوقع وتم تعيين a
كالعادة، لا يُسمح بوضع قوسين للتعبير الذي يحتوي على عامل التشغيل =
. ومن هنا الخطأ النحوي في (a, b = 6, 9)
.
بناء جملة عامل Walrus هو بالصيغة NAME:= expr
، حيث NAME
هو معرف صالح، و expr
هو تعبير صالح. وبالتالي، لا يتم دعم التعبئة والتفريغ التكراري مما يعني،
(a := 6, 9)
يكافئ ((a := 6), 9)
وفي النهاية (a, 9)
(حيث تكون قيمة a
6')
> >> ( a := 6 , 9 ) == (( a := 6 ), 9 )
True
> >> x = ( a := 696 , 9 )
> >> x
( 696 , 9 )
> >> x [ 0 ] is a # Both reference same memory location
True
وبالمثل، (a, b := 16, 19)
تعادل (a, (b := 16), 19)
وهي ليست سوى صف ثلاثي.
1.
> >> a = "some_string"
> >> id ( a )
140420665652016
> >> id ( "some" + "_" + "string" ) # Notice that both the ids are same.
140420665652016
2.
> >> a = "wtf"
> >> b = "wtf"
> >> a is b
True
> >> a = "wtf!"
> >> b = "wtf!"
> >> a is b
False
3.
> >> a , b = "wtf!" , "wtf!"
> >> a is b # All versions except 3.7.x
True
> >> a = "wtf!" ; b = "wtf!"
> >> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print ( a is b )
# prints True when the module is invoked!
4.
الإخراج (<Python3.7)
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
فمن المنطقي، أليس كذلك؟
'wtf'
ولكن لن يتم احتجاز ''.join(['w', 't', 'f'])
)'wtf!'
لم يتم اعتقاله بسبب !
. يمكن العثور على تطبيق CPython لهذه القاعدة هنا a
و b
على "wtf!"
في نفس السطر، يقوم مترجم بايثون بإنشاء كائن جديد، ثم يشير إلى المتغير الثاني في نفس الوقت. إذا قمت بذلك في أسطر منفصلة، فلن "يعرف" أن هناك بالفعل "wtf!"
ككائن (لأن "wtf!"
لم يتم تضمينه ضمنيًا وفقًا للحقائق المذكورة أعلاه). إنه تحسين وقت الترجمة. لا ينطبق هذا التحسين على إصدارات 3.7.x من CPython (راجع هذه المشكلة لمزيد من المناقشة).a, b = "wtf!", "wtf!"
عبارة عن عبارة مفردة، بينما a = "wtf!"; b = "wtf!"
هما بيانان في سطر واحد. وهذا ما يفسر سبب اختلاف الهويات في a = "wtf!"; b = "wtf!"
واشرح أيضًا سبب تشابههما عند استدعائهما في some_file.py
'a'*20
قد تم استبداله بـ 'aaaaaaaaaaaaaaaaaaaa'
أثناء الترجمة لتوفير بضع دورات على مدار الساعة أثناء وقت التشغيل. يحدث الطي الثابت فقط للسلاسل التي يقل طولها عن 21. (لماذا؟ تخيل حجم ملف .pyc
الذي تم إنشاؤه نتيجة للتعبير 'a'*10**10
). إليك مصدر التنفيذ لنفسه. > >> ( False == False ) in [ False ] # makes sense
False
> >> False == ( False in [ False ]) # makes sense
False
> >> False == False in [ False ] # now what?
True
> >> True is False == False
False
> >> False is False is False
True
> >> 1 > 0 < 1
True
> >> ( 1 > 0 ) < 1
False
> >> 1 > ( 0 < 1 )
False
وفقًا لـ https://docs.python.org/3/reference/expressions.html#comparisons
رسميًا، إذا كانت a، b، c، ...، y، z عبارة عن تعبيرات وكانت op1، op2، ...، opN عبارة عن عوامل مقارنة، فإن a op1 b op2 c ... y opN z مكافئ لـ op1 b و b op2 c و ... y opN z، فيما عدا أنه يتم تقييم كل تعبير مرة واحدة على الأكثر.
على الرغم من أن مثل هذا السلوك قد يبدو سخيفًا بالنسبة لك في الأمثلة المذكورة أعلاه، إلا أنه رائع مع أشياء مثل a == b == c
و 0 <= x <= 100
.
False is False is False
يعادل (False is False) and (False is False)
True is False == False
يعادل (True is False) and (False == False)
وبما أن الجزء الأول من العبارة ( True is False
) يتم تقييمه إلى False
، فإن التعبير العام يتم تقييمه إلى False
.1 > 0 < 1
يعادل (1 > 0) and (0 < 1)
والذي يتم تقييمه على أنه True
.(1 > 0) < 1
يعادل True < 1
و > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
إلى False
is
المشغلفيما يلي مثال مشهور جدًا موجود في جميع أنحاء الإنترنت.
1.
> >> a = 256
> >> b = 256
> >> a is b
True
> >> a = 257
> >> b = 257
> >> a is b
False
2.
> >> a = []
> >> b = []
> >> a is b
False
> >> a = tuple ()
> >> b = tuple ()
> >> a is b
True
3. الإخراج
> >> a , b = 257 , 257
> >> a is b
True
الإخراج (Python 3.7.x على وجه التحديد)
> >> a , b = 257 , 257
> >> a is b
False
الفرق بين is
و ==
is
عامل التشغيل بالتحقق مما إذا كان كلا المعاملين يشيران إلى نفس الكائن (أي، يتحقق مما إذا كانت هوية المعاملين متطابقة أم لا).==
يقارن عامل التشغيل قيم كل من المعاملات ويتحقق مما إذا كانت متطابقة.is
الأمر بالنسبة للمساواة المرجعية و ==
للمساواة في القيمة. مثال لتوضيح الأمور > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
256
كائن موجود ولكن 257
ليس كذلك
عند بدء تشغيل بايثون، سيتم تخصيص الأرقام من -5
إلى 256
. يتم استخدام هذه الأرقام كثيرًا، لذا فمن المنطقي أن تكون جاهزة.
نقلا عن https://docs.python.org/3/c-api/long.html
يحتفظ التطبيق الحالي بمصفوفة من الكائنات الصحيحة لجميع الأعداد الصحيحة بين -5 و256، وعندما تقوم بإنشاء int في هذا النطاق، فإنك تحصل فقط على مرجع للكائن الموجود. لذلك يجب أن يكون من الممكن تغيير قيمة 1. وأظن أن سلوك بايثون، في هذه الحالة، غير محدد. :-)
> >> id ( 256 )
10922528
> >> a = 256
> >> b = 256
> >> id ( a )
10922528
> >> id ( b )
10922528
> >> id ( 257 )
140084850247312
> >> x = 257
> >> y = 257
> >> id ( x )
140084850247440
> >> id ( y )
140084850247344
هنا المترجم ليس ذكيًا بما فيه الكفاية أثناء تنفيذ y = 257
ليدرك أننا أنشأنا بالفعل عددًا صحيحًا للقيمة 257,
وبالتالي يستمر في إنشاء كائن آخر في الذاكرة.
ينطبق التحسين المماثل على الكائنات الأخرى غير القابلة للتغيير مثل الصفوف الفارغة أيضًا. نظرًا لأن القوائم قابلة للتغيير، ولهذا السبب فإن [] is []
ستُرجع False
و () is ()
ستُرجع True
. وهذا ما يفسر المقتطف الثاني لدينا. دعنا ننتقل إلى الثالث،
يشير كل من a
و b
إلى نفس الكائن عند تهيئته بنفس القيمة في نفس السطر.
الإخراج
> >> a , b = 257 , 257
> >> id ( a )
140640774013296
> >> id ( b )
140640774013296
> >> a = 257
> >> b = 257
> >> id ( a )
140640774013392
> >> id ( b )
140640774013488
عندما يتم تعيين a وb على 257
في نفس السطر، يقوم مترجم Python بإنشاء كائن جديد، ثم يشير إلى المتغير الثاني في نفس الوقت. إذا قمت بذلك على أسطر منفصلة، فلن "يعرف" أن هناك بالفعل 257
ككائن.
إنه تحسين للمترجم وينطبق بشكل خاص على البيئة التفاعلية. عندما تقوم بإدخال سطرين في مترجم مباشر، يتم تجميعهما بشكل منفصل، وبالتالي يتم تحسينهما بشكل منفصل. إذا كنت ستجرب هذا المثال في ملف .py
، فلن ترى نفس السلوك، لأنه يتم تجميع الملف كله مرة واحدة. لا يقتصر هذا التحسين على الأعداد الصحيحة، فهو يعمل مع أنواع البيانات الأخرى غير القابلة للتغيير مثل السلاسل (راجع "السلاسل مثال صعب") والعوامات أيضًا،
> >> a , b = 257.0 , 257.0
> >> a is b
True
لماذا لم ينجح هذا مع Python 3.7؟ السبب المجرد هو أن تحسينات المترجم هذه خاصة بالتنفيذ (أي قد تتغير مع الإصدار ونظام التشغيل وما إلى ذلك). ما زلت أعرف ما هو التغيير الدقيق في التنفيذ الذي يسبب المشكلة، يمكنك التحقق من هذه المشكلة للحصول على التحديثات.
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
الإخراج:
> >> some_dict [ 5.5 ]
"JavaScript"
> >> some_dict [ 5.0 ] # "Python" destroyed the existence of "Ruby"?
"Python"
> >> some_dict [ 5 ]
"Python"
> >> complex_five = 5 + 0j
> >> type ( complex_five )
complex
> >> some_dict [ complex_five ]
"Python"
إذًا، لماذا تنتشر لغة بايثون في كل مكان؟
تفرد المفاتيح في قاموس بايثون هو بالتكافؤ وليس بالهوية. لذا، على الرغم من أن 5
و 5.0
و 5 + 0j
هي كائنات مختلفة من أنواع مختلفة، نظرًا لأنها متساوية، فلا يمكن أن يكون كلاهما في نفس dict
(أو set
). بمجرد إدراج أي منها، فإن محاولة البحث عن أي مفتاح مميز ولكن مكافئ ستنجح باستخدام القيمة الأصلية المعينة (بدلاً من الفشل باستخدام KeyError
):
> >> 5 == 5.0 == 5 + 0j
True
> >> 5 is not 5.0 is not 5 + 0j
True
> >> some_dict = {}
> >> some_dict [ 5.0 ] = "Ruby"
> >> 5.0 in some_dict
True
> >> ( 5 in some_dict ) and ( 5 + 0j in some_dict )
True
وينطبق هذا عند تعيين عنصر أيضًا. لذلك عندما تفعل some_dict[5] = "Python"
، تبحث Python عن العنصر الموجود بالمفتاح المكافئ 5.0 -> "Ruby"
، وتكتب فوق قيمته في مكانه، وتترك المفتاح الأصلي بمفرده.
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
فكيف يمكننا تحديث المفتاح إلى 5
(بدلاً من 5.0
)؟ لا يمكننا إجراء هذا التحديث فعليًا، ولكن ما يمكننا فعله هو حذف المفتاح أولاً ( del some_dict[5.0]
) ، ثم تعيينه ( some_dict[5]
) للحصول على العدد الصحيح 5
كمفتاح بدلاً من العائم 5.0
، على الرغم من أن هذا قد يكون ضروريًا في حالات نادرة.
كيف وجدت بايثون 5
في قاموس يحتوي على 5.0
؟ تقوم Python بذلك في وقت ثابت دون الحاجة إلى فحص كل عنصر باستخدام وظائف التجزئة. عندما تبحث بايثون عن مفتاح foo
في أمر ما، فإنها تحسب أولاً hash(foo)
(التي تعمل في وقت ثابت). نظرًا لأنه من المطلوب في Python أن تكون الكائنات التي تتم مقارنتها متساوية أيضًا لها نفس قيمة التجزئة (المستندات هنا)، 5
و 5.0
و 5 + 0j
لها نفس قيمة التجزئة.
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
ملاحظة: العكس ليس صحيحًا بالضرورة: فالكائنات ذات قيم التجزئة المتساوية قد تكون في حد ذاتها غير متساوية. (يتسبب هذا في ما يُعرف باسم تصادم التجزئة، ويقلل من أداء الوقت الثابت الذي توفره التجزئة عادةً.)
class WTF :
pass
الإخراج:
> >> WTF () == WTF () # two different instances can't be equal
False
> >> WTF () is WTF () # identities are also different
False
> >> hash ( WTF ()) == hash ( WTF ()) # hashes _should_ be different as well
True
> >> id ( WTF ()) == id ( WTF ())
True
عندما تم استدعاء id
، أنشأت Python كائن فئة WTF
وتمريره إلى وظيفة id
. تأخذ وظيفة id
id
الخاص به (موقع الذاكرة الخاص به) وترمي الكائن بعيدًا. تم تدمير الكائن.
عندما نقوم بذلك مرتين متتاليتين، تقوم بايثون بتخصيص نفس موقع الذاكرة لهذا الكائن الثاني أيضًا. نظرًا لأن id
(في CPython) يستخدم موقع الذاكرة كمعرف الكائن، فإن معرف الكائنين هو نفسه.
لذلك، يكون معرف الكائن فريدًا فقط طوال عمر الكائن. بعد تدمير الكائن، أو قبل إنشائه، يمكن أن يكون لشيء آخر نفس المعرف.
ولكن لماذا تم تقييم عامل is
إلى False
؟ دعونا نرى مع هذا المقتطف.
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
الإخراج:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
وكما قد تلاحظ، فإن الترتيب الذي تم به تدمير الأشياء هو ما أحدث الفارق هنا.
from collections import OrderedDict
dictionary = dict ()
dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
ordered_dict = OrderedDict ()
ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
another_ordered_dict = OrderedDict ()
another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
class DictWithHash ( dict ):
"""
A dict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
class OrderedDictWithHash ( OrderedDict ):
"""
An OrderedDict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
الإخراج
> >> dictionary == ordered_dict # If a == b
True
> >> dictionary == another_ordered_dict # and b == c
True
> >> ordered_dict == another_ordered_dict # then why isn't c == a ??
False
# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
TypeError : unhashable type : 'dict'
# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
> >> dictionary = DictWithHash ()
> >> dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
> >> ordered_dict = OrderedDictWithHash ()
> >> ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
> >> another_ordered_dict = OrderedDictWithHash ()
> >> another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
1
> >> len ({ ordered_dict , another_ordered_dict , dictionary }) # changing the order
2
ماذا يحدث هنا؟
السبب وراء عدم وجود مساواة متعدية بين dictionary
و ordered_dict
و another_ordered_dict
هو الطريقة التي يتم بها تنفيذ طريقة __eq__
في فئة OrderedDict
. من المستندات
اختبارات المساواة بين كائنات OrderedDict حساسة للترتيب ويتم تنفيذها كـ
list(od1.items())==list(od2.items())
. تعتبر اختبارات المساواة بين كائناتOrderedDict
وكائنات التعيين الأخرى غير حساسة للترتيب مثل القواميس العادية.
سبب هذه المساواة في السلوك هو أنه يسمح باستبدال كائنات OrderedDict
مباشرة في أي مكان يتم فيه استخدام قاموس عادي.
حسنًا، لماذا يؤثر تغيير الترتيب على طول كائن set
الذي تم إنشاؤه؟ الجواب هو عدم وجود المساواة المتعدية فقط. نظرًا لأن المجموعات عبارة عن مجموعات "غير مرتبة" من العناصر الفريدة، فإن الترتيب الذي يتم إدراج العناصر به لا يهم. ولكن في هذه الحالة، لا يهم. دعونا نقسمها قليلاً،
> >> some_set = set ()
> >> some_set . add ( dictionary ) # these are the mapping objects from the snippets above
> >> ordered_dict in some_set
True
> >> some_set . add ( ordered_dict )
> >> len ( some_set )
1
> >> another_ordered_dict in some_set
True
> >> some_set . add ( another_ordered_dict )
> >> len ( some_set )
1
> >> another_set = set ()
> >> another_set . add ( ordered_dict )
> >> another_ordered_dict in another_set
False
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
> >> dictionary in another_set
True
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
لذا فإن التناقض يرجع إلى أن another_ordered_dict in another_set
False
لأن ordered_dict
كان موجودًا بالفعل في another_set
وكما لوحظ من قبل، ordered_dict == another_ordered_dict
is False
.
def some_func ():
try :
return 'from_try'
finally :
return 'from_finally'
def another_func ():
for _ in range ( 3 ):
try :
continue
finally :
print ( "Finally!" )
def one_more_func (): # A gotcha!
try :
for i in range ( 3 ):
try :
1 / i
except ZeroDivisionError :
# Let's throw it here and handle it outside for loop
raise ZeroDivisionError ( "A trivial divide by zero error" )
finally :
print ( "Iteration" , i )
break
except ZeroDivisionError as e :
print ( "Zero division error occurred" , e )
الإخراج:
> >> some_func ()
'from_finally'
> >> another_func ()
Finally !
Finally !
Finally !
>> > 1 / 0
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
ZeroDivisionError : division by zero
> >> one_more_func ()
Iteration 0
return
أو break
أو continue
في مجموعة try
الخاصة بعبارة "try...أخيرًا"، يتم أيضًا تنفيذ الجملة finally
عند الخروج.return
الأخير الذي تم تنفيذه. بما أن الجملة finally
تُنفَّذ دائمًا، فإن عبارة return
التي يتم تنفيذها في الجملة finally
ستكون دائمًا آخر عبارة يتم تنفيذها.return
أو break
، فسيتم تجاهل الاستثناء المحفوظ مؤقتًا. some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
الإخراج:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
يتم تعريف عبارة for
في قواعد بايثون على النحو التالي:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
حيث exprlist
هو هدف المهمة. هذا يعني أنه يتم تنفيذ ما يعادل {exprlist} = {next_value}
لكل عنصر في العنصر القابل للتكرار. مثال مثير للاهتمام يوضح ذلك:
for i in range ( 4 ):
print ( i )
i = 10
الإخراج:
0
1
2
3
هل كنت تتوقع أن يتم تشغيل الحلقة مرة واحدة فقط؟
توضيح:
i = 10
لا يؤثر أبدًا على تكرارات الحلقة بسبب طريقة عمل الحلقات في بايثون. قبل بداية كل تكرار، يتم تفكيك العنصر التالي الذي يوفره المكرر ( range(4)
في هذه الحالة) وتعيين متغيرات القائمة المستهدفة ( i
في هذه الحالة). تعطي الدالة enumerate(some_string)
قيمة جديدة i
(عداد يصعد لأعلى) وحرفًا من some_string
في كل تكرار. ثم يقوم بعد ذلك بتعيين مفتاح i
(الذي تم تعيينه للتو) للقاموس some_dict
على هذا الحرف. يمكن تبسيط عملية فتح الحلقة على النحو التالي:
> >> i , some_dict [ i ] = ( 0 , 'w' )
> >> i , some_dict [ i ] = ( 1 , 't' )
> >> i , some_dict [ i ] = ( 2 , 'f' )
> >> some_dict
1.
array = [ 1 , 8 , 15 ]
# A typical generator expression
gen = ( x for x in array if array . count ( x ) > 0 )
array = [ 2 , 8 , 22 ]
الإخراج:
> >> print ( list ( gen )) # Where did the other values go?
[ 8 ]
2.
array_1 = [ 1 , 2 , 3 , 4 ]
gen_1 = ( x for x in array_1 )
array_1 = [ 1 , 2 , 3 , 4 , 5 ]
array_2 = [ 1 , 2 , 3 , 4 ]
gen_2 = ( x for x in array_2 )
array_2 [:] = [ 1 , 2 , 3 , 4 , 5 ]
الإخراج:
> >> print ( list ( gen_1 ))
[ 1 , 2 , 3 , 4 ]
> >> print ( list ( gen_2 ))
[ 1 , 2 , 3 , 4 , 5 ]
3.
array_3 = [ 1 , 2 , 3 ]
array_4 = [ 10 , 20 , 30 ]
gen = ( i + j for i in array_3 for j in array_4 )
array_3 = [ 4 , 5 , 6 ]
array_4 = [ 400 , 500 , 600 ]
الإخراج:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
في تعبير المولد، يتم تقييم جملة in
في وقت الإعلان، ولكن يتم تقييم الجملة الشرطية في وقت التشغيل.
لذلك قبل وقت التشغيل، يتم إعادة تعيين array
إلى القائمة [2, 8, 22]
، وبما أنه من بين 1
و 8
و 15
، فإن عدد 8
فقط أكبر من 0
، فإن المولد ينتج 8
فقط.
ترجع الاختلافات في مخرجات g1
و g2
في الجزء الثاني إلى الطريقة التي يتم بها إعادة تعيين قيم المتغيرين array_1
و array_2
.
في الحالة الأولى، يرتبط array_1
بالكائن الجديد [1,2,3,4,5]
وبما أن جملة in
يتم تقييمها في وقت الإعلان فإنها لا تزال تشير إلى الكائن القديم [1,2,3,4]
(الذي لم يتم تدميره).
في الحالة الثانية، يقوم تعيين الشريحة إلى array_2
بتحديث نفس الكائن القديم [1,2,3,4]
إلى [1,2,3,4,5]
. ومن ثم لا يزال لدى كل من g2
و array_2
إشارة إلى نفس الكائن (والذي تم تحديثه الآن إلى [1,2,3,4,5]
).
حسنًا، وفقًا للمنطق الذي تمت مناقشته حتى الآن، ألا ينبغي أن تكون قيمة list(gen)
في المقتطف الثالث [11, 21, 31, 12, 22, 32, 13, 23, 33]
؟ (لأن array_3
و array_4
سيتصرفان تمامًا مثل array_1
). تم توضيح سبب تحديث قيم array_4
(فقط) في PEP-289
يتم تقييم التعبير الخارجي فقط على الفور، ويتم تأجيل التعبيرات الأخرى حتى يتم تشغيل المولد.
is not ...
ليس is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
عاملًا ثنائيًا واحدًا، وله سلوك مختلف عن استخدام is
not
منفصل.is not
تقييمه إلى False
إذا كانت المتغيرات الموجودة على جانبي عامل التشغيل تشير إلى نفس الكائن وكانت True
بخلاف ذلك.(not None)
إلى True
نظرًا لأن القيمة None
هي False
في سياق منطقي، وبالتالي يصبح التعبير 'something' is True
. # Let's initialize a row
row = [ "" ] * 3 #row i['', '', '']
# Let's make a board
board = [ row ] * 3
الإخراج:
> >> board
[[ '' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
> >> board [ 0 ]
[ '' , '' , '' ]
> >> board [ 0 ][ 0 ]
''
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ 'X' , '' , '' ], [ 'X' , '' , '' ]]
لم نقم بتعيين ثلاث علامات "X"
، أليس كذلك؟
عندما نقوم بتهيئة متغير row
، يشرح هذا التصور ما يحدث في الذاكرة
وعندما تتم تهيئة board
بضرب row
، فهذا ما يحدث داخل الذاكرة (كل عنصر من العناصر board[0]
و board[1]
و board[2]
هو مرجع لنفس القائمة المشار إليها بواسطة row
)
يمكننا تجنب هذا السيناريو هنا من خلال عدم استخدام متغير row
لإنشاء board
. (سئل في هذه المسألة).
> >> board = [[ '' ] * 3 for _ in range ( 3 )]
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
funcs = []
results = []
for x in range ( 7 ):
def some_func ():
return x
funcs . append ( some_func )
results . append ( some_func ()) # note the function call here
funcs_results = [ func () for func in funcs ]
الإخراج (إصدار بايثون):
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
كانت قيم x
مختلفة في كل تكرار قبل إلحاق some_func
إلى funcs
، لكن جميع الدوال ترجع 6 عندما يتم تقييمها بعد اكتمال الحلقة.
> >> powers_of_x = [ lambda x : x ** i for i in range ( 10 )]
> >> [ f ( 2 ) for f in powers_of_x ]
[ 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 ]
x
في السياق المحيط، بدلاً من استخدام قيمة x
في وقت إنشاء الدالة. لذلك تستخدم جميع الوظائف أحدث قيمة مخصصة للمتغير للحساب. يمكننا أن نرى أنه يستخدم x
من السياق المحيط (أي ليس متغيرًا محليًا) مع: > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
نظرًا لأن x
قيمة عالمية، فيمكننا تغيير القيمة التي سيبحث عنها funcs
ويعيدها عن طريق تحديث x
:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
في ذلك الوقت. funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
الإخراج:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
لم يعد يستخدم x
في النطاق العالمي:
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
إذن ما هي الفئة الأساسية "النهائية"؟ هناك المزيد من الارتباك بالمناسبة،
2.
> >> class A : pass
> >> isinstance ( A , A )
False
> >> isinstance ( type , type )
True
> >> isinstance ( object , object )
True
3.
> >> issubclass ( int , object )
True
> >> issubclass ( type , object )
True
> >> issubclass ( object , type )
False
type
هو فئة تعريفية في بايثون.object
في بايثون، والذي يتضمن الفئات بالإضافة إلى كائناتها (المثيلات).type
الفئة هو فئة تعريفية object
الفئة، وقد ورثت كل فئة (بما في ذلك type
) بشكل مباشر أو غير مباشر من object
.object
type
. ينشأ الارتباك في المقتطفات أعلاه لأننا نفكر في هذه العلاقات ( issubclass
و isinstance
) من حيث فئات Python. لا يمكن إعادة إنتاج العلاقة بين object
type
بلغة بيثون خالصة. لكي نكون أكثر دقة، لا يمكن إعادة إنتاج العلاقات التالية في لغة بايثون النقية،object
type
(كلاهما مثيل لبعضهما البعض وكذلك نفسها) موجودة في بايثون بسبب "الغش" على مستوى التنفيذ.الإخراج:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
كان من المتوقع أن تكون علاقات الفئات الفرعية متعدية، أليس كذلك؟ (على سبيل المثال، إذا كانت A
فئة فرعية من B
، و B
هي فئة فرعية من C
، فيجب أن تكون A
فئة فرعية من C
)
__subclasscheck__
التعسفي الخاص به في فئة التعريف.issubclass(cls, Hashable)
، فإنه ببساطة يبحث عن أسلوب غير كاذب " __hash__
" في cls
أو أي شيء يرث منه.object
قابل للتجزئة، ولكن list
غير قابلة للتجزئة، فإنه يكسر علاقة العبور. class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
الإخراج:
> >> print ( SomeClass . method is SomeClass . method )
True
> >> print ( SomeClass . classm is SomeClass . classm )
False
> >> print ( SomeClass . classm == SomeClass . classm )
True
> >> print ( SomeClass . staticm is SomeClass . staticm )
True
عند الوصول إلى classm
مرتين، نحصل على كائن متساوٍ، ولكن ليس نفس الشيء؟ دعونا نرى ما يحدث مع مثيلات SomeClass
:
o1 = SomeClass ()
o2 = SomeClass ()
الإخراج:
> >> print ( o1 . method == o2 . method )
False
> >> print ( o1 . method == o1 . method )
True
> >> print ( o1 . method is o1 . method )
False
> >> print ( o1 . classm is o1 . classm )
False
> >> print ( o1 . classm == o1 . classm == o2 . classm == SomeClass . classm )
True
> >> print ( o1 . staticm is o1 . staticm is o2 . staticm is SomeClass . staticm )
True
يؤدي الوصول إلى classm
أو method
مرتين إلى إنشاء كائنات متساوية ولكن ليست نفسها لنفس مثيل SomeClass
.
self
باعتبارها الوسيطة الأولى، على الرغم من عدم تمريرها بشكل صريح). > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
ليس صادقًا أبدًا. ومع ذلك، فإن الوصول إلى الوظائف كسمات فئة (على عكس المثيلات) لا يؤدي إلى إنشاء طرق؛ لذا SomeClass.method is SomeClass.method
صادق. > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
بتحويل الوظائف إلى أساليب الفصل. أساليب الفئة هي واصفات تقوم، عند الوصول إليها، بإنشاء كائن أسلوب يربط فئة (نوع) الكائن، بدلاً من الكائن نفسه. > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
طريقة أيضًا عند الوصول إليها كسمات للفئة (وفي هذه الحالة تربط الفئة، وليس بنوعها). لذا SomeClass.classm is SomeClass.classm
غير صحيح. > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
صحيح، على الرغم من أنه ليس نفس الكائن في الذاكرة.staticmethod
بتحويل الوظائف إلى واصف "no-op"، والذي يُرجع الوظيفة كما هي. لم يتم إنشاء أي كائنات أسلوب على الإطلاق، لذا فإن المقارنة مع is
صحيحة. > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
بشكل سيء. قام CPython 3.7 بحل هذه المشكلة عن طريق تقديم أكواد تشغيل جديدة تتعامل مع أساليب الاتصال دون إنشاء كائنات الطريقة المؤقتة. يتم استخدام هذا فقط عندما يتم استدعاء الوظيفة التي تم الوصول إليها بالفعل، لذلك لا تتأثر المقتطفات هنا، ولا تزال تولد الأساليب :) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
لماذا هذا التغيير الصواب والخطأ؟
تنفيذ all
الوظائف يعادل
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
يُرجع True
لأن العنصر القابل للتكرار فارغ.
all([[]])
يُرجع False
لأن المصفوفة التي تم تمريرها تحتوي على عنصر واحد، []
، وفي بايثون، القائمة الفارغة خاطئة.
all([[[]]])
والمتغيرات العودية الأعلى تكون دائمًا True
. وذلك لأن العنصر الوحيد للمصفوفة التي تم تمريرها ( [[...]]
) لم يعد فارغًا، والقوائم ذات القيم صحيحة.
الإخراج (< 3.6):
> >> def f ( x , y ,):
... print ( x , y )
...
> >> def g ( x = 4 , y = 5 ,):
... print ( x , y )
...
> >> def h ( x , ** kwargs ,):
File "<stdin>" , line 1
def h ( x , ** kwargs ,):
^
SyntaxError : invalid syntax
>> > def h ( * args ,):
File "<stdin>" , line 1
def h ( * args ,):
^
SyntaxError : invalid syntax
الإخراج:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
)، تمرر الخطوط المائلة العكسية نفسها كما هي جنبًا إلى جنب مع سلوك الهروب من الحرف التالي. > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
) ، أفلتت الشرطة المائلة العكسية من الاقتباس اللاحق، تاركة المحلل بدون علامة اقتباس منتهية (ومن هنا جاء خطأ SyntaxError
). لهذا السبب لا تعمل الخطوط المائلة العكسية في نهاية السلسلة الأولية. x = True
y = False
الإخراج:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
له أسبقية أعلى من عامل التشغيل not
في بايثون.not x == y
مكافئًا لـ not (x == y)
وهو ما يعادل not (True == False)
الذي يتم تقييمه أخيرًا إلى True
.x == not y
يثير خطأ SyntaxError
لأنه يمكن اعتباره مكافئًا لـ (x == not) y
وليس x == (not y)
الذي ربما كنت تتوقعه من النظرة الأولى.not
جزءًا من عامل التشغيل not in
(لأن كلا من ==
و not in
لهما نفس الأسبقية)، ولكن بعد عدم التمكن من العثور على رمز in
token بعد الرمز not
، فإنه يثير خطأ SyntaxError
.الإخراج:
> >> print ( 'wtfpython' '' )
wtfpython
> >> print ( "wtfpython" "" )
wtfpython
> >> # The following statements raise `SyntaxError`
>> > # print('''wtfpython')
>> > # print("""wtfpython")
File "<input>" , line 3
print ("""wtfpython")
^
SyntaxError: EOF while scanning triple-quoted string literal
>>> print("wtf" "python")
wtfpython
>>> print("wtf" "") # or "wtf"""
wtf
'''
و """
هما أيضًا محددات سلسلة في Python والتي تسبب خطأ SyntaxError لأن مترجم Python كان يتوقع اقتباسًا ثلاثيًا نهائيًا كمحدد أثناء مسح السلسلة الحرفية الثلاثية المقتبسة الموجودة حاليًا.1.
# A simple example to count the number of booleans and
# integers in an iterable of mixed data types.
mixed_list = [ False , 1.0 , "some_string" , 3 , True , [], False ]
integers_found_so_far = 0
booleans_found_so_far = 0
for item in mixed_list :
if isinstance ( item , int ):
integers_found_so_far += 1
elif isinstance ( item , bool ):
booleans_found_so_far += 1
الإخراج:
> >> integers_found_so_far
4
> >> booleans_found_so_far
0
2.
> >> some_bool = True
> >> "wtf" * some_bool
'wtf'
> >> some_bool = False
> >> "wtf" * some_bool
''
3.
def tell_truth ():
True = False
if True == False :
print ( "I have lost faith in truth!" )
الإخراج (< 3.x):
> >> tell_truth ()
I have lost faith in truth !
bool
هي فئة فرعية من int
في بايثون
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
وبالتالي فإن True
و False
هما مثيلان لـ int
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
القيمة الصحيحة لـ True
هي 1
وقيمة False
هي 0
.
> >> int ( True )
1
> >> int ( False )
0
راجع إجابة StackOverflow للتعرف على الأساس المنطقي وراءها.
في البداية، لم يكن لدى بايثون أي نوع bool
(استخدم الناس 0 للقيمة الخاطئة والقيمة غير الصفرية مثل 1 للقيمة الحقيقية). تمت إضافة True
و False
ونوع bool
في إصدارات 2.x، ولكن من أجل التوافق مع الإصدارات السابقة، لا يمكن جعل True
و False
ثوابت. لقد كانت مجرد متغيرات مدمجة، وكان من الممكن إعادة تعيينها
كان Python 3 غير متوافق مع الإصدارات السابقة، وتم إصلاح المشكلة أخيرًا، وبالتالي لن يعمل المقتطف الأخير مع Python 3.x!
1.
class A :
x = 1
class B ( A ):
pass
class C ( A ):
pass
الإخراج:
> >> A . x , B . x , C . x
( 1 , 1 , 1 )
>> > B . x = 2
>> > A . x , B . x , C . x
( 1 , 2 , 1 )
>> > A . x = 3
>> > A . x , B . x , C . x # C.x changed, but B.x didn't
( 3 , 2 , 3 )
>> > a = A ()
>> > a . x , A . x
( 3 , 3 )
>> > a . x + = 1
>> > a . x , A . x
( 4 , 3 )
2.
class SomeClass :
some_var = 15
some_list = [ 5 ]
another_list = [ 5 ]
def __init__ ( self , x ):
self . some_var = x + 1
self . some_list = self . some_list + [ x ]
self . another_list += [ x ]
الإخراج:
> >> some_obj = SomeClass ( 420 )
> >> some_obj . some_list
[ 5 , 420 ]
> >> some_obj . another_list
[ 5 , 420 ]
> >> another_obj = SomeClass ( 111 )
> >> another_obj . some_list
[ 5 , 111 ]
> >> another_obj . another_list
[ 5 , 420 , 111 ]
> >> another_obj . another_list is SomeClass . another_list
True
> >> another_obj . another_list is some_obj . another_list
True
+=
بتعديل الكائن القابل للتغيير في مكانه دون إنشاء كائن جديد. لذا فإن تغيير سمة مثيل واحد يؤثر على المثيلات الأخرى وسمة الفئة أيضًا. some_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
الإخراج (<= 3.7.x):
> >> [ x for x in some_iterable ]
[ 'a' , 'b' ]
> >> [( yield x ) for x in some_iterable ]
< generator object < listcomp > at 0x7f70b0a4ad58 >
> >> list ([( yield x ) for x in some_iterable ])
[ 'a' , 'b' ]
> >> list (( yield x ) for x in some_iterable )
[ 'a' , None , 'b' , None ]
> >> list ( some_func (( yield x )) for x in some_iterable )
[ 'a' , 'something' , 'b' , 'something' ]
yield
في المولدات والفهم.yield
داخل القائمة وسيؤدي إلى ظهور خطأ SyntaxError
.1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
الإخراج (> 3.3):
> >> list ( some_func ( 3 ))
[]
أين ذهبت "wtf"
؟ هل يرجع ذلك إلى بعض التأثير الخاص yield from
؟ دعونا التحقق من صحة ذلك،
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
الإخراج:
> >> list ( some_func ( 3 ))
[]
نفس النتيجة، ولم ينجح هذا أيضًا.
return
مع القيم الموجودة داخل المولدات (انظر PEP380). الوثائق الرسمية تقول ذلك"... يؤدي
return expr
في المولد إلى رفعStopIteration(expr)
عند الخروج من المولد."
في حالة some_func(3)
، يتم رفع StopIteration
في البداية بسبب عبارة return
. يتم اكتشاف استثناء StopIteration
تلقائيًا داخل غلاف list(...)
وحلقة for
. لذلك، يؤدي المقتطفان أعلاه إلى قائمة فارغة.
للحصول على ["wtf"]
من المولد some_func
نحتاج إلى اكتشاف استثناء StopIteration
،
try :
next ( some_func ( 3 ))
except StopIteration as e :
some_string = e . value
> >> some_string
[ "wtf" ]
1.
a = float ( 'inf' )
b = float ( 'nan' )
c = float ( '-iNf' ) # These strings are case-insensitive
d = float ( 'nan' )
الإخراج:
> >> a
inf
> >> b
nan
> >> c
- inf
> >> float ( 'some_other_string' )
ValueError : could not convert string to float : some_other_string
> >> a == - c # inf==inf
True
> >> None == None # None == None
True
> >> b == d # but nan!=nan
False
> >> 50 / a
0.0
> >> a / a
nan
> >> 23 + b
nan
2.
> >> x = float ( 'nan' )
> >> y = x / x
> >> y is y # identity holds
True
> >> y == y # equality fails of y
False
> >> [ y ] == [ y ] # but the equality succeeds for the list containing y
True
إن 'inf'
و 'nan'
عبارة عن سلاسل خاصة (غير حساسة لحالة الأحرف)، والتي، عند كتابتها بشكل صريح للكتابة float
، يتم استخدامها لتمثيل "اللانهاية" الرياضية و"ليس رقمًا" على التوالي.
نظرًا لأنه وفقًا لمعايير IEEE NaN != NaN
، فإن إطاعة هذه القاعدة يكسر افتراض الانعكاسية لعنصر المجموعة في Python، أي إذا كان x
جزءًا من مجموعة مثل list
، فإن التطبيقات مثل المقارنة تعتمد على افتراض أن x == x
. وبسبب هذا الافتراض، تتم مقارنة الهوية أولاً (لأنها أسرع) أثناء مقارنة عنصرين، وتتم مقارنة القيم فقط عندما تكون الهويات غير متطابقة. والمقتطف التالي سيجعل الأمور أكثر وضوحا،
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
نظرًا لأن هويات x
و y
مختلفة، يتم أخذ القيم في الاعتبار، والتي تكون مختلفة أيضًا؛ ومن ثم ترجع المقارنة False
هذه المرة.
قراءة مثيرة للاهتمام: الانعكاسية، وغيرها من ركائز الحضارة
قد يبدو هذا تافهًا إذا كنت تعرف كيفية عمل المراجع في بايثون.
some_tuple = ( "A" , "tuple" , "with" , "values" )
another_tuple = ([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ])
الإخراج:
> >> some_tuple [ 2 ] = "change this"
TypeError : 'tuple' object does not support item assignment
> >> another_tuple [ 2 ]. append ( 1000 ) #This throws no error
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 ])
> >> another_tuple [ 2 ] += [ 99 , 999 ]
TypeError : 'tuple' object does not support item assignment
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 , 99 , 999 ])
لكنني اعتقدت أن الصفوف غير قابلة للتغيير ...
نقلا عن https://docs.python.org/3/reference/datamodel.html
التسلسلات غير القابلة للتغيير لا يمكن لكائن من نوع التسلسل غير القابل للتغيير أن يتغير بمجرد إنشائه. (إذا كان الكائن يحتوي على مراجع لكائنات أخرى، فقد تكون هذه الكائنات الأخرى قابلة للتغيير ويمكن تعديلها؛ ومع ذلك، لا يمكن تغيير مجموعة الكائنات المشار إليها مباشرة بواسطة كائن غير قابل للتغيير.)
+=
يقوم عامل التشغيل بتغيير القائمة في مكانها. لا يعمل تعيين العنصر، ولكن عند حدوث الاستثناء، يكون العنصر قد تم تغيير مكانه بالفعل.
يوجد أيضًا شرح في الأسئلة الشائعة الرسمية لـ Python.
e = 7
try :
raise Exception ()
except Exception as e :
pass
الإخراج (بايثون 2.x):
> >> print ( e )
# prints nothing
الإخراج (بايثون 3.x):
> >> print ( e )
NameError : name 'e' is not defined
المصدر: https://docs.python.org/3/reference/compound_stmts.html#except
عندما يتم تعيين استثناء باستخدام as
، يتم مسحه في نهاية جملة except
. هذا كما لو
except E as N :
foo
تمت ترجمته إلى
except E as N :
try :
foo
finally :
del N
وهذا يعني أنه يجب تعيين الاستثناء لاسم مختلف لتتمكن من الرجوع إليه بعد جملة الاستثناء. يتم مسح الاستثناءات لأنها، مع التتبع المرتبط بها، تشكل دورة مرجعية مع إطار المكدس، مما يحافظ على جميع السكان المحليين في هذا الإطار على قيد الحياة حتى يحدث تجميع البيانات المهملة التالي.
لم يتم تحديد نطاق الجمل في بيثون. كل شيء في المثال موجود في نفس النطاق، وتمت إزالة المتغير e
بسبب تنفيذ جملة except
. الشيء نفسه ليس هو الحال مع الوظائف التي لها وحدات داخلية منفصلة. يوضح المثال أدناه هذا:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
الإخراج:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
في Python 2.x ، يتم تعيين الاسم المتغير e
إلى مثيل Exception()
، لذلك عندما تحاول الطباعة ، فإنه لا يطبع شيئًا.
الإخراج (Python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
الإخراج:
> >> type ( list ( some_dict . keys ())[ 0 ])
str
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict # expected: Two different keys-value pairs
{ 's' : 40 }
> >> type ( list ( some_dict . keys ())[ 0 ])
str
كل من الكائن s
والسلسلة "s"
إلى نفس القيمة لأن SomeClass
يرث طريقة __hash__
لفئة str
.
يتم تقييم SomeClass("s") == "s"
إلى True
لأن SomeClass
يرث أيضًا طريقة __eq__
من فئة str
.
نظرًا لأن كلا الكائنين يضم نفس القيمة وهو متساوٍ ، يتم تمثيلهما بنفس المفتاح في القاموس.
للسلوك المطلوب ، يمكننا إعادة تعريف طريقة __eq__
في SomeClass
class SomeClass ( str ):
def __eq__ ( self , other ):
return (
type ( self ) is SomeClass
and type ( other ) is SomeClass
and super (). __eq__ ( other )
)
# When we define a custom __eq__, Python stops automatically inheriting the
# __hash__ method, so we need to define it as well
__hash__ = str . __hash__
some_dict = { 's' : 42 }
الإخراج:
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict
{ 's' : 40 , 's' : 42 }
> >> keys = list ( some_dict . keys ())
> >> type ( keys [ 0 ]), type ( keys [ 1 ])
( __main__ . SomeClass , str )
a , b = a [ b ] = {}, 5
الإخراج:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
يقوم عبارة التعيين بتقييم قائمة التعبير (تذكر أن هذا يمكن أن يكون تعبيرًا واحدًا أو قائمة مفصولة بفاصلة ، ويعزى الأخير إلى Tuple) ويعين الكائن المنفرد إلى كل من قوائم الهدف ، من اليسار إلى اليمين.
+
in (target_list "=")+
يعني أنه يمكن أن يكون هناك قوائم مستهدفة واحدة أو أكثر . في هذه الحالة ، تكون قوائم المستهدف a, b
و a[b]
(لاحظ أن قائمة التعبير هي بالضبط ، وهي في حالتنا {}, 5
).
بعد تقييم قائمة التعبير ، يتم تفريغ قيمتها على القوائم المستهدفة من اليسار إلى اليمين . لذلك ، في حالتنا ، أولاً ، يتم تفريغ {}, 5
tuple إلى a, b
ولدينا الآن a = {}
و b = 5
.
تم تعيين a
الآن إلى {}
، وهو كائن قابل للتغيير.
القائمة الهدف الثانية هي a[b]
(قد تتوقع أن يرمي هذا خطأ لأنه لم يتم تعريف كل من a
و b
في العبارات من قبل. ولكن تذكر ، لقد قمنا للتو بتعيين a
إلى {}
و b
إلى 5
).
الآن ، نقوم بتعيين المفتاح 5
في القاموس إلى tuple ({}, 5)
إنشاء مرجع دائري (يشير {...}
في الإخراج إلى نفس a
الذي يشير إليه بالفعل). مثال أبسط آخر للمرجع الدائري يمكن أن يكون
> >> some_list = some_list [ 0 ] = [ 0 ]
> >> some_list
[[...]]
> >> some_list [ 0 ]
[[...]]
> >> some_list is some_list [ 0 ]
True
> >> some_list [ 0 ][ 0 ][ 0 ][ 0 ][ 0 ][ 0 ] == some_list
True
مماثل هو الحال في مثالنا ( a[b][0]
هو نفس الكائن كما a
)
لتلخيصها ، يمكنك تقسيم المثال إلى
a , b = {}, 5
a [ b ] = a , b
ويمكن تبرير المرجع الدائري من خلال حقيقة أن a[b][0]
هو نفس a
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
الإخراج:
> >> # Python 3.10.6
222222222222222222222222222222222222222222222222222222222222222. ..
>> > # Python 3.10.8
Traceback ( most recent call last ):
...
ValueError : Exceeds the limit ( 4300 ) for integer string conversion :
value has 5432 digits ; use sys . set_int_max_str_digits ()
to increase the limit .
تعمل هذه الدعوة إلى int()
بشكل جيد في Python 3.10.6 ورفع قيمة في Python 3.10.8. لاحظ أن Python لا يزال بإمكانه العمل مع أعداد صحيحة كبيرة. يتم رفع الخطأ فقط عند التحويل بين الأعداد الصحيحة والسلاسل.
لحسن الحظ ، يمكنك زيادة الحد لعدد الأرقام المسموح به عندما تتوقع أن تتجاوز عملية ما. للقيام بذلك ، يمكنك استخدام واحدة مما يلي:
تحقق من الوثائق لمزيد من التفاصيل حول تغيير الحد الافتراضي إذا كنت تتوقع أن يتجاوز الرمز الخاص بك هذه القيمة.
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
الإخراج (Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
نعم ، إنه يعمل لمدة ثماني مرات بالضبط ويتوقف.
RuntimeError: dictionary keys changed during iteration
إذا حاولت القيام بذلك.del
العملية class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
الإخراج: 1.
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
Phew ، تم حذفه أخيرًا. ربما تكون قد خمنت ما أنقذ __del__
من الاتصال في محاولتنا الأولى لحذف x
. دعنا نضيف المزيد من التحولات إلى المثال.
2.
> >> x = SomeClass ()
> >> y = x
> >> del x
> >> y # check if y exists
< __main__ . SomeClass instance at 0x7f98a1a67fc8 >
> >> del y # Like previously, this should print "Deleted!"
> >> globals () # oh, it didn't. Let's check all our global variables and confirm
Deleted !
{ '__builtins__' : < module '__builtin__' ( built - in ) > , 'SomeClass' : < class __main__ . SomeClass at 0x7f98a1a5f668 > , '__package__' : None , '__name__' : '__main__' , '__doc__' : None }
حسنًا ، تم حذفه الآن؟
del x
لا يتصل مباشرة x.__del__()
.del x
، يحذف Python الاسم x
من النطاق الحالي والانخفاض بمقدار 1 العدد المرجعي للكائن x
المشار إليه. لا يتم استدعاء __del__()
إلا عندما يصل عدد مرجع الكائن إلى الصفر.__del__()
لأن العبارة السابقة ( >>> y
) في المترجم التفاعلي قد أنشأ مرجعًا آخر إلى نفس الكائن (على وجه التحديد ، المتغير _
الذي يشير إلى قيمة النتيجة لآخر None
التعبير على Repl) ، وبالتالي منع عدد المرجع من الوصول إلى الصفر عند مواجهة del y
globals
(أو في الحقيقة ، أي شيء سيكون له نتيجة لا None
) _
في الإشارة إلى النتيجة الجديدة ، وإسقاط المرجع الحالي. الآن وصل العدد المرجعي إلى 0 ويمكننا أن نرى "تم حذفه!" يجري طباعتها (أخيرًا!).1.
a = 1
def some_func ():
return a
def another_func ():
a += 1
return a
2.
def some_closure_func ():
a = 1
def some_inner_func ():
return a
return some_inner_func ()
def another_closure_func ():
a = 1
def another_inner_func ():
a += 1
return a
return another_inner_func ()
الإخراج:
> >> some_func ()
1
> >> another_func ()
UnboundLocalError : local variable 'a' referenced before assignment
>> > some_closure_func ()
1
> >> another_closure_func ()
UnboundLocalError : local variable 'a' referenced before assignment
عندما تقوم بإجراء مهمة لمتغير في النطاق ، يصبح هذا النطاق محليًا. لذا ، يصبح a
محليًا لنطاق another_func
، ولكن لم يتم تهيئته مسبقًا في نفس النطاق ، الذي يلقي خطأ.
لتعديل متغير النطاق الخارجي a
في another_func
، يتعين علينا استخدام الكلمة الرئيسية global
.
def another_func ()
global a
a += 1
return a
الإخراج:
> >> another_func ()
2
في another_closure_func
، يصبح a
محليًا في نطاق another_inner_func
، ولكن لم يتم تهيئته مسبقًا في نفس النطاق ، وهذا هو السبب في أنه يرمي خطأ.
لتعديل متغير النطاق الخارجي a
في another_inner_func
، استخدم الكلمة الرئيسية nonlocal
. يتم استخدام البيان غير المحلي للإشارة إلى المتغيرات المحددة في أقرب نطاق خارجي (باستثناء النطاق العالمي).
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
الإخراج:
> >> another_func ()
2
تخبر الكلمات الرئيسية global
nonlocal
مترجم بيثون عدم الإعلان عن متغيرات جديدة والبحث عنها في النطاقات الخارجية المقابلة.
اقرأ هذا الدليل القصير ولكن رائع لمعرفة المزيد حول كيفية عمل مساحات الأسماء ودقة النطاق في بيثون.
list_1 = [ 1 , 2 , 3 , 4 ]
list_2 = [ 1 , 2 , 3 , 4 ]
list_3 = [ 1 , 2 , 3 , 4 ]
list_4 = [ 1 , 2 , 3 , 4 ]
for idx , item in enumerate ( list_1 ):
del item
for idx , item in enumerate ( list_2 ):
list_2 . remove ( item )
for idx , item in enumerate ( list_3 [:]):
list_3 . remove ( item )
for idx , item in enumerate ( list_4 ):
list_4 . pop ( idx )
الإخراج:
> >> list_1
[ 1 , 2 , 3 , 4 ]
> >> list_2
[ 2 , 4 ]
> >> list_3
[]
> >> list_4
[ 2 , 4 ]
هل يمكنك تخمين لماذا الإخراج [2, 4]
؟
ليس من الجيد أبدًا تغيير الكائن الذي تكرره. الطريقة الصحيحة للقيام بذلك هي التكرار عبر نسخة من الكائن بدلاً من ذلك ، و list_3[:]
يفعل ذلك تمامًا.
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
الفرق بين del
، remove
، pop
:
del var_name
يزيل فقط ربط var_name
من مساحة الاسم المحلية أو العالمية (لهذا السبب لا يتأثر list_1
).remove
إزالة قيمة المطابقة الأولى ، وليس فهرسًا محددًا ، يرفع ValueError
إذا لم يتم العثور على القيمة.pop
بإزالة العنصر في فهرس معين ويعيده ، يرفع IndexError
في حالة تحديد فهرس غير صالح. لماذا الإخراج [2, 4]
؟
1
من list_2
أو list_4
، تكون محتويات القوائم الآن [2, 3, 4]
. يتم تحويل العناصر المتبقية إلى أسفل ، أي ، 2
في الفهرس 0 ، و 3
في الفهرس 1. بما أن التكرار التالي سوف ينظر إلى الفهرس 1 (وهو 3
) ، يتم تخطي 2
بالكامل. سيحدث شيء مشابه مع كل عنصر بديل في تسلسل القائمة. > >> numbers = list ( range ( 7 ))
> >> numbers
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> first_three , remaining = numbers [: 3 ], numbers [ 3 :]
> >> first_three , remaining
([ 0 , 1 , 2 ], [ 3 , 4 , 5 , 6 ])
>> > numbers_iter = iter ( numbers )
>> > list ( zip ( numbers_iter , first_three ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
# so far so good, let's zip the remaining
>> > list ( zip ( numbers_iter , remaining ))
[( 4 , 3 ), ( 5 , 4 ), ( 6 , 5 )]
أين انتقل العنصر 3
من قائمة numbers
؟
def zip ( * iterables ):
sentinel = object ()
iterators = [ iter ( it ) for it in iterables ]
while iterators :
result = []
for it in iterators :
elem = next ( it , sentinel )
if elem is sentinel : return
result . append ( elem )
yield tuple ( result )
result
من خلال استدعاء الوظيفة next
عليها ، وتتوقف كلما تم استنفاد أي من العناصر الجاهزة.result
. هذا ما حدث مع 3
في numbers_iter
.zip
ستكون ، > >> numbers = list ( range ( 7 ))
> >> numbers_iter = iter ( numbers )
> >> list ( zip ( first_three , numbers_iter ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
> >> list ( zip ( remaining , numbers_iter ))
[( 3 , 3 ), ( 4 , 4 ), ( 5 , 5 ), ( 6 , 6 )]
1.
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
الإخراج:
6 : for x inside loop
6 : x in global
ولكن لم يتم تعريف x
خارج نطاق الحلقة ...
2.
# This time let's initialize x first
x = - 1
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
الإخراج:
6 : for x inside loop
6 : x in global
3.
الإخراج (Python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
الإخراج (Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
في Python ، تستخدم الحلقات من أجل النطاق الموجود فيها واتركها متغيرًا محددًا. ينطبق هذا أيضًا إذا حددنا بشكل صريح المتغير من أجل الحلقة في مساحة الاسم العالمية من قبل. في هذه الحالة ، سوف يعيد إعادة توزيع المتغير الحالي.
يمكن تفسير الاختلافات في إخراج Python 2.x و Python 3.x من أجل مثال فهم القائمة من خلال اتباع التغيير الموثق في ما هو جديد في Python 3.0 Changelog:
"لم تعد شمولية القائمة تدعم النموذج النحوي
[... for var in item1, item2, ...]
. استخدم[... for var in (item1, item2, ...)]
بدلاً من ذلك ، لاحظ تلك القائمة الشاملات لها دلالات مختلفة: فهي أقرب إلى السكر النحوي لتعبير المولد داخلlist()
، وعلى وجه الخصوص ، لم تعد متغيرات التحكم في الحلقة تسرب إلى النطاق المحيط ".
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
الإخراج:
> >> some_func ()
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' ]
> >> some_func ([])
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' , 'some_string' ]
لا يتم تهيئة الوسائط الافتراضية القابلة للتغيير للوظائف في Python في كل مرة تقوم فيها بالاتصال بالوظيفة. بدلاً من ذلك ، يتم استخدام القيمة المخصصة مؤخرًا لهم كقيمة افتراضية. عندما انتقلنا بشكل صريح []
إلى some_func
كوسيطة ، لم يتم استخدام القيمة الافتراضية لمتغير default_arg
، لذلك تم إرجاع الوظيفة كما هو متوقع.
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
الإخراج:
> >> some_func . __defaults__ #This will show the default argument values for the function
([],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' ],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
> >> some_func ([])
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
تتمثل إحدى الممارسة الشائعة لتجنب الأخطاء بسبب الوسائط القابلة للتغيير في تعيين None
كقيمة افتراضية والتحقق لاحقًا مما إذا كانت أي قيمة تم نقلها إلى الوظيفة المقابلة لتلك الوسيطة. مثال:
def some_func ( default_arg = None ):
if default_arg is None :
default_arg = []
default_arg . append ( "some_string" )
return default_arg
some_list = [ 1 , 2 , 3 ]
try :
# This should raise an ``IndexError``
print ( some_list [ 4 ])
except IndexError , ValueError :
print ( "Caught!" )
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except IndexError , ValueError :
print ( "Caught again!" )
الإخراج (Python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
الإخراج (Python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
لإضافة استثناءات متعددة إلى الفقرة باستثناء ، تحتاج إلى تمريرها كأقواس tuple كوسيطة أولى. الوسيطة الثانية هي اسم اختياري ، والذي عند توفيره سيربط مثيل الاستثناء الذي تم رفعه. مثال،
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
الإخراج (Python 2.x):
Caught again!
list.remove(x): x not in list
الإخراج (Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
يتم إهمال فصل الاستثناء عن المتغير مع فاصلة ولا يعمل في بيثون 3 ؛ الطريقة الصحيحة هي استخدام as
. مثال،
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
الإخراج:
Caught again!
list.remove(x): x not in list
1.
a = [ 1 , 2 , 3 , 4 ]
b = a
a = a + [ 5 , 6 , 7 , 8 ]
الإخراج:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 ]
2.
a = [ 1 , 2 , 3 , 4 ]
b = a
a += [ 5 , 6 , 7 , 8 ]
الإخراج:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
a += b
لا يتصرف دائمًا بنفس طريقة a = a + b
قد تنفذ الفئات op=
عوامل المشغلين بشكل مختلف ، والقوائم تقوم بذلك.
يولد التعبير a = a + [5,6,7,8]
قائمة جديدة ويحدد a
إلى تلك القائمة الجديدة ، تاركًا b
دون تغيير.
يتم تعيين التعبير a += [5,6,7,8]
فعليًا إلى وظيفة "تمديد" تعمل في القائمة بحيث لا يزال a
و b
يشيران إلى نفس القائمة التي تم تعديلها في مكانها.
1.
x = 5
class SomeClass :
x = 17
y = ( x for i in range ( 10 ))
الإخراج:
> >> list ( SomeClass . y )[ 0 ]
5
2.
x = 5
class SomeClass :
x = 17
y = [ x for i in range ( 10 )]
الإخراج (Python 2.x):
> >> SomeClass . y [ 0 ]
17
الإخراج (Python 3.x):
> >> SomeClass . y [ 0 ]
5
دعونا ننفذ وظيفة ساذجة للحصول على العنصر الأوسط للقائمة:
def get_middle ( some_list ):
mid_index = round ( len ( some_list ) / 2 )
return some_list [ mid_index - 1 ]
بيثون 3.x:
> >> get_middle ([ 1 ]) # looks good
1
> >> get_middle ([ 1 , 2 , 3 ]) # looks good
2
> >> get_middle ([ 1 , 2 , 3 , 4 , 5 ]) # huh?
2
> >> len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 # good
2.5
> >> round ( len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 ) # why?
2
يبدو كما لو أن بيثون تقريب 2.5 إلى 2.
round()
تقريب Banker حيث يتم تقريب الكسور .5 إلى أقرب رقم متساوي : > >> round ( 0.5 )
0
> >> round ( 1.5 )
2
> >> round ( 2.5 )
2
> >> import numpy # numpy does the same
> >> numpy . round ( 0.5 )
0.0
> >> numpy . round ( 1.5 )
2.0
> >> numpy . round ( 2.5 )
2.0
get_middle([1])
تم إرجاعها 1 فقط لأن الفهرس كان round(0.5) - 1 = 0 - 1 = -1
، إرجاع العنصر الأخير في القائمة.لم أقابل حتى تجربة واحدة للثعباني حتى الآن لم تصادف سيناريوهات واحدة أو أكثر ،
1.
x , y = ( 0 , 1 ) if True else None , None
الإخراج:
> >> x , y # expected (0, 1)
(( 0 , 1 ), None )
2.
t = ( 'one' , 'two' )
for i in t :
print ( i )
t = ( 'one' )
for i in t :
print ( i )
t = ()
print ( t )
الإخراج:
one
two
o
n
e
tuple ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
الإخراج
> >> len ( ten_words_list )
9
4. لا تؤكد بقوة كافية
a = "python"
b = "javascript"
الإخراج:
# An assert statement with an assertion failure message.
> >> assert ( a == b , "Both languages are different" )
# No AssertionError is raised
5.
some_list = [ 1 , 2 , 3 ]
some_dict = {
"key_1" : 1 ,
"key_2" : 2 ,
"key_3" : 3
}
some_list = some_list . append ( 4 )
some_dict = some_dict . update ({ "key_4" : 4 })
الإخراج:
> >> print ( some_list )
None
> >> print ( some_dict )
None
6.
def some_recursive_func ( a ):
if a [ 0 ] == 0 :
return
a [ 0 ] -= 1
some_recursive_func ( a )
return a
def similar_recursive_func ( a ):
if a == 0 :
return a
a -= 1
similar_recursive_func ( a )
return a
الإخراج:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
بالنسبة إلى 1 ، فإن البيان الصحيح للسلوك المتوقع هو x, y = (0, 1) if True else (None, None)
.
بالنسبة إلى 2 ، فإن البيان الصحيح للسلوك المتوقع هو t = ('one',)
أو t = 'one',
(الفاصلة المفقودة) وإلا فإن المترجم الفوري يعتبر t
أن يكون str
ويتكرر على الشخصية حسب الشخصية.
()
هو رمز خاص ويشير إلى tuple
فارغة.
في 3 ، كما قد تكون قد اكتشفت بالفعل ، هناك فاصلة مفقودة بعد العنصر الخامس ( "that"
) في القائمة. لذلك عن طريق السلسلة الحرفية الضمنية ،
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
لم يتم رفع أي AssertionError
في المقتطف الرابع لأنه بدلاً من تأكيد التعبير الفردي a == b
، فإننا نؤكد التبل بأكمله. المقتطف التالي سوف يزيل الأشياء ،
> >> a = "python"
> >> b = "javascript"
> >> assert a == b
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError
>> > assert ( a == b , "Values are not equal" )
< stdin > : 1 : SyntaxWarning : assertion is always true , perhaps remove parentheses ?
>> > assert a == b , "Values are not equal"
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError : Values are not equal
بالنسبة إلى المقتطف الخامس ، فإن معظم الطرق التي تعدل عناصر كائنات التسلسل/رسم الخرائط مثل list.append
، dict.update
، list.sort
، إلخ. تعديل الكائنات في مكانها وإرجاع None
. الأساس المنطقي وراء ذلك هو تحسين الأداء من خلال تجنب إنشاء نسخة من الكائن إذا كان يمكن إجراء العملية في مكانها (المشار إليها من هنا).
أخيرًا ، يجب أن يكون كائنًا قابلاً للتغيير (مثل list
) يمكن تغييره في الوظيفة ، وأن إعادة تعيين جهاز ثابت ( a -= 1
) ليس تغييرًا في القيمة.
إن إدراك هذه nitpicks يمكن أن توفر لك ساعات من جهد تصحيح الأخطاء على المدى الطويل.
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
، ولكن وفقًا للمستنداتإذا لم يتم تحديد SEP أو
None
، فسيتم تطبيق خوارزمية تقسيم مختلفة: يتم اعتبار تشغيل مسافة بيضاء متتالية بمثابة فاصل واحد ، ولن تحتوي النتيجة على سلاسل فارغة في البداية أو النهاية إذا كانت السلسلة تؤدي إلى ارتفاع مساحة البيضاء. وبالتالي ، فإن تقسيم سلسلة فارغة أو سلسلة تتكون من مساحة بيضاء فقط مع إرجاع فاصل لا شيء[]
. إذا تم إعطاء SEP ، لا يتم تجميع المحددات المتتالية معًا ويعتبرون تحديد السلاسل الفارغة (على سبيل المثال ،'1,,2'.split(',')
['1', '', '2']
). تقسيم سلسلة فارغة مع إرجاع فاصل محدد['']
.
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
الإخراج
> >> from module import *
> >> some_weird_name_func_ ()
"works!"
> >> _another_weird_name_func ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name '_another_weird_name_func' is not defined
غالبًا ما يكون من المستحسن عدم استخدام واردات البطاقة البرية. السبب الأول الواضح لذلك ، في واردات Wildcard ، لا يتم استيراد الأسماء ذات السطح السفلي الرائد. قد يؤدي هذا إلى أخطاء أثناء وقت التشغيل.
لو استخدمنا from ... import a, b, c
بناء جملة ، لما حدث NameError
أعلاه.
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
إذا كنت ترغب حقًا في استخدام واردات Wildcard ، فيجب عليك تحديد القائمة __all__
في الوحدة النمطية الخاصة بك والتي ستحتوي على قائمة بالأشياء العامة التي ستكون متاحة عندما نقوم باستيراد Wildcard.
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
الإخراج
> >> _another_weird_name_func ()
"works!"
> >> some_weird_name_func_ ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'some_weird_name_func_' is not defined
> >> x = 7 , 8 , 9
> >> sorted ( x ) == x
False
> >> sorted ( x ) == sorted ( x )
True
> >> y = reversed ( x )
> >> sorted ( y ) == sorted ( y )
False
تُرجع الطريقة sorted
دائمًا قائمة ، ومقارنة القوائم و tuples False
دائمًا في Python.
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
على عكس sorted
، تُرجع الطريقة reversed
إلى المتكرر. لماذا؟ نظرًا لأن الفرز يتطلب تعديل التكرار في مكانه أو استخدام حاوية إضافية (قائمة) ، في حين أن العكس يمكن أن يعمل ببساطة عن طريق التكرار من الفهرس الأخير إلى الأول.
لذلك أثناء sorted(y) == sorted(y)
، فإن المكالمة الأولى لـ sorted()
ستستهلك Iterator y
، وستعود المكالمة التالية فقط إلى قائمة فارغة.
> >> x = 7 , 8 , 9
> >> y = reversed ( x )
> >> sorted ( y ), sorted ( y )
([ 7 , 8 , 9 ], [])
from datetime import datetime
midnight = datetime ( 2018 , 1 , 1 , 0 , 0 )
midnight_time = midnight . time ()
noon = datetime ( 2018 , 1 , 1 , 12 , 0 )
noon_time = noon . time ()
if midnight_time :
print ( "Time at midnight is" , midnight_time )
if noon_time :
print ( "Time at noon is" , noon_time )
الإخراج (<3.5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
لم يتم طباعة وقت منتصف الليل.
قبل Python 3.5 ، تم اعتبار القيمة المنطقية لكائن datetime.time
False
إذا كانت تمثل منتصف الليل في UTC. إنه معرض للخطأ عند استخدام if obj:
بناء جملة للتحقق مما إذا كان obj
لاغال أو ما يعادل "فارغ".
يحتوي هذا القسم على عدد قليل من الأشياء الأقل شهرة والمثيرة للاهتمام حول بيثون أن معظم المبتدئين مثلي غير مدركين (حسنًا ، ليس بعد الآن).
حسنا، هنا تذهب
import antigravity
الإخراج: SSHH ... إنه سني للغاية.
antigravity
هي واحدة من بيض عيد الفصح القليلة التي صدرها مطورو Python.import antigravity
متصفحًا ويب يشير إلى كوميدي XKCD الكلاسيكي حول Python.goto
، ولكن لماذا؟ from goto import goto , label
for i in range ( 9 ):
for j in range ( 9 ):
for k in range ( 9 ):
print ( "I am trapped, please rescue!" )
if k == 2 :
goto . breakout # breaking out from a deeply nested loop
label . breakout
print ( "Freedom!" )
الإخراج (بيثون 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
في Python كنكتة Fool في 1 أبريل 2004.goto
في Python.إذا كنت أحد الأشخاص الذين لا يحبون استخدام المساحة البيضاء في بيثون للدلالة على النطاقات ، فيمكنك استخدام C-style {} من خلال استيراد ،
from __future__ import braces
الإخراج:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
الأقواس؟ مستحيل! إذا كنت تعتقد أن هذا أمر مخيب للآمال ، فاستخدم Java. حسنًا ، شيء آخر مثير للدهشة ، هل يمكنك العثور على أين يتم تربيته SyntaxError
في رمز الوحدة النمطية __future__
؟
__future__
لتوفير ميزات من الإصدارات المستقبلية من Python. ومع ذلك ، فإن "المستقبل" في هذا السياق المحدد هو أمر مثير للسخرية.future.c
.future.c
قبل معاملته كبيان استيراد عادي.الإخراج (Python 3.x)
> >> from __future__ import barry_as_FLUFL
> >> "Ruby" != "Python" # there's no doubt about it
File "some_file.py" , line 1
"Ruby" != "Python"
^
SyntaxError : invalid syntax
>> > "Ruby" <> "Python"
True
ها نحن ذا.
هذا مهم لـ PEP-401 الذي تم إصداره في 1 أبريل 2009 (أنت تعرف الآن ، ماذا يعني ذلك).
نقلاً عن PEP-401
أدركت أن! = مشغل عدم المساواة في Python 3.0 كان خطأ فظيعًا ، يحفز الإصبع ، يعيد Flufl مشغل <> Diamond باعتباره الهجاء الوحيد.
كان هناك المزيد من الأشياء التي كان على العم باري مشاركتها في PEP. يمكنك قراءتها هنا.
إنه يعمل بشكل جيد في بيئة تفاعلية ، ولكنه سيثير SyntaxError
عند التشغيل عبر ملف Python (انظر هذه المشكلة). ومع ذلك ، يمكنك لف العبارة داخل eval
أو compile
لجعلها تعمل ،
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
انتظر ما هذا ؟ this
هو الحب ❤
الإخراج:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
إنه زن بيثون!
> >> love = this
> >> this is love
True
> >> love is True
False
> >> love is False
False
> >> love is not True or False
True
> >> love is not True or False ; love is love # Love is complicated
True
this
الوحدة في بيثون هي بيضة عيد الفصح لزين بيثون (PEP 20).love is not True or False; love is love
، المفارقة ولكنه محسوس ذاتي (إن لم يكن كذلك ، يرجى الاطلاع على الأمثلة المتعلقة بـ is
is not
عواملهم). شرط else
للحلقات. قد يكون أحد الأمثلة النموذجية:
def does_exists_num ( l , to_find ):
for num in l :
if num == to_find :
print ( "Exists!" )
break
else :
print ( "Does not exist" )
الإخراج:
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> does_exists_num ( some_list , 4 )
Exists !
>> > does_exists_num ( some_list , - 1 )
Does not exist
شرط else
في التعامل مع الاستثناء. مثال ،
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
الإخراج:
Try block executed successfully ...
else
بعد حلقة فقط عندما لا يكون هناك break
صريحة بعد كل التكرارات. يمكنك التفكير في الأمر كفقرة "nobreak".else
بعد مجموعة المحاولة أيضًا "جملة الانتهاء" لأن الوصول إلى شرط else
في بيان try
يعني أن كتلة المحاولة قد اكتملت بالفعل بنجاح. def some_func ():
Ellipsis
الإخراج
> >> some_func ()
# No output, No Error
> >> SomeRandomString
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'SomeRandomString' is not defined
> >> Ellipsis
Ellipsis
Ellipsis
كائنًا مدمجًا على مستوى العالم وهو ما يعادل ...
> >> ...
Ellipsis
pass
) > >> import numpy as np
> >> three_dimensional_array = np . arange ( 8 ). reshape ( 2 , 2 , 2 )
array ([
[
[ 0 , 1 ],
[ 2 , 3 ]
],
[
[ 4 , 5 ],
[ 6 , 7 ]
]
])
three_dimensional_array
هي مجموعة من مجموعة المصفوفات. دعنا نقول أننا نريد طباعة العنصر الثاني (الفهرس 1
) لجميع المصفوفات الأعمق ، يمكننا استخدام القطع النارية لتجاوز جميع الأبعاد السابقة > >> three_dimensional_array [:,:, 1 ]
array ([[ 1 , 3 ],
[ 5 , 7 ]])
> >> three_dimensional_array [..., 1 ] # using Ellipsis.
array ([[ 1 , 3 ],
[ 5 , 7 ]])
n_dimensional_array[firs_dim_slice, ..., last_dim_slice]
)(Callable[..., int]
أو Tuple[str, ...]
))الهجاء المقصود. من فضلك ، لا ترسل رقعة لهذا.
الإخراج (Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
هي "-10⁵ x π" في python 3 ، في حين "-10⁵ xe" في Python 2.1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
الإخراج:
> >> Yo (). bro
True
> >> Yo (). __honey
AttributeError : 'Yo' object has no attribute '__honey'
> >> Yo (). _Yo__honey
True
2.
class Yo ( object ):
def __init__ ( self ):
# Let's try something symmetrical this time
self . __honey__ = True
self . bro = True
الإخراج:
> >> Yo (). bro
True
> >> Yo (). _Yo__honey__
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'Yo' object has no attribute '_Yo__honey__'
لماذا عملت Yo()._Yo__honey
؟
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
الإخراج:
> >> A (). __variable
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'A' object has no attribute '__variable'
> >> A (). some_func ()
'Some value'
__
(Double Siderscore AKA "Dunder") ولا ينتهي بأكثر من واحد من السطح الزائد عن طريق إضافة _NameOfTheClass
في المقدمة.__honey
في المقتطف الأول ، اضطررنا إلى إلحاق _Yo
إلى المقدمة ، مما يمنع التعارض مع سمة الاسم نفسها المحددة في أي فئة أخرى.__variable
في العبارة return __variable
إلى _A__variable
، والذي يحدث أيضًا أن يكون اسم المتغير الذي أعلنناه في النطاق الخارجي.الإخراج:
> >> value = 11
> >> valuе = 32
> >> value
11
ووت؟
ملاحظة: أسهل طريقة لإعادة إنتاج هذا هو ببساطة نسخ العبارات من المقتطف أعلاه ولصقها في ملف/قذيفة.
تبدو بعض الشخصيات غير الغربية متطابقة مع الحروف في الأبجدية الإنجليزية ولكنها تعتبر متميزة من قبل المترجم.
> >> ord ( 'е' ) # cyrillic 'e' (Ye)
1077
> >> ord ( 'e' ) # latin 'e', as used in English and typed using standard keyboard
101
> >> 'е' == 'e'
False
> >> value = 42 # latin e
> >> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
> >> value
42
تقوم دالة ord()
المدمجة () بإرجاع نقطة رمز Unicode الخاصة بحرف ، ومواضع رمز مختلفة من السيريلية "E" واللاتينية "E" تبرر سلوك المثال أعلاه.
# `pip install numpy` first.
import numpy as np
def energy_send ( x ):
# Initializing a numpy array
np . array ([ float ( x )])
def energy_receive ():
# Return an empty numpy array
return np . empty ((), dtype = np . float ). tolist ()
الإخراج:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
أين جائزة نوبل؟
energy_send
، بحيث تكون مساحة الذاكرة مجانية لإعادة تخصيصها.numpy.empty()
إرجاع فتحة الذاكرة المجانية التالية دون إعادة تعزيزها. تحدث بقعة الذاكرة هذه هي نفسها التي تم تحريرها (عادة ، ولكن ليس دائمًا). def square ( x ):
"""
A simple function to calculate the square of a number by addition.
"""
sum_so_far = 0
for counter in range ( x ):
sum_so_far = sum_so_far + x
return sum_so_far
الإخراج (Python 2.x):
> >> square ( 10 )
10
ألا يجب أن يكون هذا 100؟
ملاحظة: إذا لم تكن قادرًا على إعادة إنتاج هذا ، فحاول تشغيل الملف mixed_tabs_and_spaces.py عبر shell.
لا تخلط علامات التبويب والمساحات! الحرف الذي يسبق العائد هو "علامة تبويب" ، ويتم وضع مسافة بادئة من خلال مضاعف "4 مسافات" في مكان آخر في المثال.
هذه هي الطريقة التي يتعامل بها بيثون:
أولاً ، يتم استبدال علامات التبويب (من اليسار إلى اليمين) بواحدة إلى ثمانية مساحات بحيث يكون العدد الإجمالي للأحرف حتى البديل هو مضاعف من ثمانية <...>
لذلك يتم استبدال "علامة التبويب" في السطر الأخير من وظيفة square
بثمانية مسافات ، ويدخل في الحلقة.
Python 3 لطيف بما يكفي لرمي خطأ لمثل هذه الحالات تلقائيًا.
الإخراج (Python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
أسرع # using "+", three strings:
> >> timeit . timeit ( "s1 = s1 + s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.25748300552368164
# using "+=", three strings:
> >> timeit . timeit ( "s1 += s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.012188911437988281
+=
أسرع من +
لتسلسل أكثر من سلسلتين لأن السلسلة الأولى (مثال ، s1
لـ s1 += s2 + s3
) لم يتم تدميرها أثناء حساب السلسلة الكاملة. def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s += "xyz"
assert len ( s ) == 3 * iters
def add_bytes_with_plus ( iters ):
s = b""
for i in range ( iters ):
s += b"xyz"
assert len ( s ) == 3 * iters
def add_string_with_format ( iters ):
fs = "{}" * iters
s = fs . format ( * ([ "xyz" ] * iters ))
assert len ( s ) == 3 * iters
def add_string_with_join ( iters ):
l = []
for i in range ( iters ):
l . append ( "xyz" )
s = "" . join ( l )
assert len ( s ) == 3 * iters
def convert_list_to_string ( l , iters ):
s = "" . join ( l )
assert len ( s ) == 3 * iters
الإخراج:
# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
> >> NUM_ITERS = 1000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS )
124 µs ± 4.73 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS )
211 µs ± 10.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS )
61 µs ± 2.18 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS )
117 µs ± 3.21 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS )
10.1 µs ± 1.06 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
دعنا نزيد من عدد التكرارات بعامل 10.
> >> NUM_ITERS = 10000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS ) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS ) # Quadratic increase
6.82 ms ± 134 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS ) # Linear increase
645 µs ± 24.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS ) # Linear increase
1.17 ms ± 7.25 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS ) # Linear increase
86.3 µs ± 2 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
يمكنك قراءة المزيد عن TimeIt أو ٪ timeit على هذه الروابط. يتم استخدامها لقياس وقت تنفيذ قطع الرمز.
لا تستخدم +
لتوليد سلاسل طويلة - في Python ، str
غير قابلة للتغيير ، لذلك يجب نسخ السلاسل اليمنى واليسرى في السلسلة الجديدة لكل زوج من التسلسل. إذا قمت بتسلسل أربعة سلاسل بطول 10 ، فستقوم بنسخ (10+10)+((10+10) +10)+(((10+10) +10) +10) = 90 حرفًا بدلاً من 40 الشخصيات. تزداد الأمور أسوأ من الناحية الترفيهية مع زيادة عدد وحجم السلسلة (مبررة مع أوقات تنفيذ وظيفة add_bytes_with_plus
)
لذلك ، ينصح باستخدام .format.
أو بناء الجملة %
(ومع ذلك ، فهي أبطأ قليلاً من +
للسلاسل القصيرة جدا).
أو أفضل ، إذا كنت بالفعل محتويات متوفرة في شكل كائن غير قابلة للتطبيق ، فاستخدم ''.join(iterable_object)
على عكس add_bytes_with_plus
بسبب التحسينات +=
التي تمت مناقشتها في المثال السابق ، لم تظهر add_string_with_plus
زيادة تربيعية في وقت التنفيذ. لو كان البيان s = s + "x" + "y" + "z"
بدلاً من s += "xyz"
، كانت الزيادة تربيعية.
def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s = s + "x" + "y" + "z"
assert len ( s ) == 3 * iters
> >> % timeit - n100 add_string_with_plus ( 1000 )
388 µs ± 22.4 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n100 add_string_with_plus ( 10000 ) # Quadratic increase in execution time
9 ms ± 298 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
هناك العديد من الطرق لتنسيق وإنشاء سلسلة عملاقة إلى حد ما على عكس Zen of Python ، والتي ، وفقًا لها ، والتي ،
يجب أن يكون هناك واحد-ويفضل أن يكون هناك طريقة واحدة فقط-غريبة للقيام بذلك.
dict
* some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
الإخراج:
> >> % timeit some_dict [ '5' ]
28.6 ns ± 0.115 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> some_dict [ 1 ] = 1
> >> % timeit some_dict [ '5' ]
37.2 ns ± 0.265 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> % timeit another_dict [ '5' ]
28.5 ns ± 0.142 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> another_dict [ 1 ] # Trying to access a key that doesn't exist
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
KeyError : 1
> >> % timeit another_dict [ '5' ]
38.5 ns ± 0.0913 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
لماذا تصبح نفس عمليات البحث أبطأ؟
str
، int
، أي كائن ...) ، وتخصيص متخصصة للحالة الشائعة للقواميس المكونة من مفاتيح str
-only.lookdict_unicode
في مصدر Cpython) أن جميع المفاتيح الموجودة (بما في ذلك مفتاح الموقف) هي سلاسل ، وتستخدم مقارنة سلسلة أسرع وأبسط بمفاتيح المقارنة ، بدلاً من استدعاء طريقة __eq__
.dict
بمفتاح غير str
، يتم تعديله حتى تستخدم عمليات البحث في المستقبل الوظيفة العامة.dict
المعين ، ولا يجب أن يكون المفتاح موجودًا في القاموس. لهذا السبب فإن محاولة البحث الفاشل لها نفس التأثير.dict
s * import sys
class SomeClass :
def __init__ ( self ):
self . some_attr1 = 1
self . some_attr2 = 2
self . some_attr3 = 3
self . some_attr4 = 4
def dict_size ( o ):
return sys . getsizeof ( o . __dict__ )
الإخراج: (Python 3.8 ، قد تختلف إصدارات Python 3 الأخرى قليلاً)
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104
> >> dict_size ( o2 )
104
> >> del o1 . some_attr1
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
> >> dict_size ( o1 )
232
لنحاول مرة أخرى ... في مترجم جديد:
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104 # as expected
> >> o1 . some_attr5 = 5
> >> o1 . some_attr6 = 6
> >> dict_size ( o1 )
360
> >> dict_size ( o2 )
272
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
ما الذي يجعل هذه القواميس متضخمة؟ ولماذا تم إنشاق الأشياء التي تم إنشاؤها حديثًا أيضًا؟
__init__
من المثيل الأول الذي تم إنشاؤه ، دون التسبب "unshare"). في حالة وجود حالات متعددة عند حدوث تغيير حجم ، يتم تعطيل مشاركة المفاتيح لجميع الحالات المستقبلية لنفس الفصل: لا يمكن لـ Cpython معرفة ما إذا كانت مثيلاتك تستخدم نفس مجموعة السمات بعد الآن ، وتقرر أن تنقذ على محاولة مشاركة مفاتيح.__init__
! join()
هي عملية سلسلة بدلاً من تشغيل القائمة. (نوع من الاستخدام المضاد للاستخدام الأول)
Explanation: إذا كانت join()
طريقة على سلسلة ، فيمكنها العمل على أي شيء (قائمة ، tuple ، التكرار). إذا كانت طريقة في القائمة ، فيجب تنفيذها بشكل منفصل بواسطة كل نوع. أيضًا ، ليس من المنطقي وضع طريقة خاصة بالسلسلة على واجهة برمجة تطبيقات كائن list
عامة.
قليل من البيانات الغريبة ولكنها صحيحة بشكل دلالي:
[] = ()
list
tuple
'a'[0][0][0][0][0]
صحيح أيضًا بشكل صحيح ، لأن Python لا يحتوي على نوع بيانات الحرف مثل اللغات الأخرى المتفرعة من C. سلسلة أحادية الشحن.3 --0-- 5 == 8
و --5 == 5
True
بيانات صحيحة بشكل دلالي وتقييمها. بالنظر إلى أن a
هو رقم ، ++a
و --a
كلاهما بيانات Python صالحة ولكن لا تتصرف بنفس الطريقة مقارنة بالبيانات المماثلة بلغات مثل C أو C ++ أو Java.
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
توضيح:
++
في قواعد بيثون. هو في الواقع اثنين من المشغلين +
.++a
تحليلات AS +(+a)
التي تترجم إلى a
وبالمثل ، يمكن تبرير إخراج --a
.يجب أن تكون على دراية بمشغل الفظ في بيثون. ولكن هل سبق لك أن سمعت عن مشغل الفضاء ؟
> >> a = 42
> >> a -= - 1
> >> a
43
يتم استخدامه كمشغل لزيادة بديل ، جنبًا إلى جنب مع واحد آخر
> >> a += + 1
> >> a
> >> 44
Explanation: هذه المزحة تأتي من تغريدة Raymond Hettinger. مشغل الغزاة الفضائي هو في الواقع مجرد a -= (-1)
. وهو ما يعادل a = a - (- 1)
. مماثلة لحالة a += (+ 1)
.
بيثون لديه عامل التضمين غير الموثق.
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
Explanation: إذا استبدلت False
and True
بـ 0 و 1 وقمت بالرياضيات ، فإن جدول الحقيقة يعادل مشغل التضمين المتحدث. (مصدر)
نظرًا لأننا نتحدث عن المشغلين ، فهناك أيضًا @
مشغل لضرب المصفوفة (لا تقلق ، هذه المرة حقيقية).
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
Explanation: تمت إضافة المشغل @
في Python 3.5 مع مراعاة المجتمع العلمي. يمكن لأي كائن أن يفرط في التحميل __matmul__
السحر لتحديد السلوك لهذا المشغل.
من Python 3.8 فصاعدًا ، يمكنك استخدام بناء جملة F-bring النموذجي مثل f'{some_var=}
للتصحيح السريع. مثال،
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
يستخدم Python 2 بايت للتخزين المتغير المحلي في الوظائف. من الناحية النظرية ، هذا يعني أنه يمكن تحديد 65536 متغيرًا فقط في وظيفة. ومع ذلك ، فإن Python لديه محلول مفيد مدمج في تخزين أكثر من 2^16 أسماء متغيرة. يوضح الرمز التالي ما يحدث في المكدس عندما يتم تعريف أكثر من 65536 متغيرًا محليًا (تحذير: يطبع هذا الرمز حوالي 2^18 سطرًا من النص ، لذا كن مستعدًا!):
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
لن تقوم Threads Python المتعددة بتشغيل رمز Python الخاص بك بشكل متزامن (نعم ، لقد سمعت ذلك بشكل صحيح!). قد يبدو الأمر بديهيًا لتفريخ العديد من المواضيع والسماح لهم بتنفيذ رمز Python الخاص بك بشكل متزامن ، ولكن بسبب قفل المترجم العالمي في Python ، كل ما تفعله هو جعل مؤشرات الترابط الخاصة بك تنفذ على نفس المنعطف الأساسي. تعد خيوط Python جيدة للمهام المرتبطة بـ IO ، ولكن لتحقيق التوازي الفعلي في Python للمهام المرتبطة بوحدة المعالجة المركزية ، قد ترغب في استخدام وحدة المعالجة المتعددة Python.
في بعض الأحيان ، قد لا تطبع طريقة print
القيم على الفور. على سبيل المثال،
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
سيؤدي ذلك إلى طباعة wtfpython
بعد 3 ثوانٍ بسبب الوسيطة end
لأن المخزن المؤقت للإخراج يتم مسحه إما بعد مواجهة n
أو عندما ينهي البرنامج التنفيذ. يمكننا فرض المخزن المؤقت على التدفق عن طريق تمرير flush=True
.
قائمة التقطيع مع خارج الحدود المؤشرات لا ترمي أي أخطاء
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
إن تقطيعه لا يتجزأ من إنشاء كائن جديد دائمًا. على سبيل المثال،
> >> some_str = "wtfpython"
> >> some_list = [ 'w' , 't' , 'f' , 'p' , 'y' , 't' , 'h' , 'o' , 'n' ]
> >> some_list is some_list [:] # False expected because a new object is created.
False
> >> some_str is some_str [:] # True because strings are immutable, so making a new object is of not much use.
True
إرجاع int('١٢٣٤٥٦٧٨٩')
123456789
في Python 3. في Python ، تشمل الشخصيات العشرية أحرفًا أرقامًا ، وجميع الأحرف التي يمكن استخدامها لتشكيل أرقام Radix العشرية ، على سبيل المثال u+0660 ، صفر عربي. إليكم قصة مثيرة للاهتمام تتعلق بهذا السلوك من بيثون.
يمكنك فصل الحرفيات الرقمية مع السفلية (لتحسين القراءة) من Python 3 فصاعدا.
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
. إليك تنفيذ تقريبي لأسلوب count
، مما يجعل الأمور أكثر وضوحًا
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
يرجع السلوك إلى مطابقة الفرعية الفارغة ( ''
) مع شرائح الطول 0 في السلسلة الأصلية.
بعض الطرق التي يمكنك من خلالها المساهمة في wtfpython ،
يرجى الاطلاع على المساهمة. md لمزيد من التفاصيل. لا تتردد في إنشاء قضية جديدة لمناقشة الأشياء.
ملاحظة: من فضلك لا تتواصل مع طلبات التعداد الخلفي ، ولن تتم إضافة أي روابط ما لم تكن ذات صلة بالمشروع.
كانت الفكرة والتصميم لهذه المجموعة مستوحاة في البداية من خلال مشروع Denys Dovhan الرائع WTFJS. الدعم الساحق من قبل Pythonistas أعطاها الشكل الذي هو عليه الآن.
© Satwik Kansal
If you like wtfpython, you can use these quick links to share it with your friends,
Twitter | Linkedin | فيسبوك
I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.
That's all folks! For upcoming content like this, you can add your email here.