《Python基础教程》读书笔记09-魔法方法、属性和迭代器

构造方法

一个对象被创建后,会立即调用构造方法。

class FooBar:
    def __init__(self): # 也可以有参数
        self.somevar = 42

>>> f = FooBar()
>>> f.somevar
42

super 函数

class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print ('Aaaah...')
            self.hungry = False
        else:
            print ('No, thanks!')

class SongBird(Bird):
    def __init__(self):
        super(SongBird, self).__init__() # Python3 可以 super().__init__(),super 函数不带任何参数调用
        # 在老版本中没有 super 函数,可以 Bird.__init__(self),新式类中就不要这么做了,因为如果继承了多个类
        self.sound = 'Squawk!'
    def sing(self):
        print (self.sound)

>>> sb = SongBird()
>>> sb.sing()
Squawk!
>>> sb.eat()
Aaaah...

基本的序列和映射规则

如果对象是不可变,只有两个方法,可变 4 个方法

  1. __len__(self):返回集合中的元素数,如果 __len__ 返回 0(而且没重写该行为的 __nonzero__),对象会被当作一个布尔变量中的假值。
  2. __getitem__(self, key):返回所给键对应的值。
  3. __setitem__(self, key, value)
  4. __delitem__(self, key):在对象使用 del 语句时被调用,同时必修删除和键相关的键。

对于这些方法的附加要求:

  • 对于一个序列,如果键是负数,那么要从末尾开始计数,即 x[-n]x[len(x)-n] 是一样的
  • 如果键是不合适的类型(如:序列使用字符串作为键),会引发一个 TypeError 异常。
  • 如果索引超出了范围,引发 IndexError 异常。

子类化列表,字典和字符串

标准库有3个关于序列和映射规则(UserList、UserString 和 UserDict)可以立即使用的实现

也可以子类化内建类型(list、str 和 dict)

属性

通过访问器定义的特性被称为属性

class Rectangle:
    def __init__(self):
        self.width = 0
        self.height = 0
    def setSize(self, size):
        self.width, self.height = size
    def getSize(self):
        return self.width, self.height

>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.getSize()
(10, 5)
>>> r.setSize((150, 100))
>>> r.width
150

上面的例子中,如果 size 需要称为真正的特性,并不修改 getSize 和 setSize 方法

property 函数

只需要添加一句话

size = property(getSize, setSize)

size 特性仍然取决于 getSize 和 setSize 中的计算,但他们看起来就像普通的属性一样。

property 函数有四个参数(fget, fset, fdel, doc)

  • 没有参数,产生的属性不可读不可写
  • 只有一个参数调用(一个取值的方法),产生的属性只读
  • 只有第二个参数(用关键字参数),属性只写
  • 第三个参数是一个用于删除的特性的方法(这个方法不要参数)
  • 第四个参数是一个文档字符串

实际上,property 函数不是一个真正的函数,它是其实例拥有很多特殊方法的类。__get__, __set____delete__,这3个方法合在一起,就定义了描述符的规则。实现了其中任何一个方法的对象就叫描述符。描述符的特殊之处在于它们如何被访问的,如,程序读取一个特性时(尤其是在实例中访问该特性,但该特性在类中定义时),如果该特性被绑定到实现了 __get__ 方法的对象上,那么就会调用 __get__ 方法(结果值也会被返回)。

静态方法和类成员方法

静态方法 没有 self 参数

类成员方法 需要有 cls 参数,可以由类的对象调用,但 cls 参数是自动被绑定到类的

class MyClass:
    @staticmethod
    def smeth():
        print ("静态方法")
    # smeth = staticmethod(smeth)  # 也可以这样

    @classmethod
    def cmeth(cls):
        print ("类成员方法", cls)
    # cmeth = classmethod(cmeth)   # 也可以这样

>>> MyClass.smeth()
静态方法
>>> MyClass.cmeth()
类成员方法 <class '__main__.MyClass'>
>>> x = MyClass()
>>> x.smeth() # 对象也可以调用
静态方法
>>> x.cmeth()
类成员方法 <class '__main__.MyClass'>

拦截对象的所有特性访问

  • __getattribute__(self, name):当特性 name 被访问时自动被调用
  • __getattr__(self, name):当特性 name 被访问且对象没有相应的特性时被调用
  • __setattr__(self, name, value):当试图给特性 name 赋值时会被自动调用
  • __delattr__(self, name):当试图删除特性 name 时被自动调用
>>> class Rectangle:
    def __init__(self):
        self.width = 0
        self.height = 0
    def __setattr__(self, name, value):
        if name == 'size':
            self.width, self.height = value
        else:
            self.__dict__[name] = value # __dict__方法包含一个字典,是所有实例的属性
    def __getattr__(self, name):
        if name == 'size':
            return self.width, self.height
        else:
            raise AttributeError

>>> r = Rectangle()
>>> r.size
(0, 0)
>>> r.width = 100
>>> r.height = 200
>>> r.size
(100, 200)
>>> r.size = (10,20)
>>> r.width
10
  • __setattr__ 方法在所涉及的特性不是 size 时也会被调用。
  • __getattr__ 方法只在普通的特性没有被找到的时候调用,可以使用 hasattr,getattr 函数找到 size 属性。
  • __getattribute__ 方法会拦截所有特性的访问,也拦截 __dict__ 的访问。访问 __getattribute__ 中与 self 相关的特性时,使用超类的 __getattribute__ 方法(super 函数)是唯一安全的途径。

迭代器

__iter__ 方法是迭代器规则的基础,返回一个迭代器(iterator),所谓的迭代器就是具有 next 方法(不需要参数)的对象。在调用 next 方法时,迭代器会返回它的下一个值。

Python3.0,迭代器应该实现 __next__ 方法,而不是 next。而新的内建函数 next 可以访问这个方法,即:next(it) 等同于之前的版本中的 it.next()

一个实现了 __iter__ 方法的对象是可迭代的,一个实现了 next(__next__) 方法的对象则是迭代器。

>>> class Fibs:
    def __init__(self):
        self.a = 0
        self.b = 1
    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        return self.a
    def __iter__(self):
        return self

>>> fibs = Fibs()
>>> for f in fibs:
    if f > 1000:
        print (f)
        break

1597

内建函数 iter

可以从可迭代的对象中获得迭代器

>>> it = iter([1, 2, 3])
>>> next(it) #Pyhton2.7  it.next()
1

从迭代器得到序列

使用 list 构造方法将迭代器转化为列表

>>> class TextIterator:
    value = 0
    def __next__(self):
        self.value += 1
        if self.value > 10: raise StopIteration
        return self.value
    def __iter__(self):
        return self

>>> t = TextIterator()
>>> list(t)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

生成器 generator

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)]  # list
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))  # generator
>>> g
<generator object <genexpr> at 0x1022ef630>

# 可以通过next()函数获得generator的下一个返回值:
>>> next(g)
0
...
# 直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的错误。
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

按顺序打印列表中的数字 nested = [[1, 2], [3, 4], [5]]

>>> def flatten(nested):
    for sublist in nested:
        for element in sublist:
            yield element
    return 123  # for 循环,无法获得该值

>>> nested = [[1, 2], [3, 4], [5]]
>>> for num in flatten(nested):
    print (num)

1
2
3
4
5
>>> list(flatten(nested))
[1, 2, 3, 4, 5]

用 for 循环调用 generator 时,发现拿不到 generator 的 return 语句的返回值。如果想要拿到返回值,必须捕获 StopIteration 错误,返回值包含在 StopIterationvalue 中:

>>> a = flatten(nested)
>>> next(a)
1
>>> next(a)
2
>>> a.__next__()
3
>>> a.__next__()
4
>>> a.__next__()
5

>>> try:
        x = next(a)
    except StopIteration as e:
        print('Generator return value:', e.value)


Generator return value: 111

任何包含 yield 语句的函数称为生成器。每次产生一个值,函数就会被冻结:即函数停在那点等待被重新唤醒。

生成器推导式

>>> g = ((i+2)**2 for i in range(2,27))
>>> next(g) # Python2.7   g.next()
16

和列表推导式的不同,使用普通括号 (),这个例子还是列表推导式好,但如果生成大量的值,还是生成器推导式好。
在函数调用中,不用另外加一对括号 sum(i**2 for i in range(0,10))

递归生成器

如果要处理任意层的嵌套。

>>> def flatten(nested):
    try:
        # 不要迭代类似字符串的对象
        try: nested + '' # 这是检查一个对象是不是类似于字符串对象的最简单最快速的方法
        except TypeError: pass
        else: raise TypeError # 检查字符串 end
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError: # 如果对一个数字进行迭代
        yield nested

>>> list(flatten([[[1],2],3,4,[5,[6,7]],8]))
[1, 2, 3, 4, 5, 6, 7, 8]

生成器方法

send 方法,生成器和外部进行交流的渠道。需要一个参数(需要发送的消息——任意对象)

在内部挂起生成器(yield 方法执行一次),yield 作为表达式而不是语句使用。

即:当生成器重新运行的时候,yield 方法返回一个值,也就是外部通过 send 方法发送的值,如果 next 方法被使用,那么 yield 方法返回 None

>>> def re(value):
    while True:
        new = (yield value) # 安全起见,加上括号
        if new is not None:
            print ('yield--Send')
            value = new

>>> r = re(42)
>>> r.next()
42
>>> r.next() # 因为while True,可以重复调用r.next(),yield方法返回None
42
>>> r.send('hello, world!')
yield--Send
'hello, world!'
>>> nested = [[1, 2], [3]]
>>> def flatten(nested):
    for sublist in nested:
        for element in sublist:
            new = (yield element)
            if new is not None:
                print ('new = ' + new)
                # element = new # 这里不知道怎么可以让迭代器返回 new

>>> x = flatten(nested)
>>> x.next()
1
>>> x.send('test')
new = test
2
>>> x.send('test2')
new = test2
3
>>> x.send('test3')
new = test3

Traceback (most recent call last):
  File "<pyshell#113>", line 1, in <module>
    x.send('test5')
StopIteration

生成器的两个方法

  1. throw 方法,使用异常类型调用,还有可选值以及回溯对象,用于在生成器中引发一个异常(在 yield 表达式中)
  2. close 方法,调用时不用参数,用于停止生成器

模拟生成器

>>> def flatten(nested):
    result = []
    try:
        for sublist in nested:
            for element in flatten(sublist):
                result.append(element)
    except TypeError:
        result.append(nested)
    return result

八皇后问题

水平垂直对角线,只能有一个皇后,n 个皇后则棋盘 n*n

# 判断下一个的皇后位置会不会有冲突
def conflict(state, nextX): # nextX:X坐标;如果state[0] = 3,代表第一行的皇后在第四列
    nextY = len(state) # nextY:Y坐标
    for i in range(nextY):
        if abs(state[i] - nextX) in (0, nextY - i):
            return True
    return False

def queens(num=8, state=()):
    for pos in range(num):
        if not conflict(state, pos):
            if len(state) == num - 1: # 最后一个皇后位置
                yield (pos,)
            else:
                for result in queens(num, state + (pos,)):
                    yield (pos,) + result

>>> list(queens(4))
[(1, 3, 0, 2), (2, 0, 3, 1)]

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Python基础教程》读书笔记09-魔法方法、属性和迭代器

文章字数:2.8k

本文作者:Bin

发布时间:2017-01-01, 22:39:34

最后更新:2019-10-12, 16:11:24

原始链接:http://coolview.github.io/2017/01/01/Python%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B/%E3%80%8APython%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B009-%E9%AD%94%E6%B3%95%E6%96%B9%E6%B3%95%E3%80%81%E5%B1%9E%E6%80%A7%E5%92%8C%E8%BF%AD%E4%BB%A3%E5%99%A8/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录