这最终比我想象的要难。我几乎没有触及元类和数据类的表面,但这里是我想出的解决方案,包括最后的工作演示。
我不确定我会坚持使用哪种解决方案。
# Required for dataclass
from dataclasses import dataclass
# Required to get coin data used in example.
import cbpro
""" Resources Used:
https://www.geeksforgeeks.org/__new__-in-python/
https://www.pythonpool.com/python__new__/
I dont speak or read japanese but i found the snippets to be helpful.
https://python.ms/new/#_3-super-%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E3%80%82
"""
############################################################################################################
class CoinFunctions(object):
""" A class of functionalities for a coin. All the class varients will inheirt this class. """
def get_base_currency(self):
return self.base_currency
############################################################################################################
def create_coin(data):
""" Dynamically Create a type/class and apply the CoinFunctions's methods. """
coin_class = type("GeneratedCoin", (CoinFunctions,), data) # Build a new class. Still needs to be initialized
c = coin_class()
c.__dict__ = data
return c # Return an object initialized from the generated class.
############################################################################################################
class MetaCoin(CoinFunctions):
""" class that implements the __new__ method. Haven't got into inheriting from metaclasses yet. """
# Constructs the object to be initialized
def __new__(cls, **kwargs):
self = object.__new__(cls)
self.__dict__ = kwargs
return self # Returns the object
# Initializes the object.
def __init__(self, *args, **kwargs):
pass
############################################################################################################
@dataclass()
class Coin(CoinFunctions):
""" A dataclass that is used to build a proper data set of a coin with built-in types. Inherit from CoinFunctions for functionalities"""
id : str # base_currency-quote_currency (LTC-BTC)
base_currency : str # (LTC)
quote_currency : str # (BTC)
base_min_size : str # float
base_max_size : str # int / float
quote_increment : str # double
base_increment : str # double
display_name : str
min_market_funds : str # float
max_market_funds : str # int / float
margin_enabled : bool
post_only : bool
limit_only : bool
cancel_only : bool
trading_disabled : bool
status : str
status_message : str
############################################################################################################
"""
This is my original solution.
The __attrs__ field accepts a list of names that will be variables of the class.
The list of names that is __attrs__ are not only made into variables but are the only allowed variables.
If you want a variable in your class you must include its name inside of __attrs__.
When inheriting from EasyModel the only required ovveride is the __attrs__ field.
"""
class EasyModel(object):
""" Abstract Model. Use to create models that might have extensive variable members, or in cases where applicable. """
__attrs__ = []
def __init__(self, *args, **kwargs):
self.set_using_args(args) # set variables given as a *args
self.set_using_kwargs(kwargs) # set variables given as **kwargs
self.fill_empty() # Set variables that were expected to None
def set_using_args(self, args):
""" Given a list of variables (assuming in the correct order), add the value to the proper variable name. """
#print (self.__class__.__attrs__)
for i, arg_value in enumerate(args):
#print ("\n\n********* WORKING IN ARGS *********\n\n")
arg_name = self.__class__.__attrs__[i]
setattr(self, arg_name, arg_value)
def set_using_kwargs(self, kwargs):
""" Given a dictionary, add the parameters as variables to the instance if the parameter is valid. """
for arg_name, arg_value in kwargs.items():
#print (arg_name in self.__class__.__attrs__)
if (arg_name in self.__class__.__attrs__):
setattr(self, arg_name, arg_value)
def fill_empty(self):
""" Create any undefined variables and set as None """
for arg_name in self.__class__.__attrs__:
if (not hasattr(self, arg_name)):
setattr(self, arg_name, None)
def __setattr__ (self, name, value):
""" Make sure that the attributes that will be added are expected. """
if (name in self.__class__.__attrs__):
self.__dict__[name] = value
class EasyCoin(EasyModel):
""" Using the EasyModel to quickly build a model that will be created from an API request that gives all the data below in the form of a dictionary. """
__attrs__ = ['id', 'base_currency', 'quote_currency', 'base_min_size', 'base_max_size',
'quote_increment', 'base_increment', 'display_name', 'min_market_funds', 'max_market_funds',
'margin_enabled', 'post_only', 'limit_only', 'cancel_only', 'trading_disabled', 'status', 'status_message']
def __init__(self, *args, **kwargs):
super(EasyCoin, self).__init__(*args, **kwargs)
#########################################################################################################################################
#########################################################################################################################################
# Executing The Code:
if __name__ == '__main__':
# Make a connection to coinbase pro and request a list of their crypto trade markets.
client = cbpro.PublicClient()
#list_of_coins = client.get_currencies() # an actual list of coins
list_of_coins = client.get_products() # a list of supported crypto trades.
# Each item in the list_of_coins is a dictionary.
for coin_data in list_of_coins:
c = create_coin(coin_data)
print ("")
print ("Type:", type(c))
print ("Class Bases:", c.__class__.__bases__)
print ("Object:",c)
print ("Object.__dict__", c.__dict__)
c = MetaCoin(**coin_data)
print ("")
print ("Type:", type(c))
print ("Class Bases:", c.__class__.__bases__)
print ("Object:",c)
print ("Object.__dict__", c.__dict__)
c = Coin(**coin_data)
print ("")
print ("Type:", type(c))
print ("Class Bases:", c.__class__.__bases__)
print ("Object:",c)
print ("Object.__dict__", c.__dict__)
c = EasyCoin(**coin_data)
print ("")
print ("Type:", type(c))
print ("Class Bases:", c.__class__.__bases__)
print ("Object:",c)
print ("Object.__dict__", c.__dict__)
break
输出
Type: <class '__main__.GeneratedCoin'>
Class Bases: (<class '__main__.CoinFunctions'>,)
Object: <__main__.GeneratedCoin object at 0x043867D0>
Object.__dict__ {'id': 'LINK-USD', 'base_currency': 'LINK', 'quote_currency': 'USD', 'base_min_size': '0.1', 'base_max_size': '90000', 'quote_increment': '0.00001', 'base_increment': '0.01', 'display_name': 'LINK/USD', 'min_market_funds': '10', 'max_market_funds': '100000', 'margin_enabled': False, 'post_only': False, 'limit_only': False, 'cancel_only': False, 'trading_disabled': False, 'status': 'online', 'status_message': ''}
Type: <class '__main__.MetaCoin'>
Class Bases: (<class '__main__.CoinFunctions'>,)
Object: <__main__.MetaCoin object at 0x04386730>
Object.__dict__ {'id': 'LINK-USD', 'base_currency': 'LINK', 'quote_currency': 'USD', 'base_min_size': '0.1', 'base_max_size': '90000', 'quote_increment': '0.00001', 'base_increment': '0.01', 'display_name': 'LINK/USD', 'min_market_funds': '10', 'max_market_funds': '100000', 'margin_enabled': False, 'post_only': False, 'limit_only': False, 'cancel_only': False, 'trading_disabled': False, 'status': 'online', 'status_message': ''}
Type: <class '__main__.Coin'>
Class Bases: (<class '__main__.CoinFunctions'>,)
Object: Coin(id='LINK-USD', base_currency='LINK', quote_currency='USD', base_min_size='0.1', base_max_size='90000', quote_increment='0.00001', base_increment='0.01', display_name='LINK/USD', min_market_funds='10', max_market_funds='100000', margin_enabled=False, post_only=False, limit_only=False, cancel_only=False, trading_disabled=False, status='online', status_message='')
Object.__dict__ {'id': 'LINK-USD', 'base_currency': 'LINK', 'quote_currency': 'USD', 'base_min_size': '0.1', 'base_max_size': '90000', 'quote_increment': '0.00001', 'base_increment': '0.01', 'display_name': 'LINK/USD', 'min_market_funds': '10', 'max_market_funds': '100000', 'margin_enabled': False, 'post_only': False, 'limit_only': False, 'cancel_only': False, 'trading_disabled': False, 'status': 'online', 'status_message': ''}
Type: <class '__main__.EasyCoin'>
Class Bases: (<class '__main__.EasyModel'>,)
Object: <__main__.EasyCoin object at 0x043867D0>
Object.__dict__ {'id': 'LINK-USD', 'base_currency': 'LINK', 'quote_currency': 'USD', 'base_min_size': '0.1', 'base_max_size': '90000', 'quote_increment': '0.00001', 'base_increment': '0.01', 'display_name': 'LINK/USD', 'min_market_funds': '10', 'max_market_funds': '100000', 'margin_enabled': False, 'post_only': False, 'limit_only': False, 'cancel_only': False, 'trading_disabled': False, 'status': 'online', 'status_message': ''}