紧凑语法定义简单的“struct-like”(或“record-like”,“bean-like”)类。
项目描述
概述
提供紧凑语法以定义简单的“struct-like”(或“record-like”,“bean-like”)类。生成的类与namedtuple非常相似,但可变,语法更优雅,更灵活,并且具有更多功能。
以下是功能摘要
可以为字段定义默认值。
有用的__repr__和__str__实现。
结构相等,即有用的__eq__和__ne__实现。
copy方法(复制构造函数)。
从/到字典和元组的转换。
使用 __slots__ 声明来提高性能并防止对未知字段进行赋值。
可以定义自定义方法。
支持继承。
兼容性
目前仅支持 Python 2.7。
安装
通常
pip install rbco.caseclasses
或者
easy_install rbco.caseclasses
使用
基础知识
让我们从一个简单的案例类开始
>>> from rbco.caseclasses import case
>>>
>>> @case
... class Person(object):
... """Represent a person."""
... def __init__(self, name, age=None, gender=None): pass
声明的 __init__ 仅仅是一个占位符。参数定义了类将拥有的字段及其默认值。将用一个新的 __init__ 方法替换它,该方法负责分配字段的值。
构造函数按预期工作,根据提供的 __init__ 占位符
>>> Person('John')
Person(name='John', age=None, gender=None)
>>> Person('John', 30, 'm')
Person(name='John', age=30, gender='m')
>>> Person(name='John', age=30, gender='m')
Person(name='John', age=30, gender='m')
>>> Person('John', gender='m')
Person(name='John', age=None, gender='m')
注意,在字符串表示中,字段与构造函数中定义的顺序相同。
类的文档字符串被保留
>>> Person.__doc__
'Represent a person.'
构造函数的签名没有被保留。生成的 __init__ 方法签名是通用的,只接受 *args 和 **kwargs
>>> from inspect import getargspec
>>> getargspec(Person.__init__)
ArgSpec(args=['self'], varargs='args', keywords='kwargs', defaults=None)
然而,文档字符串包含了原始签名
>>> Person.__init__.__doc__
'Original signature: (self, name, age=None, gender=None)'
无法创建没有构造函数的案例类
>>> from rbco.caseclasses import case
>>>
>>> @case
... class Foo(object): pass
Traceback (most recent call last):
...
RuntimeError: Case class must define a constructor.
可变性和__slots__
实例是可变的
>>> p = Person('John')
>>> p
Person(name='John', age=None, gender=None)
>>> p.name = 'Bob'
>>> p.age = 35
>>> p
Person(name='Bob', age=35, gender=None)
然而,无法分配给未知属性
>>> p.department = 'sales'
Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'department'
这是因为 __slots__ 声明
>>> p.__slots__
['name', 'age', 'gender']
结构相等
支持结构相等性
>>> p1 = Person('John', 30)
>>> p2 = Person('Bob', 25)
>>> p1 == p2
False
>>> p1 != p2
True
>>> p2.name = 'John'
>>> p2.age = 30
>>> p1 == p2
True
>>> p1 != p2
False
>>> p2.gender = 'm'
>>> p1 == p2
False
复制构造函数
提供了复制构造函数
>>> p1 = Person('John', 30)
>>> copy_of_p1 = p1.copy()
>>> p1
Person(name='John', age=30, gender=None)
>>> copy_of_p1
Person(name='John', age=30, gender=None)
>>> p1 is copy_of_p1
False
>>> p2 = p1.copy(name='Bob', gender='m')
>>> p2
Person(name='Bob', age=30, gender='m')
从/到字典和元组的转换
从/到字典的转换很简单。 as_dict 方法返回一个 OrderedDict
>>> p1 = Person('Mary', 33)
>>> p1
Person(name='Mary', age=33, gender=None)
>>> p1.as_dict()
OrderedDict([('name', 'Mary'), ('age', 33), ('gender', None)])
>>> Person(**p1.as_dict())
Person(name='Mary', age=33, gender=None)
从/到元组的转换也是可能的
>>> p1 = Person('John', 30)
>>> p1
Person(name='John', age=30, gender=None)
>>> p1.as_tuple()
('John', 30, None)
>>> Person(*p1.as_tuple())
Person(name='John', age=30, gender=None)
自定义成员
案例类非常类似于常规类。可以定义任何类型的自定义成员。
最常见的案例是添加自定义实例方法
>>> import math
>>> @case
... class Point(object):
... def __init__(self, x, y): pass
...
... def distance(self, other):
... return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)
>>> p1 = Point(0, 0)
>>> p2 = Point(10, 0)
>>> p1.distance(p2)
10.0
也支持其他类型的类成员
>>> @case
... class Example(object):
... class_attribute = 'some value'
...
... def __init__(self, field1): pass
...
... @staticmethod
... def static_method():
... print 'This is an static method.'
...
... @classmethod
... def class_method(cls):
... print 'This is a class method of the class {}.'.format(cls.__name__)
...
>>> e = Example('example')
>>> Example.class_attribute
'some value'
>>> e.class_attribute
'some value'
>>> Example.static_method()
This is an static method.
>>> Example.class_method()
This is a class method of the class Example.
继承
让我们创建一个基案例类和一个派生案例类
>>> @case
... class Person(object):
... def __init__(self, name, age=None, gender=None): pass
...
... def present(self):
... print "I'm {}, {} years old and my gender is '{}'.".format(
... self.name,
... self.age,
... self.gender
... )
...
>>> @case
... class Employee(Person):
... def __init__(self, name, age=None, gender=None, department=None): pass
必须重复基类的字段,但如果你手动实现案例类,你也必须这样做。
基类的方法被继承
>>> p = Person('John', 30, 'm')
>>> p.present()
I'm John, 30 years old and my gender is 'm'.
>>> e = Employee('Mary', 33, 'f', 'sales')
>>> e.present()
I'm Mary, 33 years old and my gender is 'f'.
Person 和 Employee 的实例始终被认为是不同的,因为雇员有一个额外的字段
>>> p = Person('John')
>>> e = Employee('John')
>>> p == e
False
覆盖基类方法按预期工作
>>> @case
... class ImprovedEmployee(Employee):
... def present(self):
... super(ImprovedEmployee, self).present()
... print 'I work at the {} department.'.format(self.department)
...
>>> ie = ImprovedEmployee(name='Mary', department='marketing', age=33, gender='f')
>>> ie.present()
I'm Mary, 33 years old and my gender is 'f'.
I work at the marketing department.
覆盖case class行为
可以覆盖标准的案例类方法(如 __repr__、__eq__ 等)。例如
>>> @case
... class Foo(object):
... def __init__(self, bar): pass
...
... def __eq__(self, other):
... return True # All `Foo`s are equal.
...
>>> Foo('bar') == Foo('baz')
True
甚至可以在子类方法中调用原始版本
>>> @case
... class Foo(object):
... def __init__(self, bar):
... pass
...
... def __repr__(self):
... return 'This is my string representation: ' + super(Foo, self).__repr__()
...
>>> Foo('bar')
This is my string representation: Foo(bar='bar')
无法覆盖 __init__ 方法,因为当应用 @case 装饰器时,它会替换。如果需要自定义构造函数,可以使用 CaseClassMixin 作为解决方案。
使用CaseClassMixin以获得更多灵活性
由 @case 装饰器创建的类继承自 CaseClassMixin。
>>> from rbco.caseclasses import CaseClassMixin
>>> issubclass(Person, CaseClassMixin)
True
CaseClassMixin 提供了所有“案例类”行为,除了构造函数。要直接使用 CaseClassMixin,子类必须满足的唯一要求是提供一个 __fields__ 属性,它包含字段名称的序列。
这可能在需要更大灵活性的情况下很有用。在下面的示例中,我们创建了一个具有自定义构造函数的案例类
>>> class Foo(CaseClassMixin):
... __fields__ = ('field1', 'field2')
...
... def __init__(self, field1, *args):
... self.field1 = field1 + '_modified'
... self.field2 = list(args)
...
>>> Foo('bar', 1, 2)
Foo(field1='bar_modified', field2=[1, 2])
限制
案例类的构造函数无法自定义,因为它在应用 @case 装饰器时被替换。有关替代方案,请参阅有关 CaseClassMixin 的部分。
由于 __slots__ 声明,无法分配给未知字段。
构造函数不能接受 *args 或 **kwargs
>>> @case ... class Foo(object): ... def __init__(self, **kwargs): pass Traceback (most recent call last): ... RuntimeError: Case class constructor cannot take *args or **kwargs.
有关替代方案的说明,请参阅关于 CaseClassMixin 的章节。
动机、设计决策和其他实现
与MacroPy的比较
这个项目的想法来自 MacroPy。它通过语法宏实现了case类的实现,从而以非常优雅的方式定义了case类。动机是为了在不使用语法宏或字符串评估(这是namedtuple采取的方法)的情况下提供类似的功能。换句话说:在不使用太多魔法的情况下提供最佳的实现。
与 MacroPy 的比较可以总结如下
优点
没有魔法。
允许任何类型的自定义成员,包括实例方法。
由于case类只是普通类,因此允许任何类型的继承。
缺点
MacroPy的语法更优美。与比较起来,__init__存根的东西可能有点丑。
不支持自定义初始化逻辑。这可以通过使用 CaseClassMixin 实现,但程序员需要做更多的工作。
构造函数中不支持 *args 和 **kwargs。同样,这可以通过使用 CaseClassMixin 实现,但需要做更多的工作。
其他实现
Python中存在其他“case类”概念(或类似)的实现
Brian Wickman的一个简单实现可以在 这个Gist 中找到。
废弃的实现想法
考虑了一些实现想法,但后来被摒弃了。这里讨论其中的一些。
函数式语法
这意味着使用一个函数来生成类。这可能是这样的
Person = case_class('Person', 'name', age=None, gender=None)
这个想法的第一个问题是无法保留字段的顺序。case_class 函数必须定义如下
def case_class(__name__, *args, **kwargs):
...
**kwargs 是一个无序字典,因此字段的顺序丢失。
为了克服这个问题,可以使用以下语法
Person = case_class('Person', 'name', 'age', 'gender', age=None, gender=None)
我认为这个语法不够优雅。我不喜欢字段名称的重复,也不喜欢字段名称既表示为字符串又表示为参数名称。
也许这个也会有效
Person = case_class('Person', ['name', 'age', 'gender'], {'age': None, 'gender': None})
但我想这个语法也不够优雅。
此外,使用这种语法可能难以支持一些功能,特别是
自定义成员。这意味着会复杂化 case_class 函数的签名,或者需要在类创建后添加自定义成员。如下所示
Person = case_class('Person', ...) def present(self): print ... Person.present = present
不够优雅。
继承。这需要向 case_class 函数添加一个新参数,以允许传入基类。
将字段规范作为类装饰器的参数
这将结束定义空构造函数的必要性。语法将是这样的
@case(name, age=None, gender=None)
class Person(object):
'Represent a person.'
与函数语法面临相同的问题:字段顺序未保留,因为 case 函数必须接受一个 **kwargs 参数,它是一个无序字典。
与为函数语法提供的类似的其他语法可以克服字段顺序问题。然而,我认为使用 __init__ 存根定义字段的方法更优雅。
将字段规范作为类属性
语法将是这样的
@case
class Person(object):
name = NO_DEFAULT_VALUE
age = None
gender = None
再次强调,无法保留字段顺序。case 函数需要从 Person.__dic__ 中检索类属性,而这是无序的。
可能像这样会有效
@case
class Person(object):
__fields__ = (
('name', NO_DEFAULT_VALUE),
('age', None),
('gender', None)
)
但我认为使用 __init__ 模板来定义字段的方法更加优雅。
贡献
如果您想贡献,请fork此项目并提交一个pull请求。提前感谢!
变更日志
1.0.2 (2020-04-27)
允许使用更近版本的 funcsigs。[ale-rt]
小的文档调整。[rafaelbco]
改进文档字符串。[rafaelbco]
1.0.1 (2014-05-09)
改进文档。
小的重构。
1.0.0 (2014-05-09)
首次发布。
项目详情
rbco.caseclasses-1.0.2.zip 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 75cde1b8487122e9b22dee2e02d74b561e3a373b44501c1bccb5be35485abfba |
|
MD5 | 9ab5625f6b5a66d9ddf8948662424b84 |
|
BLAKE2b-256 | 6da0e0bfc08a7a54f46fdcbce3ac3b3c3f2a353abb969ecb048b30f4fdb6f32f |