Duck typing
维基百科,自由的百科全书
在程序设计中,duck typing是动态类型的一种风格,在其中,一个对象的当前方法和属性的集合,而不是从特定的类继承或实现特定的接口,决定有效的语义。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试(见下面的历史),可以这样表述:
- “当我看到一只鸟像一个鸭子地走、像一个鸭子地游泳并且像一个鸭子地叫,那个我把那只鸟称为鸭子。”
在duck typing中,关注的只是一个对象是如何使用的,而不是对象的类型本身。例如,在非duck typed语言,我们可以建立一个函数,它接受一个类型为鸭的对象并调用这个对象的走和叫方法。在duck typed语言,这样的一个函数接受一个任意类型的对象并调用这个对象的走和叫方法。如果对象没有这些需要调用的方法,那么函数将报告一个运行时错误。这种任何拥有这样的正确的走和叫方法的对象都可被函数接受的行为引出了以上表述,并命名了这种决定类型的方式。
Duck typing通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。从静态类型语言转向动态类型语言的用户通常试图添加一些静态的(在运行之前的)类型检查,从而影响了duck typing的益处和可伸缩性,并约束了语言的动态特性。
目录 |
[编辑] 概念样例
考虑用于一个duck typed语言的以下伪代码:
function calculate(a, b, c) => return (a+b)*c
example1 = calculate (1, 2, 3)
example2 = calculate ([1, 2, 3], [4, 5, 6], 2)
example3 = calculate ('apples ', 'and oranges, ', 3)
print to_string example1
print to_string example2
print to_string example3
在样例中,每次calculate被调用都使用了没有相关的继承关系的对象(数字、列表和字符串)。只要对象支持“+”和“*”方法,操作就能成功。例如,翻译成Ruby或Python语言,运行结果应该是:
9 [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6] apples and oranges, apples and oranges, apples and oranges,
这样,duck typing允许在没有继承的情况下使用多态。唯一的要求是calculate函数需要在参数对象的变量中拥有“+”和“*”方法。以下样例(python语言)体现了鸭子测试。就in_the_forest函数而言,对象是一个鸭子:
class Duck: def quack(self): print "Quaaaaaack!" def feathers(self): print "The duck has white and gray feathers." class Person: def quack(self): print "The person imitates a duck." def feathers(self): print "The person takes a feather from the ground and shows it." def in_the_forest(duck): duck.quack() duck.feathers() def game(): donald = Duck() john = Person() in_the_forest(donald) in_the_forest(john) game()
[编辑] 静态语言中的Duck typing
一些通常的静态语言如Boo和C#版本4有指示编译器将类的类型检查安排在运行时而不是编译时并在编译器输出中包含用于运行时类型检查的代码的额外的类型注解[1][2]。这些附加内容允许这些语言享受duck typing的大多数益处,仅有的缺点是需要在编译时识别和指定这些动态类。
[编辑] 与其他类型系统的比较
[编辑] 结构类型系统
Duck typing和结构类型相似但与之不同。结构类型由类型的结构决定类型的兼容性和等价性,而duck typing只由结构中在运行时所访问的部分决定类型的兼容性。Objective Caml语言使用结构类型系统。
[编辑] 接口
接口可以提供duck typing的一些益处,但duck typing与之不同的是没有显式定义任何接口。例如,如果一个第三方Java库实现了一个使用者不允许修改的类,使用者就无法把一个这个类的实例用作一个自己定义的接口的实现,而duck typing允许这样做。
[编辑] 模板或泛型
模板函数或方法在一个静态类型上下文中应用鸭子测试;这同时带来了静态和动态类型检查的一般优点和缺点。同时,由于只有在运行时被实际调用的方法需要被实现,而模板要求实现在编译时不能证明不可到达的所有方法,因此Duck typing更具有可伸缩性。
实例包括带有模板的C++语言和Java语言的泛型。
[编辑] 批评
常常被引用的一个批评是:
- 关于duck typing的一个问题是它要求程序员在任何时候都更好地理解他/她所正在其上工作的代码。在一个强静态类型的使用了类型继承树和参数类型检查的语言。给一个类提供未预测的对象类型更为困难。例如,在Python中,你可以轻易创建一个称为Wine的类,它需要一个在其中实现press属性的类。然而,一个称为Trousers的类可能也实现press()方法。使用时Duck Typing时为了避免奇怪的、难以检测的错误,开发者需要意识到每一个“press”方法可能的使用,甚至在概念上和他/她所正在其上工作的代码没有关系。
- 本质上,问题是,“如果它像一个鸭子地走并且像一个鸭子地叫”,它可以是一只正在模仿鸭子的龙。也许你不总是想让龙进入池塘,尽管它们可以模仿鸭子。
duck typing的提倡者,如Guido van Rossum,认为这个问题可以被测试和维护代码库所需要的知识解决[3][4]。
对duck typing的批评倾向于成为关于动态类型和静态类型的争论的更广阔的观点的特殊情形。
[编辑] 历史
Alex Martelli很早(2000年)就在发布到comp.lang.python新闻组上的一则消息中使用了这一术语。他同时对鸭子测试的字面上的错误理解提出了提醒,指出这个术语已经被使用。
- In other words, don't check whether it IS-a duck: check whether it QUACKS-like-a duck, WALKS-like-a duck, etc, etc, depending on exactly what subset of duck-like behaviour you need to play your language-games with.
[编辑] Implementations
[编辑] In ColdFusion
The web application scripting language ColdFusion allows function arguments to be specified as having type any. For this sort of argument, an arbitrary object can be passed in and method calls are bound dynamically at runtime. If an object does not implement a called method, a runtime exception is thrown which can be caught and handled gracefully. In ColdFusion 8, this can be picked up as a defined event onMissingMethod()rather than through an exception handler. An alternative argument type of WEB-INF.cftags.component restricts the passed argument to be a ColdFusion Component (CFC), which provides better error messages should a non-object be passed in.
[编辑] In Objective-C
Objective-C, a cross between C and Smalltalk, allows one to declare objects of type 'id' and send any message to them, like in Smalltalk. The sender can test an object to see if it responds to a message, the object can decide at the time of the message whether it will respond to it or not, and if the sender sends a message a recipient cannot respond to, an exception is raised. Thus, duck typing is fully supported by Objective-C.
[编辑] In Python
Duck typing is heavily used in Python. The Python Tutorial's Glossary defines duck typing as follows:
Pythonic programming style that determines an object's type by inspection of its method or attribute signature rather than by explicit relationship to some type object ("If it looks like a duck and quacks like a duck, it must be a duck.") By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. Duck-typing avoids tests using
type()orisinstance(). Instead, it typically employs the EAFP (Easier to Ask Forgiveness than Permission) style of programming.
The standard example of duck typing in Python is file-like classes. Classes can implement some or all of the methods of file and can be used where file would normally be used. For example, GzipFile implements a file-like object for accessing gzip-compressed data. cStringIO allows treating a Python string as a file. Sockets and files share many of the same methods as well. However, sockets lack the tell() method and cannot be used everywhere that GzipFile can be used. This shows the flexibility of duck typing: a file-like object can implement only methods it is able to, and consequently it can only be used in situations where it makes sense.
The EAFP principle describes the use of exception handling. For example instead of checking to see if some purportedly Duck-like object has a quack() method (using if hasattr(mallard, "quack"): ...) it's usually preferable to wrap the attempted quacking with exception handling
try: mallard.quack() except (AttributeError, TypeError): print "mallard can't quack()"
Advantages of this approach are that it encourages the structured handling of other classes of errors (so, for example, a mute Duck subclass could raise a "QuackException" which can be added to the wrapper without delving more deeply into the logic of the code, and it handles situations where different classes of objects might have naming collisions for incompatible members (for example, Mallard the purported medical professional might have a boolean attribute which classifies him as a "quack=True"; an attempt to perform Mallard.quack() would raise a TypeError).
In the more practical examples of classes which implement file-like behavior the use of Python's exception handling facilities is generally preferred for handling a wide variety of I/O errors that can occur due to numerous environmental and operating system issues that are outside of the programmer's control. Here again the "duck typing" exceptions can be caught in their own clauses alongside the OS, I/O or other possible errors without complicated testing and error checking logic.
[编辑] References
- ^ Boo: Duck Typing
- ^ Anders Hejlsberg Introduces C# 4.0 at PDC 2008
- ^ Bruce Eckel.Strong Typing vs. Strong Testing.mindview.
- ^ Bill Venners.Contracts in Python. A Conversation with Guido van Rossum, Part IV.Artima.
[编辑] External links
- Duck Typing: Ruby
- How to duck type? - the psychology of static typing in Ruby
- Python documentation glossary entry on duck-typing
- Dr. Dobbs June 01 2005: "Templates and Duck Typing"
- Javascript 'typeof' limitations and duck typing
- Python from a Java perspective - Part 2 - How duck typing influences class design and design principles

