# 概述

# 函数、方法

Python 的面向对象功能是在基于函数的环境构建的。使用非数据描述符,两者完成了无缝融合。

类字典将方法存储为函数。在类定义中,方法是用 def​ 或 lambda​ 这两个创建函数的常用工具编写的。方法与常规函数的不同之处仅在于第一个参数是为对象实例保留的。按照 Python 约定,实例引用称为 self​,但也可以称为 this​或任何其他变量名称。

为了支持方法调用,函数类包含 __get__()​ 方法用于在访问属性时将其绑定成方法。这意味着所有函数都是非数据描述符,当从对象调用它们时,它们返回绑定方法。在纯 Python 中,它的工作方式如下:

class Function(object):
    . . .
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return types.MethodType(self, obj)

运行解释器显示了函数描述器在实践中的工作方式:

>>> class D(object):
...     def f(self, x):
...         return x
...
# 注意f 实际上是存在与D的属性空间之中的,即d.___dict__.get('f') == None
>>> d = D()


# Access through the class dictionary does not invoke __get__.
# It just returns the underlying function object.
>>> D.__dict__['f']
<function D.f at 0x00C45070>

# Dotted access from a class calls __get__() which just returns
# the underlying function unchanged.
>>> D.f
<function D.f at 0x00C45070>

# The function has a __qualname__ attribute to support introspection
>>> D.f.__qualname__
'D.f'

# Dotted access from an instance calls __get__() which returns the
# function wrapped in a bound method object
>>> d.f
<bound method D.f of <__main__.D object at 0x00B18C90>>

# Internally, the bound method stores the underlying function,
# the bound instance, and the class of the bound instance.
>>> d.f.__func__
<function D.f at 0x1012e5ae8>
>>> d.f.__self__
<__main__.D object at 0x1012e1f98>
>>> d.f.__class__
<class 'method'>

# 静态方法和类方法

非数据描述器为把函数绑定到方法的通常模式提供了一种简单的机制。

概括地说,函数具有 __get__()​ 方法,以便在作为属性访问时可以将其转换为方法。非数据描述符将 obj.f(args)​ 的调用转换为 f(obj, args)​ 。调用 klass.f(args)​ 因而变成 f(args)​。

下表总结了绑定及其两个最有用的变体:

转换形式 通过对象调用 通过类调用
function -- 函数 f(obj, *args) f(*args)
静态方法 f(*args) f(*args)
类方法 f(type(obj), *args) f(klass, *args)

静态方法返回底层函数,不做任何更改。调用 c.f​ 或 C.f​ 等效于通过 object.__getattribute__(c, "f")​ 或 object.__getattribute__(C, "f")​ 查找。结果,该函数变得可以从对象或类中进行相同的访问。

适合于作为静态方法的是那些不引用 self​变量的方法。

例如,一个统计用的包可能包含一个实验数据的容器类。该容器类提供了用于计算数据的平均值,均值,中位数和其他描述性统计信息的常规方法。但是,可能有在概念上相关但不依赖于数据的函数。例如, erf(x)​ 是在统计中的便捷转换,但并不直接依赖于特定的数据集。可以从对象或类中调用它: s.erf(1.5) --> .9332​ 或 Sample.erf(1.5) --> .9332​。

由于静态方法直接返回了底层的函数,因此示例调用是平淡的:

>>> class E(object):
...     def f(x):
...         print(x)
...     f = staticmethod(f)
...
>>> E.f(3)
3
>>> E().f(3)
3

使用非数据描述器,纯Python的版本 staticmethod()​ 如下所示:

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f

与静态方法不同,类方法在调用函数之前将类引用放在参数列表的最前。无论调用方是对象还是类,此格式相同:

>>> class E(object):
...     def f(klass, x):
...         return klass.__name__, x
...     f = classmethod(f)
...
>>> print(E.f(3))
('E', 3)
>>> print(E().f(3))
('E', 3)

此行为适用于当函数仅需要使用类引用并且不关心任何底层数据时的情况。 类方法的一种用途是创建替代的类构造器。 在 Python 2.3 中,类方法 dict.fromkeys()​ 会从键列表中创建一个新的字典。 纯 Python 的等价形式是:

class Dict(object):
    . . .
    def fromkeys(klass, iterable, value=None):
        "Emulate dict_fromkeys() in Objects/dictobject.c"
        d = klass()
        for key in iterable:
            d[key] = value
        return d
    fromkeys = classmethod(fromkeys)

现在可以这样构造一个新的唯一键字典:

>>> Dict.fromkeys('abracadabra')
{'a': None, 'r': None, 'b': None, 'c': None, 'd': None

使用非数据描述符协议,纯 Python 版本的 classmethod()​ 如下:

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc