【问题标题】:Proper way to create a class with a large amount of variables创建具有大量变量的类的正确方法
【发布时间】:2021-03-20 23:54:33
【问题描述】:

在多年没有这样做之后,我最近开始编写代码,并且我正在构建一个小项目以重新开始。这是一个使用各种加密货币交易市场专用 API 的小型应用程序。

我的问题不是解决问题,而是解决问题的正确方法。因为我不喜欢有很多变量的编码模型,所以我决定创建一个抽象模型来为我完成定义变量的劳动密集型工作。

问题来了。在创建需要很多变量的类时,我应该:

  1. __init__() 方法中定义每个变量,并在__init__() 标头中包含每个变量

  2. 我可以使用只需要定义所需变量列表的抽象类吗?我可以为我未来的项目这样做吗?

我的抽象类使用变量名称的“静态”列表(python,我知道),并实现 __init__(self, *args, **kwargs) 构建动态/抽象对象的方式。

我不知道是否应该为项目实现显式笨重方法或隐式抽象方法。人们/团队在现实世界中使用的标准方法是什么?

示例

显式大容量方法

class Coin(object):
    def __init__(self, id=None, base_currency=None, quote_currency=None, base_min_size=None, base_max_size=None,
             quote_increment=None, base_increment=None, display_name=None, min_market_funds=None, max_market_funds=None,
             margin_enabled=None, post_only=None, limit_only=None, cancel_only=None, trading_disabled=None,
             status=None, status_message=None):
    self.id = id
    self.base_currency = base_currency
    self.quote_currency = quote_currency
    self.base_min_size = base_min_size
    self.base_max_size = base_max_size
    self.quote_increment = quote_increment
    self.base_increment = base_increment
    self.display_name = display_name
    self.min_market_funds = min_market_funds
    self.max_market_funds = max_market_funds
    self.margin_enabled = margin_enabled
    self.post_only = post_only
    self.limit_only = limit_only
    self.cancel_only = cancel_only
    self.trading_disabled = trading_disabled
    self.status = status
    self.status_message = status_message

隐式抽象方法

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. """
        for i, arg_value in enumerate(args):
            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():
            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 Coin(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(Coin, self).__init__(*args, **kwargs)

# Both would result in the same usage.
coin_data = {...} # Json / dictionary version of object

# Create object by passin json / dictionary as **kwargs
c = Coin(**coin_data)

# Both methods would result in this usage.
print (c.display_name)

【问题讨论】:

  • 我建议使用元类,并使其完成对类属性的劳动密集型定义。
  • 我忘记了元类及其工作原理,但感谢您为我指明了正确的方向。现在是元类常用还是人们只是对变量进行硬编码?
  • 或查看数据类
  • 我会看看两者。我对此一无所知,现在应该是研究它们的好时机
  • 是的,我认为元类相当普遍,尽管@Anthony 建议的dataclasses 在许多情况下可以取代它们。 Here's Raymond Hettinger 对他们的精彩演讲。

标签: python validation model abstract-class


【解决方案1】:

这最终比我想象的要难。我几乎没有触及元类和数据类的表面,但这里是我想出的解决方案,包括最后的工作演示。

我不确定我会坚持使用哪种解决方案。

# 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': ''}

【讨论】:

    猜你喜欢
    • 2020-09-08
    • 2023-04-02
    • 1970-01-01
    • 2015-01-18
    • 2019-01-09
    • 1970-01-01
    • 1970-01-01
    • 2014-09-10
    相关资源
    最近更新 更多