سؤال

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

1.should_equal(1)

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

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

للرد على بعضكم: السبب قد تريد القيام بذلك هو ببساطة جماليات/قابلية القراءة.

 item.price.should_equal(19.99)

هذا يشبه اللغة الإنجليزية ويشير بوضوح إلى القيمة التي تم اختبارها والتي هي القيمة المتوقعة ، كما هو مفترض:

should_equal(item.price, 19.99)

هذا المفهوم هو ما RSPEC وتستند بعض الأطر Ruby الأخرى على.

هل كانت مفيدة؟

المحلول

ماذا تقصد بالضبط بتصحيح القرد هنا؟ هناك عدة تعريفات مختلفة قليلاً.

إذا كنت تقصد ، "هل يمكنك تغيير أساليب الفصل في وقت التشغيل؟" ، فإن الإجابة هي نعم بشكل قاطع:

class Foo:
  pass # dummy class

Foo.bar = lambda self: 42

x = Foo()
print x.bar()

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

class Foo:
  pass # dummy class

x = Foo()

Foo.bar = lambda self: 42

print x.bar()

لكن لا يمكنك القيام بذلك لبعض الفصول المضمنة ، مثل int أو float. يتم تنفيذ طرق هذه الفئات في C وهناك تجريدات معينة تم التضحية بها من أجل جعل التنفيذ أسهل وأكثر كفاءة.

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

نصائح أخرى

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

ومع ذلك ، قد تكون الفصول المحددة في كود Python مقلوبًا لأنها محلية لهذا المترجم المترجم.

def should_equal_def(self, value):
    if self != value:
        raise ValueError, "%r should equal %r" % (self, value)

class MyPatchedInt(int):
    should_equal=should_equal_def

class MyPatchedStr(str):
    should_equal=should_equal_def

import __builtin__
__builtin__.str = MyPatchedStr
__builtin__.int = MyPatchedInt

int(1).should_equal(1)
str("44").should_equal("44")

استمتع ؛)

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

http://clarete.github.io/forbiddenfruit/؟goback=.gde_50788_member_228887816

أو

https://pypi.python.org/pypi/forbiddenfruit/0.1.0

مع مثال السؤال الأصلي ، بعد كتابة وظيفة "Queens_equal" ، ستفعل فقط

from forbiddenfruit import curse
curse(int, "should_equal", should_equal)

وأنت على ما يرام! هناك أيضًا وظيفة "عكسية" لإزالة طريقة مصححة.

أنواع Python الأساسية غير قابلة للتغيير حسب التصميم ، كما أشار المستخدمون الآخرون:

>>> int.frobnicate = lambda self: whatever()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'int'

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

>>> class MyInt(int):
...   def frobnicate(self):
...     print 'frobnicating %r' % self
... 
>>> five = MyInt(5)
>>> five.frobnicate()
frobnicating 5
>>> five + 8
13

ليست هناك حاجة لجعل MyInt الفئة الفرعية العامة ، إما ؛ يمكن للمرء أن يحدده تمامًا مباشرة في الوظيفة أو الطريقة التي تبني المثيل.

من المؤكد أن هناك بعض المواقف التي ينظر فيها مبرمجي Python الذين يجيدون في المصطلح إلى هذا النوع من التصنيف الفرعي للشيء الصحيح الذي يجب القيام به. على سبيل المثال، os.stat() إرجاع أ tuple الفئة الفرعية التي تضيف أعضاء اسمها ، على وجه التحديد من أجل معالجة نوع القابلية للقراءة التي تشير إليها في مثالك.

>>> import os
>>> st = os.stat('.')
>>> st
(16877, 34996226, 65024L, 69, 1000, 1000, 4096, 1223697425, 1223699268, 1223699268)
>>> st[6]
4096
>>> st.st_size
4096

ومع ذلك ، في المثال المحدد الذي تقدمه ، لا أعتقد أن التصنيف الفرعي float في item.price (أو في أي مكان آخر) سيكون من المحتمل جدًا أن يعتبر الشيء القوي الذي يجب القيام به. أنا يستطيع تخيل بسهولة شخص يقرر إضافة أ price_should_equal() طريقة ل item إذا كانت هذه هي حالة الاستخدام الأساسية ؛ إذا كان المرء يبحث عن شيء أكثر عمومية ، فربما يكون من المنطقي استخدام الحجج المسماة لجعل المعنى المقصود أكثر وضوحًا ، كما في

should_equal(observed=item.price, expected=19.99)

او هناك شيء ما على طول هذه الخطوط. إنه مطول بعض الشيء ، لكن بلا شك يمكن تحسينه. ميزة محتملة لمثل هذا النهج على وضع قرد على طراز الياقوت هو ذلك should_equal() يمكن بسهولة إجراء المقارنة بين أي نوع ، وليس فقط int أو float. لكن ربما أكون قد وقعت في تفاصيل المثال المعين الذي حدث لتقديمه.

لا يمكنك تصحيح الأنواع الأساسية في بيثون. ومع ذلك ، يمكنك استخدام Pipe لكتابة رمز أكثر قابلية للقراءة الإنسان:

from pipe import *

@Pipe
def should_equal(obj, val):
    if obj==val: return True
    return False

class dummy: pass
item=dummy()
item.value=19.99

print item.value | should_equal(19.99)

إليك مثال على التنفيذ item.price.should_equal, ، على الرغم من أنني سأستخدم العشرية بدلاً من التعويم في برنامج حقيقي:

class Price(float):
    def __init__(self, val=None):
        float.__init__(self)
        if val is not None:
            self = val

    def should_equal(self, val):
        assert self == val, (self, val)

class Item(object):
    def __init__(self, name, price=None):
        self.name = name
        self.price = Price(price)

item = Item("spam", 3.99)
item.price.should_equal(3.99)

إذا كنت حقا حقا حقًا تريد القيام بتصحيح قرد في بيثون ، يمكنك القيام باختراق (sortof) مع تقنية "استيراد FOO AS BAR".

إذا كان لديك فئة مثل TelnetConnection ، وكنت ترغب في تمديده ، فطارده الفرعي في ملف منفصل وتسميته شيئًا مثل TelnetConnecteded.

ثم ، في الجزء العلوي من الكود الخاص بك ، حيث تقول عادة:

import TelnetConnection

تغيير ذلك ليكون:

import TelnetConnectionExtended as TelnetConnection

ثم في كل مكان في الكود الذي تشير إليه TelnetConnection سوف يشير فعليًا إلى TelnetConnectionEdented.

للأسف ، يفترض هذا أن لديك إمكانية الوصول إلى تلك الفئة ، و "AS" تعمل فقط في هذا الملف المعين (إنه ليس قسوة عالمية) ، لكنني وجدت أنه مفيد من وقت لآخر.

لا ، لكن لديك مستخدمي المستخدم وقائمة المستخدم التي صنعت مع وضع ذلك في الاعتبار بالضبط.

إذا كنت جوجل ستجد أمثلة لأنواع أخرى ، ولكن تم تصميم هذا.

بشكل عام القرد ، يتم استخدام ترقيع أقل في بيثون منه في روبي.

لا ، لا يمكنك فعل ذلك في بيثون. أنا أعتبر أنه شيء جيد.

ماذا فعلت should_equal فعل؟ هل هو عودة منطقية True أو False؟ في هذه الحالة ، تم تهجئتها:

item.price == 19.99

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

يفعل should_equal بدلاً من ذلك ، قم بتعيين نوع من المدقق؟ (لماذا يقتصر المدقق على قيمة واحدة؟ لماذا لا تضع القيمة فقط وعدم تحديثها بعد ذلك؟) إذا كنت تريد التحقق من المدقق ، فهذا لا يمكن أن يعمل أبدًا على أي حال ، لأنك تقترح تعديل عدد صحيح أو جميعًا الأعداد الصحيحة. (مدقق يتطلب 18.99 لتساوي 19.99 سوف تفشل دائمًا.) بدلاً من ذلك ، يمكنك تهجئته مثل هذا:

item.price_should_equal(19.99)

أو هذا:

item.should_equal('price', 19.99)

وتحديد الأساليب المناسبة على فئة العنصر أو الفطائر الفائقة.

يبدو أن ما تريد كتابته حقًا هو:

assert item.price == 19.99

(بالطبع مقارنة العوامات للمساواة ، أو استخدام العوامات للأسعار ، هو فكرة سيئة, ، لذلك كنت تكتب assert item.price == Decimal(19.99) أو أي فئة رقمية كنت تستخدمها للسعر.)

يمكنك أيضًا استخدام إطار اختبار مثل Py.Test للحصول على مزيد من المعلومات حول التأكيدات الفاشلة في اختباراتك.

لا ، للأسف لا يمكنك تمديد الأنواع التي تم تنفيذها في C في وقت التشغيل.

يمكنك الفئة الفرعية int ، على الرغم من أنها غير تافهة ، قد تضطر إلى تجاوز __new__.

لديك أيضًا مشكلة في بناء الجملة:

1.somemethod()  # invalid

لكن

(1).__eq__(1)  # valid

إليكم كيف أحقق.

result = calculate_result('blah') # some method defined somewhere else

the(result).should.equal(42)

أو

the(result).should_NOT.equal(41)

قمت بتضمين طريقة ديكور لتوسيع هذا السلوك في وقت التشغيل على طريقة قائمة بذاتها:

@should_expectation
def be_42(self)
    self._assert(
        action=lambda: self._value == 42,
        report=lambda: "'{0}' should equal '5'.".format(self._value)
    )

result = 42

the(result).should.be_42()

عليك أن تعرف قليلا عن الداخلية ولكنها تعمل.

هذا هو المصدر:

https://github.com/mdwhatcott/pyspecs

إنه أيضًا على Pypi تحت Pyspecs.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top