Difference between Python classmethod and staticmethod with a better use case.
Personally, in python I believe, classmethod's and staticmethod's are about the use in terms of the case. When it comes to classmethod and instance method, the point is about you are maintaining a state (variables) to be processed.
Couple of days back, I was working on the payment gateway implementation and have come across this best way to understand how to know what is good for the situation.
Let's say we have three class,
Now, the settings are almost similar except the say, URL, as following:
So, when we invoke it as follwing, using an object factory:
The whole idea of having the factory class getting the specific gateways helps us loose couple the scenario and hence we get to expand the scope and would sustain nicely for future expansion.
The usage is as follows:
Just last words on the value of classmethod and instance method, unless we have any variable (changing values across different object creation), there is no need for instance method. In the case of the above, we know the settings won't change among various objects of same payment gateway requests except for the inner details which had to be generated on the fly, like order name, amount and customer phone, email, etc.
Conclusion:
If we had @staticmethod on get_payment_data we would ended up choosing FirstPaymentGateway always even when we know the selected gateway was SecondaryPaymentGateway. And since we use @classmethod, we know that when we choose a pg_name as second and get a class handler of SecondaryPaymentGateway we invoke cls.get_payment_settings() of SecondaryPaymentGateway.
Couple of days back, I was working on the payment gateway implementation and have come across this best way to understand how to know what is good for the situation.
Let's say we have three class,
- BasePaymentGateway
- FirstPaymentGateway
- SecondaryPaymentGateway
Now, the BasePaymentGateway provides all the signature which the other payment gateways can follow. Considering the fact that each payment gateways will have their own settings and hence we need to fetch the settings from their respective item.
# paymentgateway.py import settings import some_util class BasePaymentGateway(object): @staticmethod def get_payment_settings(): """Provides the settings for the given item""" pass @classmethod def get_payment_data(cls, order_item, customer_item): """Provides the data that needs to be sent for the payment to be initiated""" pass @classmethod def verify_payment_data(cls, recevied_data)r pass class FirstPaymentGateway(BasePaymentGateway): @staticmethod def get_payment_settings(): return settings.FIRST_DATA @classmethod def get_payment_data(cls, order_item, customer_item): p_data = dict() # Notice that the 'cls' ensures the correct class settings are obtained. # If we had directly used the static method without cls, as: # FirstPaymentGateway.get_payment_settings() -- This would always provide # settings from FirstPaymentGateway.
_settings = cls.get_payment_settings()
p_data['order'] = order_item.name p_data['amount'] = order_item.amount p_data['customer_phone'] = customer_item.phone p_data['customer_email'] = customer_item.email p_data['url'] = _settings['PAY_URL'] p_data['drop_category'] = _settings['DROP_CATEGORY'] # Just an example of checksum that is needed for payment p_data['checksum'] = some_util.checksum_calculator(p_data) return p_data @classmethod def verify_payment_data(cls, received_data): return some_util.is_received_data_valid(received_data) class SecondaryPaymentGateway(FirstPaymentGateway): @staticmethod def get_payment_settings(): # We wanna ensure cls.get_payment_settings will get derived data return settings.SECOND_DATA
Now, the settings are almost similar except the say, URL, as following:
#settings.py
FIRST_DATA = {
'PAY_URL': 'https://secure.firstpayment.com',
'DROP_CATEGORY': 'AMEX,COD' # The categories we do not want (some settings)
}
import copy
SECOND_DATA = copy.deepcopy(FIRST_DATA)
SECOND_DATA['PAY_URL'] = 'https://secure.secondpayment.com'
So, when we invoke it as follwing, using an object factory:
#object_factory.py
import paymentgateway
class PaymentObjectFactory(object):
payment_gateways = {
'first': paymentgateway.FirstPaymentGateway,
'second': paymentgateway.SecondaryPaymentGateway
}
@staticmethod
def get_payment_handler(pg_name):
"""
Returns the specific payment gateway for the given name of pg.
:param pg_name str: The payment gateway name, example "first"
:return: Object of PaymentGateway if found else None
"""
return PaymentObjectFactory.payment_gateways.get(pg_name.lower())
The whole idea of having the factory class getting the specific gateways helps us loose couple the scenario and hence we get to expand the scope and would sustain nicely for future expansion.
The usage is as follows:
# main.py
import object_factory.py
import requests
order_item = # Some Order details
customer_item = # Some Customer data
pg_handler = object_factory.PaymentObjectFactory.get_payment_handler('first')
_data = pg_handler.get_payment_data(order_item, customer_item)
response = requests.post(_data['url'], data=_data)
print response.status
pg_handler = object_factory.PaymentObjectFactory.get_payment_handler('second')
_data = pg_handler.get_payment_data(order_item, customer_item)
# Now, since we use @classmethod we are guaranteed to get the derived class url
# getting called and hence would surly get SECOND_DATA['PAY_URL'] = 'https://secure.secondpayment.com'
# else on using @staticmethod we would get always get 'PAY_URL': 'https://secure.firstpayment.com'
response = requests.post(_data['url'], data=_data)
print response.status
Just last words on the value of classmethod and instance method, unless we have any variable (changing values across different object creation), there is no need for instance method. In the case of the above, we know the settings won't change among various objects of same payment gateway requests except for the inner details which had to be generated on the fly, like order name, amount and customer phone, email, etc.
Conclusion:
If we had @staticmethod on get_payment_data we would ended up choosing FirstPaymentGateway always even when we know the selected gateway was SecondaryPaymentGateway. And since we use @classmethod, we know that when we choose a pg_name as second and get a class handler of SecondaryPaymentGateway we invoke cls.get_payment_settings() of SecondaryPaymentGateway.
Comments
Post a Comment