# PyCharm Refactoring Tutorial

## What this tutorial is about

This tutorial shows some refactorings available in PyCharm, using the example of a simple class that makes use of the rational numbers.

## Prerequisites

Make sure that the following prerequisites are met:

• You are working with PyCharm version 2016.2 or later.

• A project is already created.

## Preparing an example

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)

### Simplifying rational number

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)

## Extracting a method

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 Ctrl+Alt+M. In the dialog box 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

## Inlining a local variable and changing method signature

Let's get rid of the variable `factor`, by using Inline variable refactoring. To do that, place the caret at the variable and press Ctrl+Alt+N. 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 Ctrl+F6. 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

## Using quick fix

Now, let's convert the existing static method to a function. To do that, press Alt+Enter, 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

## Moving the function to another file

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)

## Further changes of the class Rational

Next, let us 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

### Extracting method and using a quick fix

Next, we'll extract an expression `Rational(other, 1)` into a separate method. To do that, place the caret at the aforementioned expression, press Ctrl+Alt+M and in the dialog box that opens, type the new method name `from_int`.

Finally, place the caret at the method `from_int` declaration, press Alt+Enter, 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 on the parameter and press Shift+F6.

## Extracting a superclass

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 box 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.

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)