I have a class called Portfolio & right now I have 2 constructors (although as I just discovered u cant overload functions in python) so I have to merge these 2 constructors.
I am an experienced python programmer but this merging 2 constructors that are pretty contradictory is really doing my head in! :P
The first constructor: + Purpose is to create a .db file & initialise class variables - Initialises class member variables - Checks whether a file with the path&name db_path exists: if it does we exit coz we dont want to overwrite an existing database so we return None - We create the database (sql database) & set up the necessary tables - import portfolio data (date created, etc)
The 2nd constructor: + Purpose is to import all stock data from an existing sql database file & initialise variables - Initialises class member variables - Checks whether a file with the path&name db_path exists: if it does NOT we exit coz we dont want to overwrite an existing database so we return None - We call the function to import all stocks from the database - import portfolio data (date created, etc)
How can I merge these two constructors, ugh, why doesn't python allow overloading
class Portfolio:
""" Portfolio class implementation """
# Class Variables:
# self.db_path
# self.app_parent
# self.tracking_stocks
# self.registered_stocks
# self.total_purchase_exp
# self.ytd_roi
# self.cash_reserve
# Class Functions:
def __init__( self, _app_parent, _db_path, stock_list ):
""" Constructor: """
self.db_path = _db_path
self.app_parent = _app_parent
self.tracking_stocks = []
self.registered_stocks = []
self.total_purchase_exp = 0
self.ytd_roi = 0
self.cash_reserve = 500
if database_exists( self.db_path ):
return None
self.create_database()
self.import_portfolio_data()
for stock_data in stock_list:
self.add_stock( stock_data )
def __init__( self, _app_parent, _db_path ):
""" Constructor: """
self.db_pat开发者_如何学Ch = _db_path
self.app_parent = _app_parent
self.tracking_stocks = []
self.registered_stocks = []
self.total_purchase_exp = 0
self.ytd_roi = 0
self.cash_reserve = 500
if not self.database_exists( self.db_path ):
return None
self.import_portfolio_data()
self.import_stocks()
Why does this all have to happen in constructors?
You could construct your object, then call separate methods on it for for importing the stock lists, or creating the database.
Wrapping that up in the Builder pattern may suit your existing design.
def __init__(self, _app_parent, _db_path, stock_list=None):
self.db_path = _db_path
self.app_parent = _app_parent
self.tracking_stocks = []
self.registered_stocks = []
self.total_purchase_exp = 0
self.ytd_roi = 0
self.cash_reserve = 500
if stock_list:
if database_exists( self.db_path ):
return None
self.create_database()
self.import_portfolio_data()
for stock_data in stock_list:
self.add_stock( stock_data )
else:
if not self.database_exists( self.db_path ):
return None
self.import_portfolio_data()
self.import_stocks()
Most of this is copy-pasted, so I can't attest to the accuracy of the indentation, etc., but this is the general pattern for creating versatile __init__
methods in Python (note that __init__
by itself is not a constructor, it's an initializer). Since Python supports keyword args and default values for keyword args, there's really no reason to ever use overloading (in fact, we save a lot of repetitious code by not having overloading). Instead, we just use a keyword arg stock_list with a default value of None.
This isn't perfectly fixed up, either; some of the problems persist (since it's copy-pasted). I don't know the details of what the constructor does, but I think with a little more refactoring you could get things set up where the __init__
is much shorter and much more elegant (consider separating out pieces of the initialization into methods, like _init_db()
and call those from __init__
).
I would use class methods to handle the different cases. This is really a variant of the C++ Named Constructor idiom adapted to Python. Instead of calling the constructor directory, you call a class method to create the instance based on your needs. Here's how I would code this one up:
class Portfolio:
def __init__(self, app_parent, db_path):
"""Create a fully initialized and empty instance."""
self.db_path = db_path
self.app_parent = app_parent
self.tracking_stocks = []
self.registered_stocks = []
self.total_purchase_exp = 0
self.ytd_roi = 0
self.cash_reserve = 500
@classmethod
def new_database(cls, app_parent, db_path, stock_list):
"""Create a Portfolio instance wrapped around a new database and
initialize it with ``stock_list``. If a database already exists at
``db_path``, then an exception is thrown."""
instance = cls(app_parent, db_path)
if database_exists(instance.db_path):
raise Exception('Database already exists at '+instance.db_path)
instance.create_database()
instance.import_portfolio_data()
for stock_data in stock_list:
instance.add_stock(stock_data)
return instance
@classmethod
def from_database(cls, app_parent, db_path):
"""Create a Portfolio instance from the database located at
``db_path``. If the database does not exist, then an exception
is thrown."""
instance = cls(app_parent, db_path)
if not database_exists(instance.db_path):
raise Exception('Database does not exist at '+instance.db_path)
instance.import_portfolio_data()
instance.import_stocks()
return instance
The usage is easy:
new_portfolio = Portfolio.new_database('/data/port.db')
existing_portfolio = Portfolio.from_database('/data/port.db')
There are a few things that are pretty important from a usability and maintainability standpoint though. First, use the class instance argument (cls
) to create the new instance in the named constructor instead of the class. Instead of instance = cls(...)
, I could have used instance = Portfolio(...)
. This breaks if you add a subclass to Portfolio
and call one of the class methods.
The second (and probably more important) point is that you should be using exceptions instead of returning None
in the case of errors. It is more pythonic and just safer in general.
精彩评论