Python의 복소수 및 단위 테스트
저는 미래의 프로젝트를 위해 파이썬에 대한 지식과 경험이 필요한 과정을 막 시작했습니다. 저는 파이썬에 익숙해 지거나 피드백을받을 수있을 것이라고 생각했습니다. 2 시간 길이의 개요 비디오에서 언어의 주요 기능에 대한 적절한 요약을 얻었습니다 (https://www.youtube.com/watch?v=H1elmMBnykA), 혼자서 몇 가지 작은 일을 시도한 다음 좀 더 흥미로운 것으로 이동하기로 결정했습니다.
제목에서 알 수 있듯이 코드는 Complex복소수를 나타내는 ComplexTest, Complex클래스에 대한 단위 테스트 시퀀스의 두 클래스로 구성됩니다 . 파이썬이 기본적으로 복소수를 지원한다는 것을 알고 있습니다. 또한 모든 단위 테스트가 ComplexTest제대로 실행되고 통과 되었음을 언급해야합니다 .
나는 파이썬 코드를 처음 작성하는 것이기 때문에 말 그대로 코드의 모든 부분에 대한 주석에 관심이 있습니다. 모든 피드백을 환영합니다!
마지막으로, 저를 조금 괴롭혔던 점은 Python 2와 Python 3 사이의 명백한 충돌이었습니다.이 때문에 구현 방식이 Python 3 관점에서 "올바른"것인지 아닌지 확신 할 수 없었습니다. m 타겟팅).
나는 또한 내 세미콜론과 중괄호가 정말 그리워요 :(
ccomplex.py
from numbers import Number
import math
class Complex:
def __init__(self, re=0, im=0):
self._re = re
self._im = im
def __eq__(self, other):
if isinstance(other, Complex):
return self.re == other.re and self.im == other.im
else:
raise TypeError("The argument should be an instance of Complex")
def __neg__(self):
return Complex(-self.re, -self.im)
def __add__(self, other):
if isinstance(other, Complex):
return Complex(self.re + other.re, self.im + other.im)
else:
raise TypeError("The argument should be an instance of Complex")
def __sub__(self, other):
if isinstance(other, Complex):
return self + (-other)
else:
raise TypeError("The argument should be an instance of Complex")
def __mul__(self, other):
if isinstance(other, Complex):
a = self.re * other.re - self.im * other.im
b = self.re * other.im + self.im * other.re
return Complex(a, b)
elif isinstance(other, Number):
return Complex(self.re * other, self.im * other)
else:
raise TypeError(
"The argument should be an instance of Complex or Number")
def __rmul__(self, other):
return self * other
def __truediv__(self, other):
if isinstance(other, Complex):
if self.re == 0 and self.im == 0:
return Complex(0, 0)
if other.re == 0 and other.im == 0:
raise ValueError("The argument should be different from zero")
return (self * other.conj()) / other.mod_squared()
elif isinstance(other, Number):
return Complex(self.re / other, self.im / other)
else:
raise TypeError(
"The argument should be an instance of Complex or Number")
def __rtruediv__(self, other):
if isinstance(other, Complex):
if other.re == 0 and other.im == 0:
return Complex(0, 0)
if self.re == 0 and self.im == 0:
raise ValueError("The argument should be different from zero")
return (other * self.conj()) / self.mod_squared()
elif isinstance(other, Number):
return Complex(other, 0) / self
else:
raise TypeError(
"The argument should be an instance of Complex or Number")
def conj(self):
return Complex(self.re, -self.im)
def mod_squared(self):
return self.re * self.re + self.im * self.im
def mod(self):
return math.sqrt(self.mod_squared())
def arg(self):
return math.atan2(self.im, self.re)
@property
def re(self):
return self._re
@re.setter
def re(self, value):
self._re = value
@property
def im(self):
return self._im
@im.setter
def im(self, value):
self._im = value
def __str__(self):
op = "+" if self.im >= 0 else "-"
return "{} {} {}i".format(self.re, op, abs(self.im))
complexTest.py
from ccomplex import Complex
import math
import unittest
class ComplexTest(unittest.TestCase):
def test_equality(self):
self.assertTrue(Complex(2, 2) == Complex(2, 2))
def test_inequality(self):
self.assertFalse(Complex(1, 1) == Complex(2, 2))
def test_equality_raises_type_exception(self):
with self.assertRaises(TypeError):
z = Complex(2, 2) == "Not A Complex"
def test_negation(self):
self.assertEqual(-Complex(4, 4), Complex(-4, -4))
def test_sum(self):
z = Complex(2, 2)
self.assertEqual(z + z, Complex(4, 4))
def test_difference(self):
z = Complex(4, 4)
self.assertEqual(z - Complex(2, 2), Complex(2, 2))
def test_complex_product(self):
z1 = Complex(4, 4)
z2 = Complex(2, 2)
self.assertEqual(z1 * z2, Complex(0, 16))
def test_product_raises_type_exception(self):
with self.assertRaises(TypeError):
z = Complex(2, 2) * "Not A Complex"
def test_left_real_product(self):
z = Complex(2, 2)
self.assertEqual(z * 2, Complex(4, 4))
def test_right_real_product(self):
z = Complex(2, 2)
self.assertEqual(2 * z, Complex(4, 4))
def test_complex_division(self):
z1 = Complex(4, 4)
z2 = Complex(2, 2)
self.assertEqual(z1 / z2, Complex(2, 0))
def test_division_raises_type_exception(self):
with self.assertRaises(TypeError):
z = Complex(2, 2) / "Not A Complex"
def test_complex_division_raises_zero_division_exception(self):
with self.assertRaises(ValueError):
z = Complex(2, 2) / Complex(0, 0)
def test_real_division_raises_zero_division_exception(self):
with self.assertRaises(ZeroDivisionError):
z = Complex(2, 2) / 0
def test_left_real_division(self):
z = Complex(4, 4)
self.assertEqual(z / 2, Complex(2, 2))
def test_right_real_division(self):
z = Complex(2, 2)
self.assertEqual(2 / z, Complex(0.5, -0.5))
def test_conjugate(self):
z = Complex(2, 2)
self.assertEqual(z.conj(), Complex(2, -2))
def test_mod_squared(self):
z = Complex(2, 2)
self.assertAlmostEqual(z.mod_squared(), 8, delta=10e-16)
def test_mod(self):
z = Complex(2, 2)
self.assertAlmostEqual(z.mod(), 2 * math.sqrt(2), delta=10e-16)
def test_arg(self):
z = Complex(2, 2)
self.assertAlmostEqual(z.arg(), math.pi / 4, delta=10e-16)
if __name__ == '__main__':
unittest.main(verbosity=2)
답변
꽤 좋아 보인다.
나는 당신이 mod. 절대 값이라고도하며 파이썬이 사용하는 이름입니다. 를 구현 __abs__하면 Python의 abs함수에서 사용할 수 있습니다. 그럼 abs(Complex(3, 4))당신에게 줄 것 5.0입니다. 파이썬 자체처럼 abs(3 + 4j).
또 다른 유용한 것은 __bool__파이썬의 표준처럼 0을 거짓으로 선언 할 수있는입니다. 현재 실패합니다 (즉, 인쇄 됨).
if Complex(0, 0):
print('this should not get printed')
그런 다음 __truediv__메서드 내에서 두 번 사용할 수도 있습니다 . 처럼 if not self:.
(비) 동등 테스트는 확장 될 수 있습니다. 예를 들어, 내가 기대했던 Complex(3) == 3저를주고 True충돌하지. 그리고 내부 테스트 __truediv__는 if self == 0:.
Python 고유의 복소수가 무엇인지 살펴볼 수 있습니다.
>>> for name in dir(1j):
print(name)
__abs__
__add__
__bool__
__class__
__delattr__
__dir__
__divmod__
__doc__
__eq__
__float__
...
값 개체
다음은 대부분의 사용자가 예상치 못한 동작을 고려하는 것을 보여줍니다.
from ccomplex import Complex
a = Complex(5, 4) + Complex(3)
b = a
a.re = -a.re
print(b) # "-8 + 4i"
값은 일반적으로 변경 불가능한 것으로 간주됩니다. Python은 객체를 사용하여 값을 나타내고 객체는 공유 할 수있는 ID를 가지고 있으므로 일반적으로 값으로 간주되는 것을 만들 때 변경 불가능한 객체를 사용하는 것이 가장 좋습니다. 이것은 문자열을 수정하는 것처럼 보입니다.
a = "Hello"
a += " world"
그러나 연산자를 str구현하지 않기 때문에 __iadd__파이썬이 실제로하는 것은 a = a + " world"표현식을 평가 a + " world"하고 결과 (새 객체)를에 할당하는 문을로 해석하는 것 a입니다. 다른 개체가 해당 변수에 저장되기 때문에 명령문이 실행될 a때 의 변경 ID입니다 a += ....
>>> a = "hello"
>>> id(a)
1966355478512
>>> a += " world"
>>> id(a)
1966350779120
>>>
@re.setter및 @im.setter메서드를 제거하면 Complex클래스가 공개적으로 변경되지 않도록 변경됩니다. 그것은 좋은 시작이지만, 누군가가 내부를 직접 조작하는 것을 막는 것은 없습니다 a._re = 7.
이 클래스를 진정 불변으로 만드는 가장 쉬운 방법은 불변 기반에서 상속하는 것입니다. 적어도 Python 3.7을 사용하고 있다고 가정합니다.
from typing import NamedTuple
class Complex(NamedTuple):
re: float
im: float = 0
def __eq__(self, other):
if isinstance(other, Complex):
return self.re == other.re and self.im == other.im
else:
return NotImplemented
# ... etc ...
NamedTuple기본 클래스는 자동으로 그래서, 당신을위한 생성자를 생성 Complex(2, 3)하여 생산 2 + 3i복잡한 값입니다. im와 같이에 값이 제공되지 않으면 Complex(2)의 기본값 0이 사용됩니다 im.
re또는 im값 을 변경하려면 새 개체를 만들어야합니다.
a = Complex(-8, a.im)
또는 사용 NamedTuple._replace:
a = a._replace(re=-8)
구현되지 않음
현명한 독자는 return NotImplemented위에서 알아 차릴 것 입니다. 이것은 대안을 시도하기 위해 Python에 신호를 보내는 매직 싱글 톤입니다. 예를 들어, a == b에 폴백 수 not a.__neq__(b), b.__eq__(a)짝수 또는 not b.__ne__(a).
생각해보십시오 : Matrix수업에 대해서는 모를 수도 있지만 수업에 대해서는 알고있을 수 있습니다 Complex. 누군가가 cmplx * matrix, __mul__함수가을 올리면 TypeError게임 오버입니다. 대신 NotImplemented반환되면 Matrix.__rmul__시도 할 수 있으며 작동 할 수 있습니다.
NotImplemented 및 산술 연산 구현을 참조하십시오.
rtruediv
평가할 때 a / b먼저 시도 a.__truediv__(b)합니다. 실패하면 (정의되지 않았거나을 반환 NotImplemented) b.__rtruediv__(a)시도 할 수 있습니다.
class Complex:
...
def __rtruediv__(self, other):
if isinstance(other, Complex):
...
...
왜 isinstance(other, Complex)사실일까요? 그것은 그 모두 의미 할 것 self입니다 Complex(우리가있어 이후 Complex.__rtruediv__), 그리고 otherA는 Complex(이후 isinstance이 시나리오에서 그렇게 말했다). 그러나이 경우 우리는을 Complex() / Complex()하고 있으므로 __truediv__사용되어야 __rtruediv__하며 고려할 필요조차 없습니다.
0으로 나누기
이유는 무엇입니까 Complex(2, 2) / 0인상 a를 ZeroDivisionError같은 Complex(2, 2) / Complex(0, 0)을 제기은 ValueError? 그것은 제기해야하지 ZeroDivisionError않습니까?
테스트 이름 test_complex_division_raises_zero_division_exception이 with self.assertRaises(ValueError)조건 과 일치하지 않습니다. 즉 , 발생해야하는 사항을 알고 오류를 발견했지만 올바른 예외를 발생시키는 대신 발생한 조건과 일치하도록 테스트를 변경했습니다.