I've seen and tried the answer given at How would one decorate an inherited method in the child class? to no avail.
Sample data:
import pandas as pd
df = pd.DataFrame([('Tom', 'M'), ('Sarah', 'X')], columns=['PersonName', 'PersonSex'])
I am using the pandera
library for DataFrame data quality and validation. I have a class BaseClass
which defines some functions I'll be using in many of my schemas, e.g.:
class BaseClass:
def check_valid_sex(cls, col):
return col.isin(['M', 'F'])
I'm inheriting this class in my schema classes where I will be applying the checks. Here, first, is an example of not using this class and writing the checks explicitly in the SchemaModel
.
import pandera as pa
from pandera.typing import Series
class PersonClass(pa.SchemaModel):
PersonName: Series[str]
PersonSex: Series[str]
@pa.check('PersonSex')
def check_valid_sex(cls, col):
return col.isin(['M', 'F'])
PersonClass.validate(df)
What I would like to do is re-use the check_valid_sex
function in other SchemaModel
classes where there is a PersonSex
column, something like this:
import pandera as pa
from pandera.typing import Series
class PersonClass(pa.SchemaModel, BaseClass):
PersonName: Series[str]
PersonSex: Series[str]
# apply the pa.check function to the BaseClass function to inherit the logic
validate_sex = classmethod(pa.check(BaseClass.check_valid_sex, 'PersonSex'))
PersonClass.validate(df)
This should return a valid validation and is not working as I don't believe the function is being registered correctly. How can I decorate the parent class without overwriting it? Simply decorate with the decorator args.
CodePudding user response:
The decorator here is actually a decorator constructor that then decorates what it's called on. So you need to follow the same pattern, construct the check
with the field name, then use it to manually wrap the base class function:
validate_sex = pa.check('PersonSex')(BaseClass.check_valid_sex)
# ^^^^^^^^^^^^^^^^^^^^^ constructs decorator
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ calls on function
It's a little unclear why the base class (which is not a classmethod
) accepts cls
, while the child class attempted to wrap in classmethod
. If only the child is supposed to be a classmethod
, you could just do:
validate_sex = classmethod(pa.check('PersonSex')(BaseClass.check_valid_sex))
to wrap it, but if the base class method is actually a classmethod
it gets uglier; you'll have to unwrap it, e.g. you'd replace BaseClass.check_valid_sex
with BaseClass.check_valid_sex.__func__
(binding the function to the class, then extracting just the unbound function), or in 3.10 , you could avoid the creation of the bound class method by doing BaseClass.__dict__['check_valid_sex'].__wrapped__
(which looks up the raw classmethod
, bypassing the descriptor protocol, and extracts the function it wraps directly). You need to do this so you get the clean original function, not one indelibly bound to the parent class (where even when called on the child class, cls
would still be BaseClass
).