class 是 Python 语句class 语句是对象的创建者并且是一个隐含的赋值运算——执行时,它会产生类对象,并把引用值存储在前面所使用的变量名。

存储数据#

使用类来管理数据:

class SharedData:
    spam = 42    # 由所有实例共享
# 创建两个实例
x = SharedData()
y = SharedData()
x.spam, y.spam
(42, 42)
x.spam = 7     # 修改实例 x
x.spam, y.spam   # y 没有被修改
(7, 42)
SharedData.spam = 45 # 通过类修改数据
x.spam, y.spam   # y 被修改
(7, 45)

如果想要同时修改 xy,你需要这样:

SharedData.spam = 45
x = SharedData()
y = SharedData()
x.spam, y.spam   # x, y 均被修改
(45, 45)
id(x) == id(y)
False
x.spam = 7     # 修改实例 x
x.spam, y.spam   # y 没有被修改
(7, 45)

命名空间#

  • 无点号运算的变量名(如,X)与作用域相对应

  • 点号的属性名(例如,object.X)使用的是对象的命名空间

  • 有些作用域会对对象的命名空间进行初始化(模块和类)

类与模块#

  • 模块

    • 是数据/逻辑包

    • 通过编写 Python 文件或 C 扩展来创建

    • 通过导入来使用

    • 实现新的对象

    • class 语句创建

    • 通过调用来使用

    • 总是位于一个模块中

运算符重载#

运算符重载只是意味着在类方法中拦截内置的操作——当类的实例出现在内置操作中,Python 自动调用你的方法,并且你的方法的返回值变成了相应操作的结果。

  • 运算符重载允许类拦截常规的 Python 操作。

  • 类可以重载所有 Python 表达式运算符。

  • 类还可以重载内置操作, 如打印、函数调用、属性访问等.

  • 重载使类实例的作用更类似于内置类型。

  • 通过在类中提供特殊命名的方法来实现重载

Constructors and Expressions: __init__ and __sub__#

作为审查, 请考虑以下简单示例: 它的 Number 类, 编码在文件 number.py 中, 提供一种方法来拦截实例构造 (__init__), 以及一个用于捕获减法表达式 (__sub__)。特殊的方法, 如这些是挂钩, 让您连接到内置的操作:

# File number.py
class Number:
    def __init__(self, start):  # On Number(start)
        self.data = start

    def __sub__(self, other):  # On instance - other
        return Number(self.data - other)  # Result is a new instance
from number import Number  # Fetch class from module
X = Number(5)  # Number.__init__(X, 5)
Y = X - 2  # Number.__sub__(X, 2)
Y.data  # Y is new Number instance
3

常用的运算符重载方法#

方法

重载

调用

__init__

Constructor,构造器

建立对象: X = Class(args)

__del__

Destructor,析构器

Object reclamation(回收) of X

__add__

+

X + Y, X += Y if no __iadd__

__or__

\(\vert\) (bitwise OR)

X \(\vert\) Y, X \(\vert\)= Y if no __ior__

__repr__, __str__

Printing, conversions(转换)

print(X), repr(X), str(X)

__call__

Function calls(函数调用)

X(*args, **kargs)

__getattr__

Attribute fetch(提取特征或属性)

X.undefined

__setattr__

Attribute assignment,属性赋值

X.any = value

__delattr__

属性删除

del X.any

__getattribute__

Attribute fetch

X.any

__getitem__

Indexing, slicing, iteration

X[key], X[i:j], for loops and other iterations if no __iter__

__setitem__

Index and slice assignment

X[key] = value, X[i:j] = iterable

__delitem__

Index and slice deletion

del X[key], del X[i:j]

__len__

长度

len(X), truth tests(真值测试) if no __bool__

__bool__

Boolean tests

bool(X)

__lt__, __gt__, __le__, __ge__, __eq__, __ne__

Comparisons

X < Y, X > Y, X <= Y, X >= Y, X == Y, X != Y

__radd__

Right-side operators,右侧加法

Other + X

__iadd__

In-place augmented operators,实地(增强的)加法

X += Y (or else __add__)

__iter__, __next__

Iteration contexts

I=iter(X), next(I); for loops, in if no __con tains__, all comprehensions, map(F,X), others

__contains__

Membership test,成员关系测试

item in X (any iterable)

__index__

Integer value

hex(X), bin(X), oct(X), O[X], O[X:]

__enter__, __exit__

Context manager

with obj as var:

__get__, __set__, __delete__

Descriptor attributes

X.attr, X.attr = value, del X.attr

__new__

Creation

Object creation, before __init__

Indexing and Slicing: __getitem__ and __setitem__#

索引__getitem__)和切片__setitem__):当实例 X 出现 X[i] 这样的索引运算时,Python 会调用这个实例继承的 __getitem__ 方法(如果有的话),把 X 当作第一个参数传递,并且方括号内的索引值传递给第二个参数。

class Indexer:
    def __getitem__(self, index):
        return index ** 2
X = Indexer()
X[2]
4
for i in range(5):
    print(X[i], end=' ')
0 1 4 9 16 

拦截分片#

__getitem__ 也实现分片操作:

L = [5, 6, 7, 8, 9] 
L[2:4]
[7, 8]
L[:-2]
[5, 6, 7]
L[::2]
[5, 7, 9]
L[slice(2, 4)]
[7, 8]
L[slice(1, None)]
[6, 7, 8, 9]
L[slice(None,-2)]
[5, 6, 7]
L[slice(None, None, 2)]
[5, 7, 9]

重载:

class Indexer:
    data = [5, 6, 7, 8, 9]

    def __getitem__(self, index):  # Called for index or slice
        print('getitem:', index)
        return self.data[index]  # Perform index or slice
X = Indexer() 
X[0]
getitem: 0





5
X[1]
getitem: 1





6
X[-1]
getitem: -1





9
X[2:4]
getitem: slice(2, 4, None)





[7, 8]
X[::2]
getitem: slice(None, None, 2)





[5, 7, 9]

If used, the __setitem__ index assignment method similarly intercepts both index and slice assignments it receives a slice object for the latter, which may be passed along in another index assignment or used directly in the same way:

class IndexSetter:    
    def __setitem__(self, index, value):    # Intercept index or slice assignment       
        ...        
        self.data[index] = value            # Assign index or slice 

In fact,__getitem__ may be called automatically in even more contexts than indexing and slicing—it’s also an iteration fallback option, as we’ll see in a moment.

Index Iteration: __getitem__#

class StepperIndex:
    def __getitem__(self, i): 
        return self.data[i]
X = StepperIndex()
X.data = 'Spam'
X[1]
'p'
for item in X:
    print(item, end=' ')
S p a m 
'p' in X  # All call __getitem__ too
True
[c for c in X]                    # List comprehension
['S', 'p', 'a', 'm']
list(map(str.upper, X))           # map calls
['S', 'P', 'A', 'M']
(a, b, c, d) = X  # Sequence assignments
a, c, d 
('S', 'a', 'm')
list(X), tuple(X), ''.join(X)     # And so on... 
(['S', 'p', 'a', 'm'], ('S', 'p', 'a', 'm'), 'Spam')
X
<__main__.StepperIndex at 0x1c0ef8b3a20>

Iterable Objects: __iter__ and __next__#

Python 中的所有迭代 context 都将先尝试 __iter__ 方法, 然后再尝试 __getitem__

# File squares.py
class Squares:
    def __init__(self, start, stop):    # Save state when created
        self.value = start - 1
        self.stop = stop

    def __iter__(self):                 # Get iterator object on iter
        return self

    def __next__(self):                 # Return a square on each iteration
        if self.value == self.stop:     # Also called by next built-in
            raise StopIteration
        self.value += 1
        return self.value ** 2
for i in Squares(1, 5):
    print(i, end=' ')
1 4 9 16 25 

下面实现了跳跃迭代:

# File skipper.py
class SkipObject:
    def __init__(self, wrapped):  # Save item to be used
        self.wrapped = wrapped

    def __iter__(self):
        return SkipIterator(self.wrapped)  # New iterator each time


class SkipIterator:
    def __init__(self, wrapped):
        self.wrapped = wrapped  # Iterator state information
        self.offset = 0

    def __next__(self):
        if self.offset >= len(self.wrapped):  # Terminate iterations
            raise StopIteration
        else:
            item = self.wrapped[self.offset]  # else return and skip
            self.offset += 2
            return item


if __name__ == '__main__':
    alpha = 'abcdef'
    skipper = SkipObject(alpha)  # Make container object
    I = iter(skipper)  # Make an iterator on it
    print(next(I), next(I), next(I))  # Visit offsets 0, 2, 4
    for x in skipper:  # for calls __iter__ automatically
        for y in skipper:  # Nested fors call __iter__ again each time
            print(x + y, end=' ')  # Each iterator has its own state, offset
a c e
aa ac ae ca cc ce ea ec ee 

Coding Alternative: __iter__ plus yield#

由于生成器函数会自动保存局部变量状态并创建所需的迭代器方法, 因此它们非常适合最小化用户定义的自定义迭代器的编码要求, 并补充了从类中获取的状态保留和其他实用程序。

As a review, recall that any function that contains a yield statement is turned into a generator function. When called, it returns a new generator object with automatic retention of local scope and code position, an automatically created __iter__ method that simply returns itself, and an automatically created __next__ method that starts the function or resumes it where it last left off:

def gen(x): 
    for i in range(x): 
        yield i ** 2
G = gen(5)  # Create a generator with __iter__ and __next__
G.__iter__() == G        # Both methods exist on the same object 
True
I = iter(G)
next(I), next(I) 
(0, 1)
list(gen(5))
[0, 1, 4, 9, 16]

即使具有yield 的生成器函数恰好是名为 __iter__ 的方法, 这仍然是正确的: 每当迭代上下文工具调用时, 此类方法将返回具有必需 __next__ 的新生成器对象。作为额外的奖励, 在类中编码为方法的生成器函数可以访问实例属性和局部范围变量中的已保存状态。

# File squares_yield.py
class Squares:  # __iter__ + yield generator
    def __init__(self, start, stop):  # __next__ is automatic/implied
        self.start = start
        self.stop = stop

    def __iter__(self):
        for value in range(self.start, self.stop + 1):
            yield value**2
for i in Squares(1, 5):
    print(i, end=' ')
1 4 9 16 25 
S = Squares(1, 5)  # Runs __init__: class saves instance state
S
<__main__.Squares at 0x1c0ff230160>
I = iter(S)                # Runs __iter__: returns a generator 
next(I), next(I),  next(I), next(I), next(I) 
(1, 4, 9, 16, 25)
next(I) 
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-88-cba4a91f39e1> in <module>()
----> 1 next(I)


StopIteration: 

Membership: __contains__, __iter__, and __getitem__#

在迭代域中, 类可以使用 __iter____getitem__ 方法实现 in 成员资格运算符作为迭代。不过, 为了支持更具体的成员身份, 类可以编写 __contains__ 方法——当存在时, 此方法优先于 __iter__, __iter__ 优于 __getitem____contains__ 方法应将成员身份定义为应用于映射的键 (并且可以使用快速查找), 并作为对序列的搜索。

class Iters:
    def __init__(self, value):
        self.data = value

    def __getitem__(self, i):  # Fallback for iteration
        print('get[%s]:' % i, end='')  # Also for index, slice
        return self.data[i]

    def __next__(self):
        print('next:', end='')
        if self.ix == len(self.data):
            raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item


if __name__ == '__main__':
    X = Iters([1, 2, 3, 4, 5])  # Make instance
    print(3 in X)  # Membership
    for i in X:  # for loops
        print(i, end=' | ')
    print()
    # Other iteration contexts    print( list(map(bin, X)) )
    print([i**2 for i in X])
    # Manual iteration (what other contexts do)
    I = iter(X)
    while True:
        try:
            print(next(I), end=' @ ')
        except StopIteration:
            break
get[0]:get[1]:get[2]:True
get[0]:1 | get[1]:2 | get[2]:3 | get[3]:4 | get[4]:5 | get[5]:
get[0]:get[1]:get[2]:get[3]:get[4]:get[5]:[1, 4, 9, 16, 25]
get[0]:1 @ get[1]:2 @ get[2]:3 @ get[3]:4 @ get[4]:5 @ get[5]:

可以看出上面的显示很乱,为此我们重载 __iter__ 方法:

class Iters1(Iters):
    def __iter__(self):  # Preferred for iteration
        print('iter=> ', end='')  # Allows only one active iterator
        self.ix = 0
        return self
    
if __name__ == '__main__':
    X = Iters1([1, 2, 3, 4, 5])  # Make instance
    print(3 in X)  # Membership
    for i in X:  # for loops
        print(i, end=' | ')
    print()
    # Other iteration contexts    print( list(map(bin, X)) )
    print([i**2 for i in X])
    # Manual iteration (what other contexts do)
    I = iter(X)
    while True:
        try:
            print(next(I), end=' @ ')
        except StopIteration:
            break
iter=> next:next:next:True
iter=> next:1 | next:2 | next:3 | next:4 | next:5 | next:
iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25]
iter=> next:1 @ next:2 @ next:3 @ next:4 @ next:5 @ next:

还可以进一步的改进:

class Iters2(Iters1):
    def __contains__(self, x):  # Preferred for 'in'
        print('contains: ', end='')
        return x in self.data


if __name__ == '__main__':
    X = Iters2([1, 2, 3, 4, 5])  # Make instance
    print(3 in X)  # Membership
    for i in X:  # for loops
        print(i, end=' | ')
    print()
    # Other iteration contexts    print( list(map(bin, X)) )
    print([i**2 for i in X])
    # Manual iteration (what other contexts do)
    I = iter(X)
    while True:
        try:
            print(next(I), end=' @ ')
        except StopIteration:
            break
contains: True
iter=> next:1 | next:2 | next:3 | next:4 | next:5 | next:
iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25]
iter=> next:1 @ next:2 @ next:3 @ next:4 @ next:5 @ next:

最后我们将其合并为:

class Iters:
    def __init__(self, value):
        self.data = value

    def __getitem__(self, i):  # Fallback for iteration
        print('get[%s]:' % i, end='')  # Also for index, slice
        return self.data[i]

    def __iter__(self):  # Preferred for iteration
        print('iter=> ', end='')  # Allows only one active iterator
        self.ix = 0
        return self

    def __next__(self):
        print('next:', end='')
        if self.ix == len(self.data):
            raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item

    def __contains__(self, x):  # Preferred for 'in'
        print('contains: ', end='')
        return x in self.data


if __name__ == '__main__':
    X = Iters([1, 2, 3, 4, 5])  # Make instance
    print(3 in X)  # Membership
    for i in X:  # for loops
        print(i, end=' | ')
    print()
    # Other iteration contexts    print( list(map(bin, X)) )
    print([i**2 for i in X])
    # Manual iteration (what other contexts do)
    I = iter(X)
    while True:
        try:
            print(next(I), end=' @ ')
        except StopIteration:
            break
contains: True
iter=> next:1 | next:2 | next:3 | next:4 | next:5 | next:
iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25]
iter=> next:1 @ next:2 @ next:3 @ next:4 @ next:5 @ next:

正如我们所看到的, __getitem__ 方法更通用: 除了迭代, 它还截获显式索引以及切片。切片表达式使用包含边界的切片对象触发 __getitem__, 无论是内置类型还是用户定义类, 因此切片在我们的类中是自动的:

X = Iters('spam')
X[0]
get[0]:




's'
'spam'[1:]
'pam'
'spam'[slice(1, None)] 
'pam'
X[1:] 
get[slice(1, None, None)]:




'pam'
X[:-1] 
get[slice(None, -1, None)]:




'spa'
list(X) 
iter=> next:next:next:next:next:




['s', 'p', 'a', 'm']

Attribute Access: __getattr__ and __setattr__#

__getattr__ 方法是拦截属性点号运算的。具体地说, 对于从类创建的对象, 点运算符表达式对象. 属性也可以由代码实现, 用于引用、赋值和删除上下文。具体地说, 对于从类创建的对象, 点运算符表达式 object.attribute 也可以由代码实现, 用于引用、赋值和删除上下文。

class Empty:
        def __getattr__(self, attrname):           # On self.undefined
            if attrname == 'age':
                return 40
            else:
                raise AttributeError(attrname)
X = Empty()
X.age
40

age 变成了一个 dynamically computed attribute(动态计算的属性)—its value is formed by running code, not fetching an object.

  • The __getattribute__ method intercepts all attribute fetches, not just those that are undefined, but when using it you must be more cautious than with __get attr__ to avoid loops.

  • The property built-in function allows us to associate methods with fetch and set operations on a specific class attribute.

  • Descriptors provide a protocol for associating __get__ and __set__ methods of a class with accesses to a specific class attribute. - Slots attributes are declared in classes but create implicit storage in each instance.

Call 表达式: __call__#

Python 为应用于您的实例的函数调用表达式运行 __call__ 方法,这样可以让类实例类似于函数。

class Callee:
    def __call__(self, *pargs, **kargs):  # Intercept instance calls
        print('Called:', pargs, kargs)  # Accept arbitrary arguments
C = Callee()
C(1, 2, 3)
Called: (1, 2, 3) {}
C(1, 2, 3, x=4, y=5)
Called: (1, 2, 3) {'x': 4, 'y': 5}

更正式的说,所有的参数传递方式,__call__ 方法都支持:

class C:
    def __call__(self, a, b, c=5, d=6): ...        # Normals and defaults


class C:
    def __call__(self, *pargs, **
                 kargs): ...       # Collect arbitrary arguments


class C:
    def __call__(self, *pargs, d=6, **kargs): ...  # keyword-only argument

都匹配如下调用方式:

X = C()
X(1, 2)  # Omit defaults
X(1, 2, 3, 4)  # Positionals
X(a=1, b=2, d=4)  # Keywords
X(*[1, 2], **dict(c=3, d=4))  # Unpack arbitrary arguments
X(1, *(2, ), c=3, **dict(d=4))  # Mixed modes