numbers --- کلاس‌های پایه انتزاعی عددی

کد منبع: Lib/numbers.py


ماژول numbers (PEP 3141) سلسله‌مراتبی از کلاس‌های پایه انتزاعی عددی را تعریف می‌کند که به‌تدریج عملیات بیشتری را تعریف می‌کنند. هیچ‌کدام از انواع تعریف‌شده در این ماژول برای نمونه‌سازی در نظر گرفته نشده‌اند.

class numbers.Number

ریشه‌ی سلسله‌مراتب عددی. اگر فقط می‌خواهید بررسی کنید که آرگومان x یک عدد است، بدون توجه به نوع آن، از isinstance(x, Number) استفاده کنید.

برج عددی

class numbers.Complex

زیرکلاس‌های این نوع، اعداد مختلط را توصیف می‌کنند و شامل عملیات‌هایی می‌شوند که بر نوع توکار complex اعمال می‌شوند. این موارد عبارت‌اند از: تبدیل‌ها به complex و bool، real، imag، +، -، *، /، **، abs()، conjugate()، ==، و !=. همه به‌جز - و != انتزاعی هستند.

real

انتزاعی. کامپوننت حقیقی این عدد را بازیابی می‌کند.

imag

انتزاعی. کامپوننت موهومی این عدد را بازیابی می‌کند.

abstractmethod conjugate()

انتزاعی. مزدوج مختلط را برمی‌گرداند. برای مثال، (1+3j).conjugate() == (1-3j).

class numbers.Real

به Complex، کلاس Real عملگرهایی را می‌افزاید که روی اعداد حقیقی کار می‌کنند.

به‌طور خلاصه، آن‌ها عبارتند از: تبدیل به float، math.trunc()، round()، math.floor()، math.ceil()، divmod()، //، %، <، <=، >، و >=.

Real همچنین برای complex()، real، imag و conjugate() پیش‌فرض‌هایی فراهم می‌کند.

class numbers.Rational

زیرنوعی از Real است و ویژگی‌های numerator و denominator را می‌افزاید. همچنین یک پیاده‌سازی پیش‌فرض برای float() فراهم می‌کند.

مقدارهای numerator و denominator باید نمونه‌هایی از Integral باشند و باید در ساده‌ترین شکل باشند، به‌طوری که denominator مثبت باشد.

numerator

انتزاعی. صورت این عدد گویا.

denominator

انتزاعی. مخرج این عدد گویا.

class numbers.Integral

زیرنوعی از Rational است و یک تبدیل به int اضافه می‌کند. پیش‌فرض‌هایی را برای float()، numerator و denominator فراهم می‌کند. متدهای انتزاعی برای pow() با پیمانه و عملیات رشته‌بیتی اضافه می‌کند: <<، >>، &، ^، |، ~.

نکاتی برای پیاده‌سازان نوع

پیاده‌سازان باید دقت کنند که اعداد برابر، برابر باشند و برای آن‌ها مقادیر هش یکسانی تولید کنند. اگر دو گسترش متفاوت از اعداد حقیقی وجود داشته باشد، این موضوع ممکن است ظریف باشد. همچنین Hashing of numeric types را ببینید.

افزودن کلاس‌های پایه انتزاعی عددی بیشتر

البته، کلاس‌های پایه انتزاعی (ABC) بیشتری برای اعداد امکان‌پذیر هستند، و این سلسله‌مراتب ضعیفی خواهد بود اگر امکان افزودن آن‌ها را از بین ببرد. می‌توانید MyFoo را بین Complex و Real به این صورت اضافه کنید:

class MyFoo(Complex): ...
MyFoo.register(Real)

پیاده‌سازی عملیات حسابی

ما می‌خواهیم عملیات حسابی را به‌گونه‌ای پیاده‌سازی کنیم که عملیات حالت مختلط، یا یک پیاده‌سازی را فراخوانی کنند که نویسنده‌ی آن از نوع هر دو آرگومان آگاه بوده است، یا هر دو را به نزدیک‌ترین نوع توکار تبدیل کنند و عملیات را در آنجا انجام دهند. برای زیرنوع‌های Integral، این بدان معنا است که __add__() و __radd__() باید به‌صورت زیر تعریف شوند:

class MyIntegral(Integral):

    def __add__(self, other):
        if isinstance(other, MyIntegral):
            return do_my_adding_stuff(self, other)
        elif isinstance(other, OtherTypeIKnowAbout):
            return do_my_other_adding_stuff(self, other)
        else:
            return NotImplemented

    def __radd__(self, other):
        if isinstance(other, MyIntegral):
            return do_my_adding_stuff(other, self)
        elif isinstance(other, OtherTypeIKnowAbout):
            return do_my_other_adding_stuff(other, self)
        elif isinstance(other, Integral):
            return int(other) + int(self)
        elif isinstance(other, Real):
            return float(other) + float(self)
        elif isinstance(other, Complex):
            return complex(other) + complex(self)
        else:
            return NotImplemented

برای یک عملیات با نوع مختلط روی زیرکلاس‌های Complex، ۵ حالت مختلف وجود دارد. تمام کد بالا را که به MyIntegral و OtherTypeIKnowAbout ارجاع نمی‌دهد، «کد پیش‌ساخته (boilerplate)» می‌نامم. a نمونه‌ای از A خواهد بود که زیرنوعی از Complex است (a : A <: Complex)، و b : B <: Complex. a + b را در نظر می‌گیرم:

  1. اگر A متد __add__() را تعریف کند که b را بپذیرد، همه‌چیز درست است.

  2. اگر A به کد پیش‌ساخته متوسل شود و قرار باشد مقداری را از __add__() برگرداند، این امکان را از دست می‌دهیم که B __radd__() هوشمندتری تعریف کرده باشد؛ بنابراین کد پیش‌ساخته باید NotImplemented را از __add__() برگرداند. (یا ممکن است A اصلاً __add__() را پیاده‌سازی نکرده باشد.)

  3. سپس نوبت به __radd__() متعلق به B می‌رسد. اگر a را بپذیرد، همه‌چیز درست است.

  4. اگر به کد پیش‌ساخته (boilerplate) بازگردد، دیگر هیچ متد ممکنی برای امتحان باقی نمانده است، بنابراین این جایی است که پیاده‌سازی پیش‌فرض باید در آن قرار داشته باشد.

  5. اگر B <: A باشد، پایتون B.__radd__ را پیش از A.__add__ امتحان می‌کند. این مورد مشکلی ندارد، زیرا با آگاهی از A پیاده‌سازی شده است، بنابراین می‌تواند آن نمونه‌ها را پیش از واگذاری به Complex مدیریت کند.

اگر A <: Complex و B <: Real باشد و هیچ دانش دیگری بین آن‌ها مشترک نباشد، عملیات مشترک مناسب همان عملیاتی است که شامل complex توکار می‌شود، و هر دو __radd__() به آن می‌رسند؛ بنابراین a+b == b+a.

از آنجا که بیشتر عملیات روی هر نوع داده‌ای بسیار مشابه خواهد بود، تعریف یک تابع کمکی که نمونه‌های مستقیم و معکوس هر عملگر معین را تولید می‌کند، می‌تواند مفید باشد. برای مثال، fractions.Fraction از این روش استفاده می‌کند:

def _operator_fallbacks(monomorphic_operator, fallback_operator):
    def forward(a, b):
        if isinstance(b, (int, Fraction)):
            return monomorphic_operator(a, b)
        elif isinstance(b, float):
            return fallback_operator(float(a), b)
        elif isinstance(b, complex):
            return fallback_operator(complex(a), b)
        else:
            return NotImplemented
    forward.__name__ = '__' + fallback_operator.__name__ + '__'
    forward.__doc__ = monomorphic_operator.__doc__

    def reverse(b, a):
        if isinstance(a, Rational):
            # Includes ints.
            return monomorphic_operator(a, b)
        elif isinstance(a, Real):
            return fallback_operator(float(a), float(b))
        elif isinstance(a, Complex):
            return fallback_operator(complex(a), complex(b))
        else:
            return NotImplemented
    reverse.__name__ = '__r' + fallback_operator.__name__ + '__'
    reverse.__doc__ = monomorphic_operator.__doc__

    return forward, reverse

def _add(a, b):
    """a + b"""
    return Fraction(a.numerator * b.denominator +
                    b.numerator * a.denominator,
                    a.denominator * b.denominator)

__add__, __radd__ = _operator_fallbacks(_add, operator.add)

# ...