it-swarm.asia

أنواع المعلمات وظيفة في بيثون

ما لم أكن مخطئًا ، فإن إنشاء وظيفة في Python يعمل مثل هذا:

def my_func(param1, param2):
    # stuff

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

221
Leif Andersen

يتم كتابة Python بشدة لأن كل كائن لديه نوع ، كل كائن يعرف نوعه ، من المستحيل استخدام كائن من نوع عن طريق الخطأ أو عن قصد "كما لو كان" كائن a different type ، وجميع العمليات الأولية على الكائن يتم تفويضها إلى نوعه.

هذا لا علاقة له أسماء . A name في Python "ليس له نوع": إذا وعندما تم تعريف الاسم ، يشير الاسم إلى كائن ، و كائن لديه نوع (لكن هذا لا يفرض في الواقع نوعًا على name : الاسم هو الاسم).

يمكن أن يشير الاسم في Python تمامًا إلى كائنات مختلفة في أوقات مختلفة (كما هو الحال في معظم لغات البرمجة ، ولكن ليس كلها) - وليس هناك قيد على الاسم ، إذا كان قد أشار ذات مرة إلى كائن من النوع X ، بعد ذلك ، يتم تقييد الإشارة إلى الكائنات الأخرى من النوع X فقط. القيود على الأسماء ليست جزءًا من مفهوم "الكتابة القوية" ، على الرغم من أن بعض المتحمسين لـ ثابت الكتابة (حيث الأسماء تفعل قم بالتقييد ، وفي وقت ثابت ، يُعرف أيضًا باسم AKA-compile-time ، والأزياء أيضًا) إساءة استخدام المصطلح بهذه الطريقة.

138
Alex Martelli

قامت الإجابات الأخرى بعمل جيد في شرح كتابة البط و الإجابة البسيطة بواسطة tzot :

لا يحتوي Python على متغيرات ، مثل اللغات الأخرى حيث يكون للمتغيرات نوع وقيمة ؛ لديها أسماء تشير إلى كائنات ، والتي تعرف نوعها.

ومع ذلك ، تغير شيء واحد مثير للاهتمام منذ عام 2010 (عندما تم طرح السؤال لأول مرة) ، وهو تنفيذ PEP 3107 (تم تنفيذه في بيثون 3). يمكنك الآن بالفعل تحديد نوع المعلمة ونوع نوع الإرجاع للدالة مثل هذا:

def pick(l: list, index: int) -> int:
    return l[index]

يمكننا هنا أن نرى أن pick تأخذ معلمتين ، قائمة l و عدد صحيح index. يجب أيضًا إرجاع عدد صحيح.

لذلك ، يشير ضمنا إلى أن l هي قائمة بالأعداد الصحيحة التي يمكننا رؤيتها دون بذل الكثير من الجهد ، ولكن بالنسبة للوظائف الأكثر تعقيدًا ، يمكن أن تكون مربكة بعض الشيء فيما ينبغي أن تحتويه القائمة. نريد أيضًا أن تكون القيمة الافتراضية لـ index 0. ولحل هذه المشكلة ، يمكنك اختيار كتابة pick مثل هذا بدلاً من ذلك:

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

لاحظ أننا نضع الآن في سلسلة كنوع l ، المسموح به بناءً ، لكنها ليست جيدة للتحليل البرمجي (والذي سنعود إليه لاحقًا).

من المهم ملاحظة أن Python لن ترفع TypeError إذا مررت بتعويم إلى index ، والسبب في ذلك هو أحد النقاط الرئيسية في فلسفة تصميم Python: "نحن جميعًا نوافق على البالغين هنا" ، مما يعني أنه من المتوقع أن تكون على دراية بما يمكنك نقله إلى وظيفة وما لا يمكنك القيام به. إذا كنت تريد حقًا كتابة التعليمات البرمجية التي تلقي TypeErrors ، فيمكنك استخدام الدالة isinstance للتحقق من أن الوسيطة التي تم تمريرها من النوع المناسب أو فئة فرعية مثل هذا:

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

مزيد من المعلومات حول السبب الذي يجعلك نادراً ما تفعل هذا وما يجب عليك فعله هو الحديث عنه في القسم التالي وفي التعليقات.

PEP 3107 لا يحسن فقط قراءة الكود ولكن لديه أيضًا العديد من حالات الاستخدام المناسبة التي يمكنك قراءتها عن هنا .


حظي التعليق التوضيحي بالكثير من الاهتمام في Python 3.5 مع إدخال PEP 484 والذي يقدم وحدة نمطية قياسية لتلميحات الكتابة.

جاءت تلميحات الكتابة هذه من مدقق النوع mypy ( GitHub ) ، والتي أصبحت الآن PEP 484 متوافقة.

مع وحدة الكتابة تأتي مع مجموعة شاملة من تلميحات الكتابة ، بما في ذلك:

  • List ، Tuple ، Set ، Map - لـ list ، Tuple ، set و map على التوالي.
  • Iterable - مفيد للمولدات.
  • Any - عندما يمكن أن يكون أي شيء.
  • Union - عندما يمكن أن يكون أي شيء ضمن مجموعة محددة من الأنواع ، بدلاً من Any.
  • Optional - عندما يكون قد يكون بلا. اختصار لـ Union[T, None].
  • TypeVar - يستخدم مع الأدوية الجنيسة.
  • Callable - تستخدم في المقام الأول للوظائف ، ولكن يمكن استخدامها ل callables الأخرى.

هذه هي تلميحات النوع الأكثر شيوعًا. يمكن العثور على قائمة كاملة في وثائق وحدة الطباعة .

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

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

إحدى الميزات القوية هي Callable التي تتيح لك كتابة أساليب التعليق التوضيحي التي تأخذ دالة كوسيطة. فمثلا:

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

يمكن أن يصبح المثال أعلاه أكثر دقة من خلال استخدام TypeVar بدلاً من Any ، ولكن تم ترك هذا بمثابة تمرين للقارئ لأنني أعتقد أنني ملأت بالفعل إجابتي بالكثير من المعلومات حول الميزات الجديدة الرائعة التي تم تمكينها عن طريق كتابة تلميح .


في السابق عندما تم توثيق كود Python واحد على سبيل المثال Sphinx يمكن الحصول على بعض الوظائف المذكورة أعلاه من خلال كتابة مستندات منسقة مثل هذا:

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

كما ترون ، يأخذ هذا عددًا من الأسطر الإضافية (يعتمد الرقم الدقيق على مدى صراحة ما تريده وكيف تقوم بتنسيق سلسلة المستندات الخاصة بك). ولكن يجب أن يكون من الواضح الآن لك كيف توفر PEP 3107 بديلاً يفوق العديد من الطرق (كلها؟). هذا صحيح بشكل خاص في تركيبة مع PEP 484 والتي ، كما رأينا ، توفر وحدة نمطية قياسية تحدد بناء الجملة لهذه تلميحات/تعليقات توضيحية يمكن استخدامها بطريقة تجعلها غير مبهمة ودقيقة ولكنها مرنة ، مما يجعل لمجموعة قوية.

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


يمكن العثور على مثال لرمز Python الذي يستخدم بشكل كبير تلميح الكتابة هنا .

521
erb

أنت لا تحدد نوع. ستفشل الطريقة فقط (في وقت التشغيل) إذا حاولت الوصول إلى السمات التي لم يتم تعريفها في المعلمات التي تم تمريرها في.

لذلك هذه الوظيفة البسيطة:

def no_op(param1, param2):
    pass

... لن تفشل بغض النظر عن ما يتم تمرير اثنين من الحجج في.

ومع ذلك ، هذه الوظيفة:

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... ستفشل في وقت التشغيل إذا لم يكن لكل من param1 و param2 سمات قابلة للاستدعاء باسم quack.

13
TM.

العديد من اللغات لها متغيرات ، والتي من نوع معين ولها قيمة. بيثون ليس لديها متغيرات. له كائنات ، وتستخدم أسماء للإشارة إلى هذه الكائنات.

في لغات أخرى ، عندما تقول:

a = 1

ثم متغير (عدد صحيح عادة) يغير محتوياته إلى القيمة 1.

في بيثون ،

a = 1

يعني "استخدام الاسم a للإشارة إلى الكائن 1 ". يمكنك القيام بما يلي في جلسة بيثون التفاعلية:

>>> type(1)
<type 'int'>

تسمى الدالة type بالكائن 1 ؛ نظرًا لأن كل كائن يعرف نوعه ، فمن السهل بالنسبة لـ type اكتشاف النوع المذكور وإعادته.

وبالمثل ، كلما حددت وظيفة

def funcname(param1, param2):

تستقبل الوظيفة كائنين وتسميهما param1 و param2 ، بغض النظر عن أنواعهما. إذا كنت تريد التأكد من أن الكائنات المستلمة من نوع معين ، فقم بتدوين وظيفتك كما لو كانت من النوع (الأنواع) المطلوبة وتتبع الاستثناءات التي يتم طرحها إذا لم تكن كذلك. الاستثناءات التي يتم طرحها هي عادة TypeError (استخدمت عملية غير صالحة) و AttributeError (لقد حاولت الوصول إلى عضو غير موجود (الطرق هي أعضاء أيضًا)).

8
tzot

بيثون لا تكتب بقوة بمعنى التحقق من النوع الثابت أو وقت الترجمة.

تندرج معظم شفرة Python تحت ما يسمى "Duck Typing" - على سبيل المثال ، تبحث عن طريقة read على كائن - أنت لا تهتم إذا كان الكائن ملفًا على القرص أو مأخذ توصيل ، فقط أريد أن أقرأ N بايت منه.

6
Mark Rushakoff

كما يشرح أليكس مارتيلي ،

الحل العادي ، Pythonic ، المفضل هو دائمًا "كتابة البط": حاول استخدام الوسيطة كما لو كانت من نوع معين مرغوب فيه ، قم بذلك في محاولة/باستثناء بيان يلاحظ جميع الاستثناءات التي يمكن أن تنشأ إذا لم تكن الوسيطة في الواقع من هذا النوع (أو أي نوع آخر يحاكيه بشكل جيد ؛-) ، وفي الجملة ما عدا ، جرب شيئًا آخر (باستخدام الوسيطة "كما لو كانت" من نوع آخر).

قراءة بقية منصبه للحصول على معلومات مفيدة.

5
Nick Presta

لا تهتم بيثون بما تنقله إلى وظائفها. عند استدعاء my_func(a,b) ، ستحتفظ المتغيرات param1 و param2 بقيمتي a و b. لا يعلم Python أنك تتصل بالوظيفة بالأنواع المناسبة ، وتتوقع أن يهتم بها المبرمج. إذا تم استدعاء وظيفتك بأنواع مختلفة من المعلمات ، فيمكنك التفاف التعليمة البرمجية للوصول إليها باستخدام try/باستثناء الكتل وتقييم المعلمات بأي طريقة تريدها.

3
Kyle

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

عندما تقوم دالة str باستدعاء طريقة فئة __str__ ، تقوم بفحص نوعها بمهارة:

>>> class A(object):
...     def __str__(self):
...         return 'a','b'
...
>>> a = A()
>>> print a.__str__()
('a', 'b')
>>> print str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type Tuple)

كما لو أن Guido يلمح إلى الاستثناء الذي يجب أن يثيره البرنامج إذا واجه نوعًا غير متوقع.

2
Antony Hatchkins

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

بشكل عام ، يعود الأمر إلى الكود الخاص بك للتأكد من أنك تدور حول كائنات من النوع المناسب - لا يوجد مترجم لفرض ذلك في وقت مبكر.

2
Justin Ethier

في بيثون كل شيء له نوع. ستقوم دالة Python بعمل أي شيء يُطلب منه القيام به إذا كان نوع الوسائط يدعمها.

مثال: foo ستضيف كل شيء يمكن __add__ed ؛) دون الحاجة إلى القلق بشأن نوعه. وهذا يعني ، لتجنب الفشل ، يجب أن تقدم فقط تلك الأشياء التي تدعم الإضافة.

def foo(a,b):
    return a + b

class Bar(object):
    pass

class Zoo(object):
    def __add__(self, other):
        return 'zoom'

if __name__=='__main__':
    print foo(1, 2)
    print foo('james', 'bond')
    print foo(Zoo(), Zoo())
    print foo(Bar(), Bar()) # Should fail
1
Pratik Deoghare

لم أر هذا مذكورًا في إجابات أخرى ، لذا سأضيف هذا إلى الوعاء.

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

إحدى الأدوات الرئيسية للقيام بذلك هي وظيفة isinstance ().

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

def process(data):
    if not isinstance(data, bytes) and not isinstance(data, bytearray):
        raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data))
    # Do more stuff

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

1
Dread Quixadhal

لاستخدام وحدة الطباعة بفعالية (الجديد في Python 3.5) ، قم بتضمين الكل (*).

from typing import *

وستكون جاهزًا للاستخدام:

List, Tuple, Set, Map - for list, Tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

ومع ذلك ، لا يزال بإمكانك استخدام أسماء الأنواع مثل int ، list ، dict ، ...

0
prosti