实现数据类型的 Pythonic 方式(Python 2.7)

标签 python oop types

我的大部分编程经验都是使用 C++。灵感来自 Bjarne Stroustrup 的演讲 here ,我最喜欢的编程技术之一是“类型丰富”的编程;开发新的健壮数据类型,不仅可以通过将功能包装到类型中来减少我必须编写的代码量(例如向量加法,而不是 newVec.x = vec1.x + vec2.x; newVec.y = ... 等,我们可以只使用 newVec = vec1 + vec2) 但也会在编译时通过强类型系统揭示代码中的问题。

我最近在 Python 中进行的一个项目 2.7 需要具有上限和下限的整数值。我的第一直觉是创建一个新的数据类型(类),它的行为与 Python 中的普通数字完全相同,但始终在其(动态)边界值内。

class BoundInt:
    def __init__(self, target = 0, low = 0, high = 1):
        self.lowerLimit = low
        self.upperLimit = high
        self._value = target
        self._balance()

    def _balance(self):
        if (self._value > self.upperLimit):
            self._value = self.upperLimit
        elif (self._value < self.lowerLimit):
            self._value = self.lowerLimit
        self._value = int(round(self._value))

    def value(self):
        self._balance()
        return self._value

    def set(self, target):
        self._value = target
        self._balance()

    def __str__(self):
        return str(self._value)

这是一个好的开始,但它需要像这样访问这些 BoundInt 类型的内容
x = BoundInt()
y = 4
x.set(y)           #it would be nicer to do something like x = y
print y            #prints "4"
print x            #prints "1"
z = 2 + x.value()  #again, it would be nicer to do z = 2 + x
print z            #prints "3" 

我们可以在类中添加大量python的“魔术方法”定义来添加更多功能:
def __add__(self, other):
    return self._value + other

def __sub__(self, other):
    return self._value - other

def __mul__(self, other):
    return self._value * other

def __div__(self, other):
    return self._value / other

def __pow__(self, power):
    return self._value**power

def __radd__(self, other):
    return self._value + other

#etc etc

现在代码的大小正在迅速爆炸,并且正在编写的内容有大量重复,但返回很少,这似乎根本不是 Pythonic。

当我开始想从普通的 Python 数字(整数?)和其他 BoundInt 对象构造 BoundInt 对象时,事情变得更加复杂
x = BoundInt()
y = BoundInt(x)
z = BoundInt(4)

其中,据我所知,需要在 BoundInt() 构造函数中使用相当大/丑陋的 if/else 类型检查语句,因为 python 不支持(c 样式)重载。

所有这些感觉都非常像尝试用 python 编写 c++ 代码,如果我最喜欢的一本书 Code Complete 2 被认真对待,这是一种大罪。我觉得我是在逆流而上,而​​不是让它带我前进。

我非常想学习编写 python 'pythonic-ally',解决这种问题域的最佳方法是什么?什么是学习正确的 Pythonic 风格的好资源?

最佳答案

标准库、流行的 PyPI 模块和 ActiveState 配方中有大量代码可以执行此类操作,因此您最好阅读示例,而不是试图从基本原理中弄清楚。另外,请注意,这与创建 list 非常相似。 -like 或 dict -like class,还有更多的例子。

但是,对于您想要做什么,有一些答案。我将从最严重的开始,然后向后工作。

Things get even more complicated when I start to want to construct BoundInt objects from normal python numbers (integers?), and other BoundInt objects … Which, as far as I'm aware requires the use of rather large/ugly if/else type checking statements within the BoundInt() constructor, as python does not support (c style) overloading.



啊,但是想想你在做什么:你正在构建一个 BoundInt来自任何可以像整数一样的东西,例如,一个实际的 intBoundInt , 对?那么,为什么不:
def __init__(self, target, low, high):
    self.target, self.low, self.high = int(target), int(low), int(high)

我假设您已经添加了 __int__方法 BoundInt ,当然(相当于 C++ explicit operator int() const )。

另外,请记住,缺少重载并不像您从 C++ 中想到的那么严重,因为没有用于制作副本的“复制构造函数”;您只需将对象传递给周围,所有这些都会在幕后处理。

例如,想象一下这个 C++ 代码:
BoundInt foo(BoundInt param) { BoundInt local = param; return local; }
BoundInt bar;
BoundInt baz = foo(bar);

本副本barparam , paramlocal , local到一个未命名的“返回值”变量,然后到 baz .其中一些将被优化掉,而其他(在 C++11 中)将使用移动而不是复制,但是,您仍然有 4 个复制/移动构造函数/赋值运算符的概念调用。

现在看看 Python 的等价物:
def foo(param): local = param; return local
bar = BoundInt();
baz = foo(bar)

在这里,我们只有一个 BoundInt实例——显式创建的那个——而我们所做的只是给它绑定(bind)新的名字。偶分配baz作为超出 bar 范围的新对象的成员和 baz不会复制。制作副本的唯一方法是显式调用 BoundInt(baz)再次。 (这不是 100% 正确,因为有人总是可以检查您的对象并尝试从外部克隆它,而 pickledeepcopy 等实际上可能会这样做……但在这种情况下,它们是仍然没有调用您或编译器编写的“复制构造函数”。)

现在,将所有这些运算符转发到值怎么样?

好吧,一种可能性是动态进行。详细信息取决于您使用的是 Python 3 还是 2(对于 2,您需要支持多远)。但是这个想法是你只有一个名称列表,对于每个名称,你定义一个具有该名称的方法,该方法调用值对象上的同名方法。如果您想要一个草图,请提供额外的信息并询问,但您最好寻找动态方法创建的示例。

那么,那是 Pythonic 吗?这要看情况。

如果您正在创建数十个“类似整数”的类,那么是的,它肯定比复制粘贴代码或添加“编译时”生成步骤要好,并且可能比添加其他不需要的基类要好。

如果您尝试跨多个版本的 Python 工作并且不想记住“我应该停止提供哪个版本 __cmp__ 来再次像 int 一样?”输入问题,我可能会更进一步并从 int 中获取方法列表本身(取 dir(int()) 并将一些名字列入黑名单)。

但是如果你只是在做这门课,比如说,只是 Python 2.6-2.7 或只是 3.3+,我认为这是一个折腾。

一个很好的阅读类(class)是 fractions.Fraction标准库中的类。它是清晰编写的纯 Python 代码。它部分演示了动态和显式机制(因为它根据通用动态转发功能明确定义了每个特殊消息),如果您同时拥有 2.x 和 3.x,则可以比较和对比两者。

同时,您的类(class)似乎未指定。如 xBoundIntyint , 应该 x+y真回个int (就像在您的代码中一样)?如果没有,你需要绑定(bind)它吗?怎么样y+x ?应该怎么做x+=y做?等等。

最后,在 Python 中,通常值得让“值类”像这样不可变,即使直观的 C​​++ 等价物是可变的。例如,考虑这个:
>>> i = BoundInt(3, 0, 10)
>>> j = i
>>> i.set(5)
>>> j
5

我不认为你会期待这一点。这不会发生在 C++ 中(对于典型的值类),因为 j = i会创建一个新副本,但在 Python 中,它只是将一个新名称绑定(bind)到同一个副本。 (它相当于 BoundInt &j = i ,而不是 BoundInt j = i 。)

如果你想要 BoundInt保持不变,除了消除像 set 这样明显的东西,还要确保不执行 __iadd__和 friend 。如果您遗漏了 __iadd__ , i += 2将变成i = i.__add__(2) : 换句话说,它会创建一个新实例,然后重新绑定(bind) i到那个新实例,留下旧实例。

关于实现数据类型的 Pythonic 方式(Python 2.7),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13242501/

相关文章:

python - 使用pandas进行回归,报错: cannot concatenate 'str' and 'float' objects

javascript - 组匹配之间的正则表达式匹配文本

java - 我们可以为对象实现的接口(interface)数量是否有限? ( java 语)

c++ - 为什么我应该更喜欢使用成员初始化列表?

C++/Eigen : Get type of matrix argument to a template written for MatrixBase

Java 比较泛型和 Void

python - ipython和python之间的输出差异

python - 如何对每一行求和并用总和替换行中的每个值?

java - 迭代器模式的目的。我理解对了吗?

excel - 如何声明非变量类型