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() با پیمانه و عملیات رشته‌بیتی اضافه می‌کند: <<، >>، &، ^، |، ~.

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

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

Adding more numeric ABCs

البته، کلاس‌های پایه انتزاعی (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

There are 5 different cases for a mixed-type operation on subclasses of Complex. All of the above code that doesn't refer to MyIntegral and OtherTypeIKnowAbout is referred to as "boilerplate".

In this description, a is an instance of A and b is an instance of B, where A and B are both subtypes of Complex. Consider a + b:

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

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

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

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

  5. If B is a subtype of A, Python tries B.__radd__ before A.__add__. This is ok, because it was implemented with knowledge of A, so it can handle those instances before delegating to Complex.

If A is a subtype of Complex and B is a subtype of Real without sharing any other knowledge, then the appropriate shared operation is the one involving the built in complex, and both __radd__() s land there, so 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)

# ...