Python 中的纯静态 classes - 使用 metaclass、class 装饰器或其他东西?
Purely static classes in Python - Use metaclass, class decorator, or something else?
在我正在开发的程序的一部分中,我想使用作为数据集某些函数的项执行线性回归 X
。所使用的确切模型可由用户配置,特别是要使用的术语(或术语集)。这涉及生成矩阵 X'
,其中 X'
的每一行都是 X
对应行的函数。 X'
的列将作为我的回归的预测变量。
例如,假设我的数据集是二维的(X
有 2
列)。如果我们将 x
和 x'
表示为 X
和 X'
的对应行,则假设 x
是二维的 x'
可能类似于
[ 1, x[0], x[1], x[0] * x[1], sqrt(x[0]), sqrt(x[1]), x[0]**2, x[1]**2 ]
您可以看到这些术语成组出现。首先是 1(常量),然后是未转换的数据(线性),然后是两个数据元素的乘积(如果 x
具有两个以上的维度,则所有成对乘积),然后是平方根和平方个别条款。
我需要在 python 中以某种方式定义所有这些术语集,这样每个术语都有一个用户可读的名称、生成术语的函数、从输入,根据数据的列标签为术语生成标签的函数等。从概念上讲,这些都感觉它们应该是 TermSet
class 或类似的东西的实例,但这并不完全工作,因为他们的方法需要不同。我的第一个想法是使用这样的东西:
termsets = {} # Keep track of sets
class SqrtTerms:
display = 'Square Roots' # user-readable name
@staticmethod
def size(d):
"""Number of terms based on input columns"""
return d
@staticmethod
def make(X):
"""Make the terms from the input data"""
return numpy.sqrt(X)
@staticmethod
def labels(columns):
"""List of term labels based off of data column labels"""
return ['sqrt(%s)' % c for c in columns]
termsets['sqrt'] = SqrtTerms # register class in dict
class PairwiseProductTerms:
display = 'Pairwise Products'
@staticmethod
def size(d):
return (d * (d-1)) / 2
@staticmethod
def make(X):
# Some more complicated code that spans multiple lines
...
@staticmethod
def labels(columns):
# Technically a one-liner but also more complicated
return ['(%s) * (%s)' % (columns[c1], columns[c2])
for c1 in range(len(columns)) for c2 in range(len(columns))
if c2 > c1]
termsets['pairprod'] = PairwiseProductTerms
这行得通:我可以从字典中检索 classes,将我想使用的那些放在列表中,然后对每个调用适当的方法。不过,创建仅包含静态属性和方法的 classes 看起来很丑陋而且不 pythonic。我想出的另一个想法是创建一个 class 装饰器,可以像这样使用:
# Convert bound methods to static ones, assign "display" static
# attribute and add to dict with key "name"
@regression_terms(name='sqrt', display='Square Roots')
class SqrtTerms:
def size(d):
return d
def make(X):
return numpy.sqrt(X)
def labels(columns):
return ['sqrt(%s)' % c for c in columns]
这给出了相同的结果,但更清晰,更易读和写(对我自己而言)(尤其是当我需要大量这些内容时)。然而,事情在引擎盖下的实际工作方式是模糊的,任何阅读本文的人可能很难一开始就弄清楚到底发生了什么。我还想过为这些创建一个 metaclass 但这听起来有点矫枉过正。我应该在这里使用更好的模式吗?
总有人会说这是滥用语言。我说 Python 被设计成可滥用的,并且能够创建不需要解析器但看起来不像 lisp 的 DSL 是它的核心优势之一。
如果你真的有很多这样的东西,那就选择 metaclass。如果这样做,除了拥有术语词典之外,您还可以拥有引用这些术语的属性。这真的很好,因为你可以有这样的代码:
print Terms.termsets
print Terms.sqrt
print Terms.pairprod
print Terms.pairprod.size(5)
return 结果如下:
{'pairprod': <class '__main__.PairwiseProductTerms'>,
'sqrt': <class '__main__.SqrtTerms'>}
<class '__main__.SqrtTerms'>
<class '__main__.PairwiseProductTerms'>
10
完整的代码在这里:
from types import FunctionType
class MetaTerms(type):
"""
This metaclass will let us create a Terms class.
Every subclass of the terms class will have its
methods auto-wrapped as static methods, and
will be added to the terms directory.
"""
def __new__(cls, name, bases, attr):
# Auto-wrap all methods as static methods
for key, value in attr.items():
if isinstance(value, FunctionType):
attr[key] = staticmethod(value)
# call types.__new__ to finish the job
return super(MetaTerms, cls).__new__(cls, name, bases, attr)
def __init__(cls, name, bases, attr):
# At __init__ time, the class has already been
# built, so any changes to the bases or attr
# will not be reflected in the cls.
# Call types.__init__ to finish the job
super(MetaTerms, cls).__init__(name, bases, attr)
# Add the class into the termsets.
if name != 'Terms':
cls.termsets[cls.shortname] = cls
def __getattr__(cls, name):
return cls.termsets[name]
class Terms(object):
__metaclass__ = MetaTerms
termsets = {} # Keep track of sets
class SqrtTerms(Terms):
display = 'Square Roots' # user-readable name
shortname = 'sqrt' # Used to find in Terms.termsets
def size(d):
"""Number of terms based on input columns"""
return d
def make(X):
"""Make the terms from the input data"""
return numpy.sqrt(X)
def labels(columns):
"""List of term labels based off of data column labels"""
return ['sqrt(%s)' % c for c in columns]
class PairwiseProductTerms(Terms):
display = 'Pairwise Products'
shortname = 'pairprod'
def size(d):
return (d * (d-1)) / 2
def make(X):
pass
def labels(columns):
# Technically a one-liner but also more complicated
return ['(%s) * (%s)' % (columns[c1], columns[c2])
for c1 in range(len(columns)) for c2 in range(len(columns))
if c2 > c1]
print Terms.termsets
print Terms.sqrt
print Terms.pairprod
print Terms.pairprod.size(5)
如果您将元class 和基本术语class 隐藏在一个单独的模块中,那么没有人需要查看它——只需from baseterm import Terms
。您还可以做一些很酷的自动发现/自动导入,其中将模块转储到正确的目录中会自动将它们添加到您的 DSL 中。
有了元class,功能集可以很容易地有机地增长,因为你发现了你想让你的迷你语言做的其他事情。
在我正在开发的程序的一部分中,我想使用作为数据集某些函数的项执行线性回归 X
。所使用的确切模型可由用户配置,特别是要使用的术语(或术语集)。这涉及生成矩阵 X'
,其中 X'
的每一行都是 X
对应行的函数。 X'
的列将作为我的回归的预测变量。
例如,假设我的数据集是二维的(X
有 2
列)。如果我们将 x
和 x'
表示为 X
和 X'
的对应行,则假设 x
是二维的 x'
可能类似于
[ 1, x[0], x[1], x[0] * x[1], sqrt(x[0]), sqrt(x[1]), x[0]**2, x[1]**2 ]
您可以看到这些术语成组出现。首先是 1(常量),然后是未转换的数据(线性),然后是两个数据元素的乘积(如果 x
具有两个以上的维度,则所有成对乘积),然后是平方根和平方个别条款。
我需要在 python 中以某种方式定义所有这些术语集,这样每个术语都有一个用户可读的名称、生成术语的函数、从输入,根据数据的列标签为术语生成标签的函数等。从概念上讲,这些都感觉它们应该是 TermSet
class 或类似的东西的实例,但这并不完全工作,因为他们的方法需要不同。我的第一个想法是使用这样的东西:
termsets = {} # Keep track of sets
class SqrtTerms:
display = 'Square Roots' # user-readable name
@staticmethod
def size(d):
"""Number of terms based on input columns"""
return d
@staticmethod
def make(X):
"""Make the terms from the input data"""
return numpy.sqrt(X)
@staticmethod
def labels(columns):
"""List of term labels based off of data column labels"""
return ['sqrt(%s)' % c for c in columns]
termsets['sqrt'] = SqrtTerms # register class in dict
class PairwiseProductTerms:
display = 'Pairwise Products'
@staticmethod
def size(d):
return (d * (d-1)) / 2
@staticmethod
def make(X):
# Some more complicated code that spans multiple lines
...
@staticmethod
def labels(columns):
# Technically a one-liner but also more complicated
return ['(%s) * (%s)' % (columns[c1], columns[c2])
for c1 in range(len(columns)) for c2 in range(len(columns))
if c2 > c1]
termsets['pairprod'] = PairwiseProductTerms
这行得通:我可以从字典中检索 classes,将我想使用的那些放在列表中,然后对每个调用适当的方法。不过,创建仅包含静态属性和方法的 classes 看起来很丑陋而且不 pythonic。我想出的另一个想法是创建一个 class 装饰器,可以像这样使用:
# Convert bound methods to static ones, assign "display" static
# attribute and add to dict with key "name"
@regression_terms(name='sqrt', display='Square Roots')
class SqrtTerms:
def size(d):
return d
def make(X):
return numpy.sqrt(X)
def labels(columns):
return ['sqrt(%s)' % c for c in columns]
这给出了相同的结果,但更清晰,更易读和写(对我自己而言)(尤其是当我需要大量这些内容时)。然而,事情在引擎盖下的实际工作方式是模糊的,任何阅读本文的人可能很难一开始就弄清楚到底发生了什么。我还想过为这些创建一个 metaclass 但这听起来有点矫枉过正。我应该在这里使用更好的模式吗?
总有人会说这是滥用语言。我说 Python 被设计成可滥用的,并且能够创建不需要解析器但看起来不像 lisp 的 DSL 是它的核心优势之一。
如果你真的有很多这样的东西,那就选择 metaclass。如果这样做,除了拥有术语词典之外,您还可以拥有引用这些术语的属性。这真的很好,因为你可以有这样的代码:
print Terms.termsets
print Terms.sqrt
print Terms.pairprod
print Terms.pairprod.size(5)
return 结果如下:
{'pairprod': <class '__main__.PairwiseProductTerms'>,
'sqrt': <class '__main__.SqrtTerms'>}
<class '__main__.SqrtTerms'>
<class '__main__.PairwiseProductTerms'>
10
完整的代码在这里:
from types import FunctionType
class MetaTerms(type):
"""
This metaclass will let us create a Terms class.
Every subclass of the terms class will have its
methods auto-wrapped as static methods, and
will be added to the terms directory.
"""
def __new__(cls, name, bases, attr):
# Auto-wrap all methods as static methods
for key, value in attr.items():
if isinstance(value, FunctionType):
attr[key] = staticmethod(value)
# call types.__new__ to finish the job
return super(MetaTerms, cls).__new__(cls, name, bases, attr)
def __init__(cls, name, bases, attr):
# At __init__ time, the class has already been
# built, so any changes to the bases or attr
# will not be reflected in the cls.
# Call types.__init__ to finish the job
super(MetaTerms, cls).__init__(name, bases, attr)
# Add the class into the termsets.
if name != 'Terms':
cls.termsets[cls.shortname] = cls
def __getattr__(cls, name):
return cls.termsets[name]
class Terms(object):
__metaclass__ = MetaTerms
termsets = {} # Keep track of sets
class SqrtTerms(Terms):
display = 'Square Roots' # user-readable name
shortname = 'sqrt' # Used to find in Terms.termsets
def size(d):
"""Number of terms based on input columns"""
return d
def make(X):
"""Make the terms from the input data"""
return numpy.sqrt(X)
def labels(columns):
"""List of term labels based off of data column labels"""
return ['sqrt(%s)' % c for c in columns]
class PairwiseProductTerms(Terms):
display = 'Pairwise Products'
shortname = 'pairprod'
def size(d):
return (d * (d-1)) / 2
def make(X):
pass
def labels(columns):
# Technically a one-liner but also more complicated
return ['(%s) * (%s)' % (columns[c1], columns[c2])
for c1 in range(len(columns)) for c2 in range(len(columns))
if c2 > c1]
print Terms.termsets
print Terms.sqrt
print Terms.pairprod
print Terms.pairprod.size(5)
如果您将元class 和基本术语class 隐藏在一个单独的模块中,那么没有人需要查看它——只需from baseterm import Terms
。您还可以做一些很酷的自动发现/自动导入,其中将模块转储到正确的目录中会自动将它们添加到您的 DSL 中。
有了元class,功能集可以很容易地有机地增长,因为你发现了你想让你的迷你语言做的其他事情。