PyCharm Refactoring Tutorial
This tutorial shows some refactorings available in PyCharm, using the example of a simple class that makes use of the rational numbers.
Make sure that the following prerequisites are met:
You are working with PyCharm version 2016.2 or later.
A project is already created.
Create a Python file rational.py in your project and add the following code:
from collections import namedtuple
class Rational(namedtuple('Rational', ['num', 'denom'])):
def __new__(cls, num, denom):
if denom == 0:
raise ValueError('Denominator cannot be null')
if denom < 0:
num, denom = -num, -denom
return super().__new__(cls, num, denom)
def __str__(self):
return '{}/{}'.format(self.num, self.denom)
Let's simplify a rational number by dividing numerator and denominator by the greatest common divisor:
from collections import namedtuple
class Rational(namedtuple('Rational', ['num', 'denom'])):
def __new__(cls, num, denom):
if denom == 0:
raise ValueError('Denominator cannot be null')
if denom < 0:
num, denom = -num, -denom
x = abs(num)
y = abs(denom)
while x:
x, y = y % x, x
factor = y
return super().__new__(cls, num // factor, denom // factor)
def __str__(self):
return '{}/{}'.format(self.num, self.denom)
Now, let's extract the search for a greatest common divisor to a separate method. To do that, select the statements
x = abs(num)
y = abs(denom)
while x:
x, y = y % x, x
factor = y
and press CtrlAlt0M. In the dialog that opens type the method name gcd
and then click OK:
@staticmethod
def gcd(denom, num):
x = abs(num)
y = abs(denom)
while x:
x, y = y % x, x
factor = y
return factor
Let's get rid of the variable factor
, by using Inline variable refactoring. To do that, place the caret at the variable and press CtrlAlt0N. All the detected factor
variables are inlined.
Next, change the parameter names using Change signature. To do that, place the caret at the method declaration line and press CtrlF6. In the dialog that opens, rename the parameters denom
and num
to x
and y
respectively, and click to change the order of parameters.
You end up with the following code:
@staticmethod
def gcd(x, y):
x = abs(x)
y = abs(y)
while x:
x, y = y % x, x
return y
Now, let's convert the existing static method to a function. To do that, press AltEnter, from the suggestion list choose Convert static method to function and press Enter:
from collections import namedtuple
class Rational(namedtuple('Rational', ['num', 'denom'])):
def __new__(cls, num, denom):
if denom == 0:
raise ValueError('Denominator cannot be null')
if denom < 0:
num, denom = -num, -denom
factor = gcd(num, denom)
return super().__new__(cls, num // factor, denom // factor)
def __str__(self):
return '{}/{}'.format(self.num, self.denom)
def gcd(x, y):
x = abs(x)
y = abs(y)
while x:
x, y = y % x, x
return y
Now, we'll move the function to a separate file and add an import statement. To do that, place the caret at the function gcd
declaration and press F6. In the dialog that opens specify the fully qualified path of the destination file util.py. This file does not exist, but it is created automatically:
def gcd(x, y):
x = abs(x)
y = abs(y)
while x:
x, y = y % x, x
return y
The import statement is also added automatically. Thus the file rational.py looks as follows:
from collections import namedtuple
from util import gcd
class Rational(namedtuple('Rational', ['num', 'denom'])):
def __new__(cls, num, denom):
if denom == 0:
raise ValueError('Denominator cannot be null')
if denom < 0:
num, denom = -num, -denom
factor = gcd(num, denom)
return super().__new__(cls, num // factor, denom // factor)
def __str__(self):
return '{}/{}'.format(self.num, self.denom)
Next, let's add declarations of the magic methods for the addition/subtraction operations on the objects of the class Rational
:
from collections import namedtuple
from util import gcd
class Rational(namedtuple('Rational', ['num', 'denom'])):
def __new__(cls, num, denom):
if denom == 0:
raise ValueError('Denominator cannot be null')
factor = gcd(num, denom)
if denom < 0:
num, denom = -num, -denom
return super().__new__(cls, num // factor, denom // factor)
def __str__(self):
return '{}/{}'.format(self.num, self.denom)
def __add__(self, other):
if isinstance(other, int):
other = Rational(other, 1)
if isinstance(other, Rational):
new_num = self.num * other.denom + other.num * self.denom
new_denom = self.denom * other.denom
return Rational(new_num, new_denom)
return NotImplemented
def __neg__(self):
return Rational(-self.num, self.denom)
def __radd__(self, other):
return self + other
def __sub__(self, other):
return self + (-other)
def __rsub__(self, other):
return -self + other
Next, we'll extract an expression Rational(other, 1)
into a separate method. To do that, place the caret at the aforementioned expression, press CtrlAlt0M and in the dialog that opens, type the new method name from_int
.
Finally, place the caret at the method from_int
declaration, press AltEnter, select Make method static from the suggestion list, and then press Enter:
@staticmethod
def from_int(other):
return Rational(other, 1)
Finally, let's change the name of the parameter other
to number
. To do that, place the caret at the parameter and press ShiftF6.
Next, we'll move the implementations of the methods __radd__
, __sub__
and __rsub__
into a superclass. Also, we'll make the methods __neg__
and __add__
abstract.
This is how it's done... Place the caret at the class Rational
declaration, on the context menu point to Refactor | Extract and choose Superclass.... Next, in the dialog that opens, specify the name of the superclass (here it's AdditiveMixin
) and select the methods to be added to the superclass. For the methods __neg__
and __add__
, select the checkboxes in the column Make abstract. For more information, refer to Extract superclass.
End up with the following code:
from abc import abstractmethod, ABCMeta
from collections import namedtuple
from util import gcd
class AdditiveMixin(metaclass=ABCMeta):
@abstractmethod
def __add__(self, other):
pass
@abstractmethod
def __neg__(self):
pass
def __radd__(self, other):
return self + other
def __sub__(self, other):
return self + (-other)
def __rsub__(self, other):
return -self + other
class Rational(namedtuple('Rational', ['num', 'denom']), AdditiveMixin):
def __new__(cls, num, denom):
if denom == 0:
raise ValueError('Denominator cannot be null')
factor = gcd(num, denom)
if denom < 0:
num, denom = -num, -denom
return super().__new__(cls, num // factor, denom // factor)
def __str__(self):
return '{}/{}'.format(self.num, self.denom)
def __add__(self, other):
if isinstance(other, int):
other = self.from_int(other)
if isinstance(other, Rational):
new_num = self.num * other.denom + other.num * self.denom
new_denom = self.denom * other.denom
return Rational(new_num, new_denom)
return NotImplemented
def from_int(self, number):
return Rational(number, 1)
def __neg__(self):
return Rational(-self.num, self.denom)