Python question about overridden/redifined imported function as Class method

Question:

In https://github.com/biopython/biopython/blob/518c4be6ae16f1e00bfd55781171da91282b340a/Bio/SeqUtils/ProtParam.py I have this importing statement:

from Bio.SeqUtils import molecular_weight

and then in a Class:

class ProteinAnalysis:

.....
.....
    def molecular_weight(self):
        """Calculate MW from Protein sequence."""
        return molecular_weight(
            self.sequence, seq_type="protein", monoisotopic=self.monoisotopic
        )

......
......

What is this type of coding called? Is it normal to call imported function and class method with same name?

To my knowledge, self.molecular_weights is not equal to molecular_weights, but why call them the same? Is that PEP 8 compliant?

Would

a = ProteinAnalysis()

print(a.molecular_weights == molecular_weights)

give True or False?

What about:

print(a.molecular_weights(*args,**kwargs) == molecular_weights(*args, **kwargs))

Asked By: pippo1980

||

Answers:

"What is this type of coding called"

This appears to simply be an API decision. I don’t know if there is a special name for it or not.

"Is it normal to call imported function and class method with same name?"

"normal" isn’t exactly a well defined, so instead I will suggest there is nothing unusual about it.

"To my knowledge, self.molecular_weights is not equal to molecular_weights, but why call them the same?"

When supplying the same parameters to the molecular_weights function, the result will be the same value. And as mentioned earlier this is what was decided when building the API.

"Is that PEP 8 compliant?"

Probably.

"would molecular_weights == a.molecular_weights be True"

No

"would molecular_weights() == a.molecular_weights() be True"

No, molecular_weights has required parameters so this would raise and exception.

But this would return true:

molecular_weights(a.sequence, seq_type="protein", monoisotopic=a.monoisotopic)  == a.molecular_weights()

Whoever wrote the code wanted the ProteinAnalaysis instances to have the molecular_weight function as part of its method API.

Answered By: Alexander

Yes. This is normal as there is no contradiction. You cannot freely get access to any class attributes even in methods inside a class. For example, let’s look at this:

var = 5

class SomeClass:
    var = 6

    def show(self):
        print(var)

SomeClass().show()

The result is 5. As there is only one "variable" var in global scope.

If we unlink it via del var, we will get NameError:

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [38], in <cell line: 1>()
----> 1 SomeClass.show()

Input In [37], in SomeClass.show()
      5 def show():
----> 6     print(var)

NameError: name 'var' is not defined

But you can get free access during the class initialisation:

class SomeClass:
    var = 6
    print(var)

    def show():
        print(var)

Hereinabove code prints 6.

In Biopython code only the imported function is used because the calling is inside a method where there is no direct access to the class attributes. Moreover, the method even is not initialised at the moment.

a.molecular_weights == molecular_weights gives False because even if they do the same they are different objects: a method & a function.

molecular_weights() == a.molecular_weights() would give True, if the same arguments are sent.

There is no contradiction with PEP8 naming rules as both do the same but one is a function, other is a method of appropriate class and objects.

Answered By: Vovin

this is an example I made to answer my question above:

in a folder named modul I have __init__.py :

#!/usr/bin/env python3

def pippo():
    
    print('pippo')
    
    return 5

in the same dir where modul folder is , I have run.py

from modul import pippo

print(pippo, type(pippo))

class pluto():
    
    print(pippo, type(pippo))
    
    def pippo(self):
        
        print('redifined')
        
        print(pippo, type(pippo))
        return pippo()
    
    
a = pluto()

a.pippo()

print('a.pippo() --->   ', a.pippo(), type(a.pippo))

print('fun == : ',a.pippo  ==  pippo)

print('fun() == : ', a.pippo()  ==  pippo())

print('type(fun) == : ', type(a.pippo)  ==  type(pippo))

print('type == : ' , type(a.pippo) ,  type(pippo),' == : ',  type(a.pippo) ==  type(pippo))

print('id : ', id(a.pippo)  ,  id(pippo), ' id : ', id(a.pippo)  ==  id(pippo))

result:

<function pippo at 0x7fcaa27a30d0> <class 'function'>
<function pippo at 0x7fcaa27a30d0> <class 'function'>
redifined
<function pippo at 0x7fcaa27a30d0> <class 'function'>
pippo
redifined
<function pippo at 0x7fcaa27a30d0> <class 'function'>
pippo
a.pippo() --->    5 <class 'method'>
fun == :  False
redifined
<function pippo at 0x7fcaa27a30d0> <class 'function'>
pippo
pippo
fun() == :  True
type(fun) == :  False
type == :  <class 'method'> <class 'function'>  == :  False
id :  140508326858176 140508286038224  id :  False

of course in my question molecular_weights needed *args, **kwargs while my example show a function that just return an int

So again to me its like the imported function is returned loaded with class attributes, and I get that point, but why calling the method that returned that with same name ?

Answered By: pippo1980