与继承方法调用相比,使用 len()、max() 和 min() 等静态函数的优点

发布于 2024-08-08 22:53:19 字数 266 浏览 10 评论 0 原文

我是一个Python新手,我不知道为什么Python通过obj.len()、obj实现len(obj)、max(obj)和min(obj)作为类似静态的函数(我来自java语言) .max() 和 obj.min()

与方法调用相比,使用 len()... 有何优点和缺点(除了明显的不一致之外)?

为什么 guido 选择这个而不是方法调用? (如果需要的话,这可以在python3中解决,但在python3中没有改变,所以必须有充分的理由......我希望)

谢谢!

i am a python newbie, and i am not sure why python implemented len(obj), max(obj), and min(obj) as a static like functions (i am from the java language) over obj.len(), obj.max(), and obj.min()

what are the advantages and disadvantages (other than obvious inconsistency) of having len()... over the method calls?

why guido chose this over the method calls? (this could have been solved in python3 if needed, but it wasn't changed in python3, so there gotta be good reasons...i hope)

thanks!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

随风而去 2024-08-15 22:53:19

最大的优点是内置函数(和运算符)可以在适当的时候应用额外的逻辑,而不仅仅是简单地调用特殊方法。例如,min 可以查看多个参数并应用适当的不等式检查,或者它可以接受单个可迭代参数并进行类似的操作; abs 当在没有特殊方法的对象上调用时 __abs__ 可以尝试将所述对象与 0 进行比较,并在需要时使用对象更改符号方法(尽管目前没有);等等。

因此,为了保持一致性,所有具有广泛适用性的操作都必须始终通过内置函数和/或运算符,并且这些内置函数有责任查找并应用适当的特殊方法(在一个或多个参数上),使用替代方法适用的逻辑等等。

没有正确应用此原则(但在 Python 3 中修复了不一致问题)的一个示例是“向前迭代迭代器”:在 2.5 及更早版本中,您需要定义并调用非特殊命名的 next< /code> 迭代器上的方法。在 2.6 及更高版本中,您可以以正确的方式执行此操作:迭代器对象定义 __next__,新的 next 内置函数可以调用它 apply额外的逻辑,例如提供默认值(在 2.6 中,为了向后兼容,您仍然可以采用糟糕的旧方法,尽管在 3.* 中您不能再这样做了)。

另一个例子:考虑表达式x + y。在传统的面向对象语言中(只能根据最左边参数的类型进行分派——例如 Python、Ruby、Java、C++、C# 和 c),如果 x 是某种内置的 - in type 且 y 是您自己喜欢的新类型,如果该语言坚持将所有逻辑委托给 type(x) 方法,那么您就很不幸了实现加法(假设该语言允许运算符重载;-)。

在 Python 中,+ 运算符(当然还有类似的内置 operator.add,如果您喜欢的话)会尝试 x 类型的 __add__,并且如果那个人不知道如何处理 y,则尝试 y 类型的 __radd__。因此,您可以定义知道如何将自身添加到整数、浮点数、复数等的类型,以及知道如何将此类内置数字类型添加到自身的类型(即,您可以对其进行编码,以便 y 是您喜欢的新类型的实例并且 x 是时,>x + y 和 y + x 都可以正常工作某些内置数字类型的实例)。

“通用函数”(如 PEAK 中)是一种更优雅的方法(允许基于类型组合的任何重写,永远,疯狂偏执地关注 OOP 鼓励的最左边的参数!-),但是(a) 不幸的是,它们没有被 Python 3 接受,并且 (b) 它们当然需要将泛型函数表示为独立的(必须将函数视为“属于”任何单个函数,这绝对是疯狂的)类型,其中重点是可以根据其多个参数类型的任意组合进行不同的覆盖/重载!-)。任何用 Common Lisp、Dylan 或 PEAK 编程过的人都知道我在说什么;-)。

因此,独立的函数和运算符是正确的、一致的方法(尽管在基本的 Python 中缺乏泛型函数确实消除了固有优雅的一些部分,但它仍然优雅与实用的合理结合!-)。

The big advantage is that built-in functions (and operators) can apply extra logic when appropriate, beyond simply calling the special methods. For example, min can look at several arguments and apply the appropriate inequality checks, or it can accept a single iterable argument and proceed similarly; abs when called on an object without a special method __abs__ could try comparing said object with 0 and using the object change sign method if needed (though it currently doesn't); and so forth.

So, for consistency, all operations with wide applicability must always go through built-ins and/or operators, and it's those built-ins responsibility to look up and apply the appropriate special methods (on one or more of the arguments), use alternate logic where applicable, and so forth.

An example where this principle wasn't correctly applied (but the inconsistency was fixed in Python 3) is "step an iterator forward": in 2.5 and earlier, you needed to define and call the non-specially-named next method on the iterator. In 2.6 and later you can do it the right way: the iterator object defines __next__, the new next built-in can call it and apply extra logic, for example to supply a default value (in 2.6 you can still do it the bad old way, for backwards compatibility, though in 3.* you can't any more).

Another example: consider the expression x + y. In a traditional object-oriented language (able to dispatch only on the type of the leftmost argument -- like Python, Ruby, Java, C++, C#, &c) if x is of some built-in type and y is of your own fancy new type, you're sadly out of luck if the language insists on delegating all the logic to the method of type(x) that implements addition (assuming the language allows operator overloading;-).

In Python, the + operator (and similarly of course the builtin operator.add, if that's what you prefer) tries x's type's __add__, and if that one doesn't know what to do with y, then tries y's type's __radd__. So you can define your types that know how to add themselves to integers, floats, complex, etc etc, as well as ones that know how to add such built-in numeric types to themselves (i.e., you can code it so that x + y and y + x both work fine, when y is an instance of your fancy new type and x is an instance of some builtin numeric type).

"Generic functions" (as in PEAK) are a more elegant approach (allowing any overriding based on a combination of types, never with the crazy monomaniac focus on the leftmost arguments that OOP encourages!-), but (a) they were unfortunately not accepted for Python 3, and (b) they do of course require the generic function to be expressed as free-standing (it would be absolutely crazy to have to consider the function as "belonging" to any single type, where the whole POINT is that can be differently overridden/overloaded based on arbitrary combination of its several arguments' types!-). Anybody who's ever programmed in Common Lisp, Dylan, or PEAK, knows what I'm talking about;-).

So, free-standing functions and operators are just THE right, consistent way to go (even though the lack of generic functions, in bare-bones Python, does remove some fraction of the inherent elegance, it's still a reasonable mix of elegance and practicality!-).

丶视觉 2024-08-15 22:53:19

它强调对象的功能,而不是其方法或类型。功能由“辅助”函数声明,例如 __iter__ 和 __len__ ,但它们不构成接口。该接口位于内置函数中,除此之外,还位于内置运算符中,例如用于索引和切片的 + 和 []。

有时,它不是一对一的对应关系:例如,iter(obj) 返回对象的迭代器,即使未定义 __iter__ 也可以工作。如果未定义,它将继续查看对象是否定义了 __getitem__ 并将返回一个按索引访问对象的迭代器(如数组)。

这与 Python 的 Duck Typing 相结合,我们只关心我们可以对对象做什么,而不关心它是否属于特定类型。

It emphasizes the capabilities of an object, not its methods or type. Capabilites are declared by "helper" functions such as __iter__ and __len__ but they don't make up the interface. The interface is in the builtin functions, and beside this also in the buit-in operators like + and [] for indexing and slicing.

Sometimes, it is not a one-to-one correspondance: For example, iter(obj) returns an iterator for an object, and will work even if __iter__ is not defined. If not defined, it goes on to look if the object defines __getitem__ and will return an iterator accessing the object index-wise (like an array).

This goes together with Python's Duck Typing, we care only about what we can do with an object, not that it is of a particular type.

我是男神闪亮亮 2024-08-15 22:53:19

实际上,这些并不是您所考虑的“静态”方法。它们是内置函数,实际上只是实现Python对象上某些方法的别名他们。

>>> class Foo(object):
...     def __len__(self):
...             return 42
... 
>>> f = Foo()
>>> len(f)
42

无论对象是否实现它们,它们始终可供调用。关键是要有一定的一致性。惯例是实现 len 并让调用者始终通过更具可读性的 len(obj) 而不是 obj 来访问它,而不是某个类具有一个名为 length() 的方法和另一个名为 size() 的方法。 methodThatDoesSomethingCommon 方法

Actually, those aren't "static" methods in the way you are thinking about them. They are built-in functions that really just alias to certain methods on python objects that implement them.

>>> class Foo(object):
...     def __len__(self):
...             return 42
... 
>>> f = Foo()
>>> len(f)
42

These are always available to be called whether or not the object implements them or not. The point is to have some consistency. Instead of some class having a method called length() and another called size(), the convention is to implement len and let the callers always access it by the more readable len(obj) instead of obj.methodThatDoesSomethingCommon

2024-08-15 22:53:19

我认为原因是这些基本操作可以在具有与容器相同接口的迭代器上完成。然而,它实际上不适用于 len:

def foo():
    for i in range(10):
        yield i
print len(foo())

... 因 TypeError 失败。 len() 不会消耗和计算迭代器;它仅适用于具有 __len__ 调用的对象。

所以,就我而言,len() 不应该存在。说 obj.len 比说 len(obj) 更自然,并且与语言的其余部分和标准库更一致。我们不说append(lst, 1);我们说 lst.append(1)。对于长度有一个单独的全局方法是一种奇怪的、不一致的特殊情况,并且会占用全局命名空间中非常明显的名称,这是Python的一个非常坏的习惯。

这与鸭子类型无关;您可以使用 getattr(obj, "len") 来决定是否可以在对象上使用 len,这与使用 getattr(obj, “__len__”)

话虽如此,对于那些认为这是语言疣的人来说,这是一个很容易忍受的问题。

另一方面,min 和 max 确实适用于迭代器,这使它们可以与任何特定对象分开使用。这很简单,所以我只举一个例子:

import random
def foo():
    for i in range(10):
        yield random.randint(0, 100)
print max(foo())

但是,没有 __min____max__ 方法来覆盖其行为,因此没有一致的方法来提供高效的搜索对于已排序的容器。如果容器按照您正在搜索的相同键进行排序,则最小/最大是 O(1) 操作而不是 O(n),并且公开它的唯一方法是通过不同的、不一致的方法。 (当然,这可以相对容易地在语言中修复。)

接下来讨论另一个问题:它阻止使用 Python 的方法绑定。作为一个简单的、人为的示例,您可以这样做来提供一个函数来将值添加到列表中:

def add(f):
    f(1)
    f(2)
    f(3)
lst = []
add(lst.append)
print lst

这适用于所有成员函数。但是,您不能使用 min、max 或 len 来做到这一点,因为它们不是它们所操作的对象的方法。相反,您必须求助于 functools.partial,这是其他语言中常见的笨拙的二等解决方法。

当然,这种情况并不常见;但正是不常见的情况告诉我们语言的一致性。

I thought the reason was so these basic operations could be done on iterators with the same interface as containers. However, it actually doesn't work with len:

def foo():
    for i in range(10):
        yield i
print len(foo())

... fails with TypeError. len() won't consume and count an iterator; it only works with objects that have a __len__ call.

So, as far as I'm concerned, len() shouldn't exist. It's much more natural to say obj.len than len(obj), and much more consistent with the rest of the language and the standard library. We don't say append(lst, 1); we say lst.append(1). Having a separate global method for length is an odd, inconsistent special case, and eats a very obvious name in the global namespace, which is a very bad habit of Python.

This is unrelated to duck typing; you can say getattr(obj, "len") to decide whether you can use len on an object just as easily--and much more consistently--than you can use getattr(obj, "__len__").

All that said, as language warts go--for those who consider this a wart--this is a very easy one to live with.

On the other hand, min and max do work on iterators, which gives them a use apart from any particular object. This is straightforward, so I'll just give an example:

import random
def foo():
    for i in range(10):
        yield random.randint(0, 100)
print max(foo())

However, there are no __min__ or __max__ methods to override its behavior, so there's no consistent way to provide efficient searching for sorted containers. If a container is sorted on the same key that you're searching, min/max are O(1) operations instead of O(n), and the only way to expose that is by a different, inconsistent method. (This could be fixed in the language relatively easily, of course.)

To follow up with another issue with this: it prevents use of Python's method binding. As a simple, contrived example, you can do this to supply a function to add values to a list:

def add(f):
    f(1)
    f(2)
    f(3)
lst = []
add(lst.append)
print lst

and this works on all member functions. You can't do that with min, max or len, though, since they're not methods of the object they operate on. Instead, you have to resort to functools.partial, a clumsy second-class workaround common in other languages.

Of course, this is an uncommon case; but it's the uncommon cases that tell us about a language's consistency.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文