跳转到内容

Python 编程/类

来自维基教科书,开放世界中的开放书籍


类是一种将相似的数据和函数聚合在一起的方式。类本质上是一个作用域,在其中执行各种代码(尤其是函数定义),而此作用域的局部变量成为类的属性,以及由此类构建的任何对象的属性。由类构建的对象称为该类的实例

Python 中的类概览

import math
class MyComplex:
  """A complex number"""       # Class documentation
  classvar = 0.0               # A class attribute, not an instance one
  def phase(self):             # A method
    return math.atan2(self.imaginary, self.real)
  def __init__(self):          # A constructor
    """A constructor"""
    self.real = 0.0            # An instance attribute
    self.imaginary = 0.0
c1 = MyComplex()
c1.real = 3.14                 # No access protection
c1.imaginary = 2.71
phase = c1.phase()             # Method call
c1.undeclared = 9.99           # Add an instance attribute
del c1.undeclared              # Delete an instance attribute

print(vars(c1))                # Attributes as a dictionary
vars(c1)["undeclared2"] = 7.77 # Write access to an attribute
print(c1.undeclared2)          # 7.77, indeed

MyComplex.classvar = 1         # Class attribute access
print(c1.classvar == 1)        # True; class attribute access, not an instance one
print("classvar" in vars(c1))  # False
c1.classvar = -1               # An instance attribute overshadowing the class one
MyComplex.classvar = 2         # Class attribute access
print(c1.classvar == -1)       # True; instance attribute access
print("classvar" in vars(c1))  # True

class MyComplex2(MyComplex):   # Class derivation or inheritance
  def __init__(self, re = 0, im = 0):
    self.real = re             # A constructor with multiple arguments with defaults
    self.imaginary = im
  def phase(self):
    print("Derived phase")
    return MyComplex.phase(self) # Call to a base class; "super"
c3 = MyComplex2()
c4 = MyComplex2(1, 1)
c4.phase()                     # Call to the method in the derived class

class Record: pass             # Class as a record/struct with arbitrary attributes
record = Record()
record.name = "Joe"
record.surname = "Hoe"

定义类

[编辑 | 编辑源代码]

要定义类,请使用以下格式

class ClassName:
    "Here is an explanation about your class"
    pass

此类定义中的大写字母是约定,但语言不需要。通常最好至少添加一个简短的解释来说明你的类应该做什么。上面的代码中的 pass 语句只是告诉 Python 解释器继续执行,不做任何事情。一旦你添加了第一个语句,就可以删除它。

实例构建

[编辑 | 编辑源代码]

类是一个可调用对象,在被调用时构建类的实例。假设我们创建一个类 Foo。

class Foo:
    "Foo is our new toy."
    pass

要构建类 Foo 的实例,请“调用”类对象

f = Foo()

这会构建类 Foo 的一个实例,并在 f 中创建一个指向它的引用。

类成员

[编辑 | 编辑源代码]

为了访问类实例的成员,请使用语法 <类实例>.<成员>。还可以使用 <类名>.<成员> 访问类定义的成员。

方法是类中的函数。第一个参数(方法必须始终至少接受一个参数)始终是调用函数的类实例。例如

>>> class Foo:
...     def setx(self, x):
...         self.x = x
...     def bar(self):
...         print(self.x)

如果执行此代码,不会发生任何事情,至少在构建 Foo 的实例之前,然后在该实例上调用 bar 之前,不会发生任何事情。

为什么是必填参数?
[编辑 | 编辑源代码]

在普通函数中,如果你要设置一个变量,例如 test = 23,你将无法访问 test 变量。键入 test 会显示它未定义。这在类函数中也是如此,除非它们使用 self 变量。

基本上,在前面的示例中,如果我们删除 self.x,函数 bar 将无法执行任何操作,因为它无法访问 x。setx() 中的 x 会消失。self 参数将变量保存到类的“共享变量”数据库中。

为什么是 self?

[编辑 | 编辑源代码]

你不需要使用 self。但是,使用 self 是一种规范。

调用方法

[编辑 | 编辑源代码]

调用方法与调用函数非常相似,但不是将实例作为第一个参数传递,就像形式参数列表所建议的那样,而是将函数作为实例的属性使用。

>>> f = Foo()
>>> f.setx(5)
>>> f.bar()

这将输出

5

可以使用它作为定义类的属性(而不是该类的实例)来调用任意对象上的方法,如下所示

>>> Foo.setx(f,5)
>>> Foo.bar(f)

这将具有相同的输出。

动态类结构

[编辑 | 编辑源代码]

正如上面的方法 setx 所示,Python 类中的成员可以在运行时改变,而不仅仅是它们的值,这与 C++ 或 Java 等语言中的类不同。我们甚至可以在运行上面的代码后删除 f.x。

>>> del f.x
>>> f.bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 5, in bar
AttributeError: Foo instance has no attribute 'x'

这带来的另一个影响是,我们可以在程序执行期间更改 Foo 类的定义。在下面的代码中,我们在 Foo 类定义中创建一个名为 y 的成员。如果我们随后创建 Foo 的新实例,它现在将拥有这个新成员。

>>> Foo.y = 10
>>> g = Foo()
>>> g.y
10

查看类字典

[编辑 | 编辑源代码]

这一切的核心是一个 字典,可以通过“vars(ClassName)”访问

>>> vars(g)
{}

起初,这个输出毫无意义。我们刚刚看到 g 拥有成员 y,那么为什么它不在成员字典中呢?但是,如果你还记得,我们将 y 放入类定义 Foo 中,而不是 g 中。

>>> vars(Foo)
{'y': 10, 'bar': <function bar at 0x4d6a3c>, '__module__': '__main__',
 'setx': <function setx at 0x4d6a04>, '__doc__': None}

在那里我们拥有 Foo 类定义的所有成员。当 Python 检查 g.member 时,它首先检查 g 的 vars 字典中的“member”,然后检查 Foo。如果我们创建 g 的新成员,它将被添加到 g 的字典中,但不会添加到 Foo 的字典中。

>>> g.setx(5)
>>> vars(g)
{'x': 5}

请注意,如果我们现在为 g.y 分配一个值,我们不是将该值分配给 Foo.y。Foo.y 将仍然是 10,但 g.y 现在将覆盖 Foo.y

>>> g.y = 9
>>> vars(g)
{'y': 9, 'x': 5}
>>> vars(Foo)
{'y': 10, 'bar': <function bar at 0x4d6a3c>, '__module__': '__main__',
 'setx': <function setx at 0x4d6a04>, '__doc__': None}

果然,如果我们检查这些值

>>> g.y
9
>>> Foo.y
10

请注意,f.y 也将是 10,因为 Python 在 vars(f) 中找不到“y”,所以它将从 vars(Foo) 中获取“y”的值。

有些人可能还注意到,Foo 中的方法出现在类字典中,与 x 和 y 位于同一位置。如果你还记得关于 lambda 函数 的那一节,我们可以将函数视为变量。这意味着我们可以在运行时将方法分配给类,就像我们分配变量一样。但是,如果你这样做,请记住,如果我们调用类实例的方法,传递给方法的第一个参数始终是类实例本身。

更改类字典

[编辑 | 编辑源代码]

我们也可以使用类的 __dict__ 成员访问类的成员字典。

>>> g.__dict__
{'y': 9, 'x': 5}

如果我们在 g.__dict__ 中添加、删除或更改键值对,这将与我们在 g 的成员中进行这些更改具有相同的效果。

>>> g.__dict__['z'] = -4
>>> g.z
-4

为什么要使用类?

[编辑 | 编辑源代码]

类之所以特殊,是因为一旦创建了实例,该实例就独立于所有其他实例。我可以有两个实例,每个实例都有不同的 x 值,它们不会影响彼此的 x。

f = Foo()
f.setx(324)
f.boo()
g = Foo()
g.setx(100)
g.boo()

f.boo()g.boo() 将打印不同的值。

新式类

[编辑 | 编辑源代码]

新式类在 Python 2.2 中引入。新式类是基类为内置类的类,最常见的是 object。在低级层面上,旧类和新类之间的主要区别在于它们的类型。旧类实例都具有 instance 类型。新式类实例将返回与其类型相同的 x.__class__ 的内容。这将用户定义的类与内置类放在同一个水平面上。旧/经典类将在 Python 3 中消失。考虑到这一点,所有开发都应该使用新式类。新式类还添加了像属性和静态方法这样的结构,这些结构为 Java 程序员所熟知。

旧/经典类

>>> class ClassicFoo:
...     def __init__(self):
...         pass

新式类

>>> class NewStyleFoo(object):
...     def __init__(self):
...         pass

属性是具有 getter 和 setter 方法的属性。

>>> class SpamWithProperties(object):
...     def __init__(self):
...         self.__egg = "MyEgg"
...     def get_egg(self):
...         return self.__egg
...     def set_egg(self, egg):
...         self.__egg = egg
...     egg = property(get_egg, set_egg)

>>> sp = SpamWithProperties()
>>> sp.egg
'MyEgg'
>>> sp.egg = "Eggs With Spam"
>>> sp.egg
'Eggs With Spam'
>>>

并且从 Python 2.6 开始,使用 @property 装饰器

>>> class SpamWithProperties(object):
...     def __init__(self):
...         self.__egg = "MyEgg"
...     @property
...     def egg(self):
...         return self.__egg
...     @egg.setter
...     def egg(self, egg):
...         self.__egg = egg

静态方法

[编辑 | 编辑源代码]

Python 中的静态方法就像 C++ 或 Java 中的对应方法一样。静态方法没有 "self" 参数,也不需要您在使用它们之前实例化类。它们可以使用 staticmethod() 定义。

>>> class StaticSpam(object):
...     def StaticNoSpam():
...         print("You can't have have the spam, spam, eggs and spam without any spam... that's disgusting")
...     NoSpam = staticmethod(StaticNoSpam)

>>> StaticSpam.NoSpam()
You can't have have the spam, spam, eggs and spam without any spam... that's disgusting

它们也可以使用函数装饰器 @staticmethod 定义。

>>> class StaticSpam(object):
...     @staticmethod
...     def StaticNoSpam():
...         print("You can't have have the spam, spam, eggs and spam without any spam... that's disgusting")

像所有面向对象的语言一样,Python 提供对继承的支持。继承是一个简单的概念,通过它,一个类可以扩展另一个类的功能,或者在 Python 的情况下,扩展多个类的功能。使用以下格式:

class ClassName(BaseClass1, BaseClass2, BaseClass3,...):
    ...

ClassName 被称为派生类,即从基类派生而来。派生类将具有其基类的所有成员。如果在派生类和基类中定义了方法,派生类中的成员将覆盖基类中的成员。为了使用基类中定义的方法,有必要将该方法作为属性在定义类上调用,如上面的 Foo.setx(f,5)

>>> class Foo:
...     def bar(self):
...         print("I'm doing Foo.bar()")
...     x = 10
...
>>> class Bar(Foo):
...     def bar(self):
...         print("I'm doing Bar.bar()")
...         Foo.bar(self)
...     y = 9
...
>>> g = Bar()
>>> Bar.bar(g)
I'm doing Bar.bar()
I'm doing Foo.bar()
>>> g.y
9
>>> g.x
10

再次,我们可以通过查看类字典来了解发生了什么。

>>> vars(g)
{}
>>> vars(Bar)
{'y': 9, '__module__': '__main__', 'bar': <function bar at 0x4d6a04>,
 '__doc__': None}
>>> vars(Foo)
{'x': 10, '__module__': '__main__', 'bar': <function bar at 0x4d6994>,
 '__doc__': None}

当我们调用 g.x 时,它首先按惯例查看 vars(g) 字典。如上所述,它接下来会检查 vars(Bar),因为 g 是 Bar 的一个实例。但是,由于继承,如果它在 vars(Bar) 中没有找到 x,Python 将会检查 vars(Foo)。

多重继承

[编辑 | 编辑源代码]

如部分 #Inheritance 所示,一个类可以从多个类派生

class ClassName(BaseClass1, BaseClass2, BaseClass3):
    pass

多重继承中一个棘手的问题是方法解析:在方法调用时,如果方法名来自多个基类或它们的基类,应该调用哪个基类方法。

方法解析顺序取决于类是旧式类还是新式类。对于旧式类,派生类按从左到右的顺序考虑,基类的基类在移动到右边之前被考虑。因此,在上面,BaseClass1 首先被考虑,如果在那里没有找到方法,则考虑 BaseClass1 的基类。如果失败,则考虑 BaseClass2,然后考虑它的基类,依此类推。对于新式类,请参阅在线的 Python 文档。

链接

特殊方法

[编辑 | 编辑源代码]

有一些方法具有保留名称,这些名称用于特殊目的,例如模拟数值或容器操作,以及其他一些操作。所有这些名称都以两个下划线开头和结尾。约定是,以单个下划线开头的成员是它们引入的范围的 "私有" 成员。

初始化和删除

[编辑 | 编辑源代码]

这些目的之一是构造实例,它的特殊名称是 "__init__"。__init__() 在返回实例之前被调用(手动返回实例是不必要的)。例如:

class A:
    def __init__(self):
        print('A.__init__()')
a = A()

输出

A.__init__()

__init__() 可以接收参数,在这种情况下,必须向类传递参数才能创建实例。例如:

class Foo:
    def __init__ (self, printme):
        print(printme)
foo = Foo('Hi!')

输出

Hi!

以下示例展示了使用 __init__() 和不使用 __init__() 之间的区别

class Foo:
    def __init__ (self, x):
         print(x)
foo = Foo('Hi!')
class Foo2:
    def setx(self, x):
        print(x)
f = Foo2()
Foo2.setx(f,'Hi!')

输出

Hi!
Hi!

类似地,当实例被销毁时(例如,当它不再被引用时)会调用 "__del__"。

__enter__ 和 __exit__
[编辑 | 编辑源代码]

这些方法也是构造函数和析构函数,但它们只在使用 with 实例化类时执行。示例

class ConstructorsDestructors:
    def __init__(self):
        print('init')

    def __del__(self):
        print('del')

    def __enter__(self):
        print('enter')

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit')

with ConstructorsDestructors():
    pass
init
enter
exit
del

元类 构造函数。

将对象转换为字符串,例如使用 print 语句或使用 str() 转换函数,可以通过覆盖 __str__ 来覆盖。通常,__str__ 返回对象内容的格式化版本。这通常不会是可执行的内容。

例如

class Bar:
    def __init__ (self, iamthis):
        self.iamthis = iamthis
    def __str__ (self):
        return self.iamthis
bar = Bar('apple')
print(bar)

输出

apple

这个函数很像 __str__()。如果 __str__ 不存在,但这个存在,则使用这个函数的输出来代替打印。__repr__ 用于以字符串形式返回对象的表示。一般来说,它可以执行以获取回原始对象。

例如

class Bar:
    def __init__ (self, iamthis):
        self.iamthis = iamthis
    def __repr__(self):
        return "Bar('%s')" % self.iamthis
bar = Bar('apple')
bar

输出(注意区别:可能不需要将它放在 print 中,但在 Python 2.7 中需要)

Bar('apple')
字符串表示覆盖函数
函数 运算符
__str__ str(A)
__repr__ repr(A)
__unicode__ unicode(x) (仅限 2.x)
__setattr__
[编辑 | 编辑源代码]

这是负责设置类属性的函数。它提供了正在分配的变量的名称和值。当然,每个类都带有一个默认的 __setattr__,它只是设置变量的值,但我们可以覆盖它。

>>> class Unchangable:
...    def __setattr__(self, name, value):
...        print("Nice try")
...
>>> u = Unchangable()
>>> u.x = 9
Nice try
>>> u.x
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: Unchangable instance has no attribute 'x'
__getattr___
[编辑 | 编辑源代码]

与 __setattr__ 相似,除了这个函数是在我们尝试访问类成员时被调用,默认情况下它只返回该值。

>>> class HiddenMembers:
...     def __getattr__(self, name):
...         return "You don't get to see " + name
...
>>> h = HiddenMembers()
>>> h.anything
"You don't get to see anything"
__delattr__
[编辑 | 编辑源代码]

这个函数被调用来删除属性。

>>> class Permanent:
...     def __delattr__(self, name):
...         print(name, "cannot be deleted")
...
>>> p = Permanent()
>>> p.x = 9
>>> del p.x
x cannot be deleted
>>> p.x
9
属性覆盖函数
函数 间接形式 直接形式
__getattr__ getattr(A, B) A.B
__setattr__ setattr(A, B, C) A.B = C
__delattr__ delattr(A, B) del A.B

运算符重载

[编辑 | 编辑源代码]

运算符重载允许我们使用内置的 Python 语法和运算符来调用我们定义的函数。

二元运算符
[编辑 | 编辑源代码]

如果一个类具有 __add__ 函数,我们可以使用 '+' 运算符来添加类的实例。这将调用 __add__ 函数,并将类的两个实例作为参数传递给它,返回值将是加法的结果。

>>> class FakeNumber:
...     n = 5
...     def __add__(A,B):
...         return A.n + B.n
...
>>> c = FakeNumber()
>>> d = FakeNumber()
>>> d.n = 7
>>> c + d
12

要覆盖增强赋值运算符,只需在正常的二元运算符前面添加 'i' 即可,例如,对于 '+=',使用 '__iadd__' 代替 '__add__'。该函数将被赋予一个参数,该参数将是增强赋值运算符右侧的对象。该函数的返回值将被赋值给运算符左侧的对象。

>>> c.__imul__ = lambda B: B.n - 6
>>> c *= d
>>> c
1

需要注意的是,如果增强运算符函数没有被直接设置,增强赋值运算符也会使用正常的运算符函数。这将按预期工作,"__add__" 将被调用用于 "+=" 等等。

>>> c = FakeNumber()
>>> c += d
>>> c
12
二元运算符重载函数
函数 运算符
__add__ A + B
__sub__ A - B
__mul__ A * B
__truediv__ A / B
__floordiv__ A // B
__mod__ A % B
__pow__ A ** B
__and__ A & B
__or__ A | B
__xor__ A ^ B
__eq__ A == B
__ne__ A != B
__gt__ A > B
__lt__ A < B
__ge__ A >= B
__le__ A <= B
__lshift__ A << B
__rshift__ A >> B
__contains__ A in B
A not in B
一元运算符
[编辑 | 编辑源代码]

一元运算符将简单地传递它们被调用的类的实例。

>>> FakeNumber.__neg__ = lambda A : A.n + 6
>>> -d
13
一元运算符重载函数
函数 运算符
__pos__ +A
__neg__ -A
__inv__ ~A
__abs__ abs(A)
__len__ len(A)
项目运算符
[编辑 | 编辑源代码]

在 Python 中,还可以覆盖索引和切片运算符。这让我们可以使用 class[i] 和 class[a:b] 语法在自己的对象上。

项目运算符最简单的形式是 __getitem__。它将类的实例和索引的值作为参数。

>>> class FakeList:
...     def __getitem__(self,index):
...         return index * 2
...
>>> f = FakeList()
>>> f['a']
'aa'

我们还可以为与将值赋值给项目相关的语法定义一个函数。除了 __getitem__ 的参数外,该函数的参数还包括要赋值的值。

>>> class FakeList:
...     def __setitem__(self,index,value):
...         self.string = index + " is now " + value
...
>>> f = FakeList()
>>> f['a'] = 'gone'
>>> f.string
'a is now gone'

我们也可以对切片做同样的事情。同样,每个语法都有不同的参数列表与之关联。

>>> class FakeList:
...     def __getslice___(self,start,end):
...         return str(start) + " to " + str(end)
...
>>> f = FakeList()
>>> f[1:4]
'1 to 4'

请记住,切片语法中的起始和结束参数可以为空。在这种情况下,Python 对起始和结束都有默认值,如下所示。

>> f[:]
'0 to 2147483647'

请注意,此处显示的切片结束的默认值只是 32 位系统上的最大可能的有符号整数,并且可能根据您的系统和 C 编译器而有所不同。

  • __setslice__ 的参数是 (self,start,end,value)

我们还有用于删除项目和切片的运算符。

  • __delitem__ 的参数是 (self,index)
  • __delslice__ 的参数是 (self,start,end)

请注意,这些与 __getitem__ 和 __getslice__ 相同。

项目运算符重载函数
函数 运算符
__getitem__ C[i]
__setitem__ C[i] = v
__delitem__ del C[i]
__getslice__ C[s:e]
__setslice__ C[s:e] = v
__delslice__ del C[s:e]

其他重载

[编辑 | 编辑源代码]
其他重载函数
函数 运算符
__cmp__ cmp(x, y)
__hash__ hash(x)
__nonzero__ bool(x)
__call__ f(x)
__iter__ iter(x)
__reversed__ reversed(x) (2.6+)
__divmod__ divmod(x, y)
__int__ int(x)
__long__ long(x)
__float__ float(x)
__complex__ complex(x)
__hex__ hex(x)
__oct__ oct(x)
__index__
__copy__ copy.copy(x)
__deepcopy__ copy.deepcopy(x)
__sizeof__ sys.getsizeof(x) (2.6+)
__trunc__ math.trunc(x) (2.6+)
__format__ format(x, ...) (2.6+)

编程实践

[编辑 | 编辑源代码]

Python 类的灵活性意味着类可以采用各种行为。但是,为了易于理解,最好谨慎使用 Python 的许多工具。尝试在类定义中声明所有方法,并始终使用 <class>.<member> 语法,而不是 __dict__,只要有可能。查看C++Java 中的类,看看大多数程序员会从类中期待什么。

由于 Python 类中所有 Python 成员都可以被类外部的函数/方法访问,因此除了覆盖 __getattr__、__setattr__ 和 __delattr__ 之外,没有办法强制封装。但是,一般做法是,类的创建者或模块的创建者只需要相信用户只会使用预期的接口,并且为了那些确实需要访问该模块的用户,避免限制对该模块工作机制的访问。当使用类或模块的预期接口以外的部分时,请记住这些部分可能会在模块的后续版本中发生变化,您甚至可能会在模块中导致错误或未定义的行为,因为封装是私有的。

文档字符串

[编辑 | 编辑源代码]

定义类时,习惯上使用类定义开头的字符串字面量来记录类。然后,该字符串将被放置在类定义的 __doc__ 属性中。

>>> class Documented:
...     """This is a docstring"""
...     def explode(self):
...         """
...         This method is documented, too! The coder is really serious about
...         making this class usable by others who don't know the code as well
...         as he does.
...
...         """
...         print("boom")
>>> d = Documented()
>>> d.__doc__
'This is a docstring'

文档字符串是一种非常有用的记录代码的方法。即使您从未编写过任何单独的文档(承认吧,对于许多程序员来说,这样做是优先级最低的事情),在您的类中包含信息丰富的文档字符串将极大地提高它们的可用性。

存在一些将 Python 代码中的文档字符串转换为可读 API 文档的工具,例如EpyDoc

也不要仅仅停留在记录类定义上。类中的每个方法也应该有自己的文档字符串。请注意,上述示例类 Documented 中的方法 explode 的文档字符串有一个相当长的跨越多行的文档字符串。它的格式符合 Python 创建者 Guido van Rossum 在PEP 8 中的风格建议。

在运行时添加方法

[编辑 | 编辑源代码]
到一个类
[编辑 | 编辑源代码]

在运行时向类添加方法非常容易。假设我们有一个名为Spam的类和一个函数cook。我们希望能够在Spam类的所有实例上使用函数cook。

class Spam:
  def __init__(self):
    self.myeggs = 5

def cook(self):
  print("cooking %s eggs" % self.myeggs)

Spam.cook = cook   #add the function to the class Spam
eggs = Spam()      #NOW create a new instance of Spam
eggs.cook()        #and we are ready to cook!

这将输出

cooking 5 eggs
到一个类的实例
[编辑 | 编辑源代码]

向已经创建的类的实例添加方法有点棘手。再次假设我们有一个名为Spam的类,并且我们已经创建了eggs。但是我们发现我们想烹饪这些鸡蛋,但我们不想创建一个新的实例,而是使用已经创建的那个。

class Spam:
  def __init__(self):
    self.myeggs = 5

eggs = Spam()

def cook(self):
  print("cooking %s eggs" % self.myeggs)

import types
f = types.MethodType(cook, eggs, Spam)
eggs.cook = f

eggs.cook()

现在我们可以烹饪我们的鸡蛋,最后一条语句将输出

cooking 5 eggs
使用一个函数
[编辑 | 编辑源代码]

我们还可以编写一个函数,使向类的实例添加方法的过程更容易。

def attach_method(fxn, instance, myclass):
  f = types.MethodType(fxn, instance, myclass)
  setattr(instance, fxn.__name__, f)

我们现在只需要调用attach_method,并将要附加的函数、要附加到的实例以及实例派生的类作为参数传递。因此我们的函数调用可能如下所示

attach_method(cook, eggs, Spam)

注意在函数add_method中我们不能写instance.fxn = f因为这会将一个名为fxn的函数添加到实例中。

[编辑 | 编辑源代码]
华夏公益教科书