نصائح وحيل من my Telegram-channelpythonetc ، أغسطس 2019



إنها مجموعة جديدة من النصائح والحيل حول Python والبرمجة من قناة Telegram-channelpythonetc.

المنشورات السابقة


إذا لم يكن لدى مثيل لفئة ما سمة تحمل الاسم المحدد ، فإنه يحاول الوصول إلى سمة الفصل بنفس الاسم.

>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2 

من السهل إلى حد ما أن يكون لدى إحدى السمات سمة لا توجد بها فئة أو لها سمة لها قيمة مختلفة:

 >>> class A: ... x = 2 ... def __init__(self): ... self.x = 3 ... self.y = 4 ... >>> A().x 3 >>> Ax 2 >>> A().y 4 >>> Ay AttributeError: type object 'A' has no attribute 'y' 

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

 class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None # see __set_name__ def __get__(self, instance, owner): if instance is not None: raise AttributeError( f'{instance} has no attribute {self._name}' ) return self._value def __set_name__(self, owner, name): self._name = name class_only = ClassOnlyDescriptor class A: x = class_only(2) print(Ax) # 2 A().x # raises AttributeError 

انظر أيضًا كيف يعمل مصمم ديكور جانغو classonlymethod : https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6


الدالات المعلنة في نص الفصل لا يمكنها رؤية نطاق الفصل. من المنطقي أن نطاق الفصل موجود فقط أثناء إنشاء الفصل.

 >>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined 

هذه ليست مشكلة عادة: يتم الإعلان عن الأساليب داخل الفصل لتصبح طرقًا ويتم استدعاؤها لاحقًا:

 >>> class A: ... x = 2 ... def f(self): ... print(self.x) ... >>> >>> >>> A().f() 2 

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

 >>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined 

الفهم ، ومع ذلك ، ليس لديهم إمكانية الوصول إلى self . الطريقة الوحيدة لإنجاحه هي إضافة نطاق إضافي (نعم ، هذا قبيح):

 >>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2] 



في Python ، None يساوي None لذا يبدو أنه يمكنك التحقق من بلا مع == :

 ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None: # ← ← ← exceptions = {} if word in exceptions: return exceptions[word] elif any(word.endswith(t) for t in ES_TAILS): return word + 'es' elif word.endswith('y'): return word[0:-1] + 'ies' else: return word + 's' exceptions = dict( mouse='mice', ) print(make_plural('python')) print(make_plural('bash')) print(make_plural('ruby')) print(make_plural('mouse', exceptions=exceptions)) 

هذا شيء خاطئ لفعله. None الواقع يساوي None ، لكنه ليس الشيء الوحيد الذي هو. قد تكون الكائنات المخصصة مساوية لـ بلا:

 >>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False 

الطريقة الصحيحة الوحيدة للمقارنة مع None هي استخدام is None .


يمكن أن تحتوي عوامات بايثون على قيم NaN. يمكنك الحصول على واحدة مع math.nan . nan لا يساوي أي شيء بما في ذلك نفسه:

 >>> math.nan == math.nan False 

أيضًا ، كائن NaN ليس فريدًا ، يمكن أن يكون لديك العديد من كائنات NaN مختلفة من مصادر مختلفة:

 >>> float('nan') nan >>> float('nan') is float('nan') False 

هذا يعني أنه لا يمكنك عمومًا استخدام NaN كمفتاح قاموس:

 >>> d = {} >>> d[float('nan')] = 1 >>> d[float('nan')] = 2 >>> d {nan: 1, nan: 2} 


تتيح لك typing تحديد النوع للمولدات. يمكنك أيضًا تحديد النوع الذي تم إنتاجه ، والنوع الذي يمكن إرساله إلى المولد وما يتم إرجاعه. Generator[int, None, bool] عبارة عن مولد ينتج عنه أعداد صحيحة ويعيد قيمة منطقية ولا يدعم g.send() .

هنا هو مثال أكثر تعقيدا قليلا. chain_while إنتاج chain_while من مولدات أخرى حتى يقوم أحدها بإرجاع شيء يمثل إشارة للتوقف وفقًا لوظيفة condition :

 from typing import Generator, Callable, Iterable, TypeVar Y = TypeVar('Y') S = TypeVar('S') R = TypeVar('R') def chain_while( iterables: Iterable[Generator[Y, S, R]], condition: Callable[[R], bool], ) -> Generator[Y, S, None]: for it in iterables: result = yield from it if not condition(result): break def r(x: int) -> Generator[int, None, bool]: yield from range(x) return x % 2 == 1 print(list(chain_while( [ r(5), r(4), r(3), ], lambda x: x is True, ))) 


إن شرح طريقة المصنع ليس بهذه البساطة كما قد يبدو. الإلحاح الفوري هو استخدام شيء مثل هذا:

 class A: @classmethod def create(cls) -> 'A': return cls() 

ومع ذلك ، هذا ليس الشيء الصحيح الذي يجب القيام به. the catch هو ، create لا يُرجع A ، يُرجع مثيل cls الذي هو A أو أي من أحفاده. انظر إلى هذا الكود:

 class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create() 

نتيجة فحص mypy هي error: Incompatible return value type (got "A", expected "B") . مرة أخرى ، المشكلة هي super().create() يتم إضافة تعليق super().create() لإرجاع A بينما تقوم بإرجاع B بوضوح في هذه الحالة.

يمكنك إصلاح ذلك عن طريق إضافة تعليقات cls باستخدام TypeVar:

 AType = TypeVar('AType') BType = TypeVar('BType') class A: @classmethod def create(cls: Type[AType]) -> AType: return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create() 

الآن create إرجاع مثيل فئة cls . ومع ذلك ، فإن هذه التعليقات التوضيحية فضفاضة جدًا ، فقد فقدنا المعلومات التي تفيد بأن cls هي نوع فرعي من A :

 AType = TypeVar('AType') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() 

الخطأ هو "Type[AType]" has no attribute "DATA" .

لإصلاح أنه يجب عليك تحديد AType بشكل صريح AType فرعي من A مع الوسيطة bound لـ TypeVar :

 AType = TypeVar('AType', bound='A') BType = TypeVar('BType', bound='B') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create() 

Source: https://habr.com/ru/post/ar466317/


All Articles