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

Sam*_*ter 8 python oop types

我的大部分编程经验都是使用C++.受到Bjarne Stroustrup 在这里的讲话的启发,我最喜欢的编程技术之一是"类型丰富"的编程; 开发新的健壮数据类型,不仅可以通过将功能包装到类型中来减少我必须编写的代码量(例如向量添加,而不是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)
Run Code Online (Sandbox Code Playgroud)

这是一个好的开始,但它需要像这样访问这些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" 
Run Code Online (Sandbox Code Playgroud)

我们可以在类中添加大量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
Run Code Online (Sandbox Code Playgroud)

现在代码的大小正在快速爆炸,并且对于正在编写的内容有大量的重复,对于非常小的回报,这看起来根本不是pythonic.

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

x = BoundInt()
y = BoundInt(x)
z = BoundInt(4)
Run Code Online (Sandbox Code Playgroud)

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

所有这一切都非常像试图在python中编写c ++代码,如果我最喜欢的一本书Code Code 2被认真对待,这是一个重大的罪恶.我觉得我正在游动动态的打字电流,而不是让它带我前进.

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

aba*_*ert 4

标准库、流行的 PyPI 模块和 ActiveState 配方中都有大量代码可以执行此类操作,因此您最好阅读示例,而不是尝试从第一原理中找出答案。list另请注意,这与创建-like 或-like 类非常相似dict,还有更多示例。

\n\n

但是,对于您想做的事情,有一些答案。我将从最严重的开始,然后向后进行。

\n\n
\n

当我开始想要从普通的 python 数字(整数?)和其他 BoundInt 对象构造 BoundInt 对象时,事情变得更加复杂\n \xe2\x80\xa6\n 据我所知,其中需要使用BoundInt() 构造函数中相当大/丑陋的 if/else 类型检查语句,因为 python 不支持(c 风格)重载。

\n
\n\n

啊,但是想想你在做什么:你正在BoundInt从任何可以表现得像整数的东西构造 a ,包括,比如说,一个实际的int或 a BoundInt,对吧?那么,为什么不呢:

\n\n
def __init__(self, target, low, high):\n    self.target, self.low, self.high = int(target), int(low), int(high)\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

另外,请记住,缺乏重载并不像您想象的 C++ 那样严重,因为没有用于创建副本的“复制构造函数”;您只需将物体传递出去,所有的事情都会在幕后得到处理。

\n\n

例如,想象一下这个 C++ 代码:

\n\n
BoundInt foo(BoundInt param) { BoundInt local = param; return local; }\nBoundInt bar;\nBoundInt baz = foo(bar);\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

现在看看 Python 的等价物:

\n\n
def foo(param): local = param; return local\nbar = BoundInt();\nbaz = foo(bar)\n
Run Code Online (Sandbox Code Playgroud)\n\n

在这里,我们刚刚得到了一个BoundInt实例\xe2\x80\x94,这是显式创建的\xe2\x80\x94,我们所做的就是将新名称绑定到它。即使分配为超出baz范围的新对象的成员,也不会创建副本。唯一进行复制的就是再次显式调用。(这并不是 100% 正确,因为有人总是可以检查您的对象并尝试从外部克隆它,并且 、等实际上可能会这样做\xe2\x80\xa6 但在这种情况下,他们\'我仍然没有调用您或编译器编写的“复制构造函数”。)barbazBoundInt(baz)pickledeepcopy

\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n

但是,如果您只是在 Python 2.6-2.7 或 3.3+ 中学习这一类,我认为这是一个难以抉择的问题。

\n\n

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

\n\n

同时,您的班级似乎未指定。如果xis aBoundIntyis an intx+y是否真的应该返回 an int(就像在您的代码中那样)?如果没有的话需要绑定吗?关于什么y+x?应该做什么x+=y?等等。

\n\n

最后,在 Python 中,通常值得将这样的“值类”设为不可变,即使直观的 C​​++ 等效项是可变的。例如,考虑一下:

\n\n
>>> i = BoundInt(3, 0, 10)\n>>> j = i\n>>> i.set(5)\n>>> j\n5\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

如果你想BoundInt保持不变,除了消除诸如 之类的明显的东西之外set,还要确保不要实现__iadd__和朋友。如果省略__iadd__,i += 2将变成i = i.__add__(2): 换句话说,它将创建一个新实例,然后重新绑定i到该新实例,而保留旧实例。

\n