Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can PyCharm help me to extract a free function from a method?

I'd like to get from

class Sum:
    def __init__(self, a, b):
        self._a = a
        self._b = b

    def evaluate(self):
        return self._a + self._b

to

def add(a, b):
    return a + b


class Sum:
    def __init__(self, a, b):
        self._a = a
        self._b = b

    def evaluate(self):
        return add(self._a, self._b)

i.e., extract from method evaluate the expression self._a + self._b as a "free function" (a function that isn't a method). Is this possible with PyCharm's automatic refactorings?

like image 592
das-g Avatar asked Oct 16 '25 11:10

das-g


2 Answers

I don't know whether this can be done with a single refactoring, but it can be done almost fully automated in 3-4 steps:

  1. extract a method with Ctrl+Alt+M
  2. turn it into a free function ("move method to top-level" in PyCharm lingo) with F6
  3. rename arguments with Shift+F6
  4. (Optional:) move the function to the top of the source file (manually)

The steps in detail:

First step: extract a method

  1. Select the expression or statement(s) to be extracted (here self._a + self._b)
  2. Press Ctrl+Alt+M

    or

    from the context menu of the selection, choose Refactor > Extract > Method...

  3. In the dialog window that appears, enter a name for your function (in my example add) and click OK.

The result will look like this:

class Sum:
    def __init__(self, a, b):
        self._a = a
        self._b = b

    def evaluate(self):
        return self.add()

    def add(self):
        return self._a + self._b

Second step: turn method into a free function

Now, let's pull the new method out of the class and turn it into a module-level function:

  1. Select the newly extracted method or place the cursor anywhere in it (signature or body)
  2. Press F6

    or

    from the context menu, choose Refactor > Move...

  3. In "Make Method Top-Level" dialog window that appears, leave the "To" input field as-is (i.e. set to the current module's source file location) and click Refactor

The result will look like this:

class Sum:
    def __init__(self, a, b):
        self._a = a
        self._b = b

    def evaluate(self):
        return add(self._a, self._b)


def add(_a, _b):
    return _a + _b

Third step: rename the arguments

As you can see, the refactoring has correctly replaced the self argument with two arguments for the operands. Unfortunately, it has used the _... names of the corresponding "hidden" instance members.

  1. Select one underscore (_) in the function's signature or where the function uses the arguments
  2. Press Shift+F6

    or

    from the context menu of the selection, choose Refactor > Rename...

  3. In the "Rename" dialog window that appears, the underscore will already be selected, so just press or Del and then hit Enter (or click the Refactor button)

Repeat for the other function argument(s).

(In scenarios where you want to choose completely different names, simply place the cursor into the argument name to change without selecting a portion of it. Then the whole name will start out selected in the dialog input field and can be overwritten by typing something new.)

The result will look like this:

class Sum:
    def __init__(self, a, b):
        self._a = a
        self._b = b

    def evaluate(self):
        return add(self._a, self._b)


def add(a, b):
    return a + b

Fourth step (optional): Move the function to the top of the source file

(or where-ever you want to place it)

As you can see, PyCharm placed the new top-level function after the class. I wanted to have it before it. Moving it with Shift+Alt+ as recommended in John's answer didn't work for me in PyCharm 2016.3.2 on GNOME, so I just selected the function, and cut it with Crtl+x and pasted it where I want it with Crtl+v.

like image 139
das-g Avatar answered Oct 18 '25 01:10

das-g


There is not a direct way of doing what you want, but you can work around it:

  1. Select the self._a + self._b statement and Right Click > Refactor > Extract > Method, or Ctrl + Alt + M
  2. In the popup window, choose a name for your function (ex. add) > OK. This will generate a class function named as given. The process will also refactor the usage to match your case.
  3. Select the newly generated function and it contents and by holding Shift + Alt hit the Up or Down buttons to move it where you want it to be in your code. Also as you have it selected and placed in it's final place, hit Shift + Tab to fix you identation.
  4. Change the generated function arguments to match your case:
    Erase self, add a, b, etc.
    Also erase self. everywhere inside the function. You can do this by find and replace:
    Ctrl + R > type self. in the first search bar > leave the second search bar empty and starting by the beginning of your file, click on the Replace button, until everything seems correct.
  5. In your evaluate function erase the self. that will be generated before the name of your generated function. Also pass the correct arguments to the function (ex. return add(self.a, self.b))

And you are done, it is a bit of work and it is not that much automated, but it will save you some typing time!

This is tested in PyCharm 2017.1 pro edition

like image 22
John Moutafis Avatar answered Oct 18 '25 01:10

John Moutafis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!