Python 魔法方法 特性和迭代器


# coding = utf-8

###############################################################################

# 1.构造函数
# 构造函数就是初始化方法,不同普通方法,它在对象创建后会自动调用__init__
class FooBar:
    def __init__(self):
        self.somevar = 42

f = FooBar()
print(f.somevar)
print()

# 可给构造函数提供参数
class FooBar2:
    def __init__(self, value=42):
        self.somevar = value

f2 = FooBar2()
print(f2.somevar)
print()

f3 = FooBar2('Tana')
print(f3.somevar)
print()

# Python提供了魔法方法__del__,也称为析构函数,它在对象被销毁(作为垃圾被收集)前调用~

class FooBar3:
    def __init__(self, value=42):
        self.someval = value
    def __del__(self):
        print('销毁啦')

f4 = FooBar3('Joe')
f4 = None
print()


###############################################################################
# 1.1 重写普通方法和特殊的构造函数
# 每个类都有一个或多个超类,并从它们那里继承行为。对类B的实例调用方法(或访问其属性时),
# 如果找不到该方法或属性 将从其超类A中查找,

class A:
    def hello(self):
        print("Hello,I'm A")
class B(A):
    pass

a = A()
b = B()
a.hello() # Hello,I'm A
b.hello() # Hello,I'm A
print()

class B(A):
    def hello(self):
        print("Hello,I'm B")

a = A()
b = B()
a.hello() # Hello,I'm A
b.hello() # Hello,I'm B
print()

# 构造函数用于初始化新建对象的状态,而对于大多数子类而言,除了超类的初始化代码外,
# 还要有自己的初始化代码。虽然所有方法的重写机制都相同,但与普通方法重写相比,
# 重写构造函数时可能会遇到一个特别的问题:重写构造函数必须调用超类的构造函数,否则可能无法正确的初始化对象

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

b = Bird()
b.eat() # Aaaaa
b.eat() # No, thanks
print()

class SongBird(Bird):
    def __init__(self):
        self.sound = 'Squawk'
    def sing(self):
        print(self.sound)

sb = SongBird()
sb.sing()
print()

# sb.eat() #AttributeError: 'SongBird' object has no attribute 'hungry' 报错
# SongBird 没有属性hungry,因为在SongBird中重写了构造函数,但新的构造函数没有初始化属性hungry的代码
# 有两种方式可以解决:调用未关联的超类构造函数 或 使用函数super


# 1.2 调用未关联的超类构造函数
# 类名.__init__(self)

class SongBird(Bird):
    def __init__(self):
        Bird.__init__(self)
        self.sound = 'Squawk'
    def sing(self):
        print(self.sound)

sb = SongBird()
sb.eat()
print()

# 对实例调用方法时,方法的参数self会自动关联到实例(称为关联的方法)
# 但如果对类调用方法(例如Bird.__init__(self))时,由于没有实例与其关联,可随便设置self,这样的方法被称为未关联的


# 1.3 使用函数super
# super().__init__()
# 调用函数super时,会将当前类和当前实例作为参数。对其返回的对象调用方法时,调用的将是超类(不是当前类)的方法

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

class SongBird(Bird):
    def __init__(self):
        super().__init__()
        self.sound = 'Squawk'
    def sing(self):
        print(self.sound)

sb = SongBird()
sb.sing()
sb.eat()
print()

# 函数super返回的是一个super对象,这个对象将负责为你执行方法解析。
# 当你它的属性时,它将在所有的超类(以及超类的超类)中查找,直到找到指定的属性或引发AttributeError



###############################################################################
# 2. 元素访问
# 协议 协议通常是指规范行为的规则
# 在python中,多态仅仅基于对象的行为(而不是基于祖先,如属于哪个类或超类)
# 在其他语言中可能要求对象属于特定的类或实现了特定的接口。而Python通常只要求对象遵循特定的协议。
# 因此,要成为序列,只需遵循序列协议即可、


# 2.1 基本的序列和映射协议
# 序列和映射基本上是元素的集合,要实现他们的基本行为(协议),不可变对象需实现2个方法,可变对象需要实现4个方法
# ① __len__(self):这个方法应返回集合包含的项数,对序列而言就是元素个数,对字典而言就是键值对的个数
#    如果__len__返回零(且没有实现覆盖这种行为的__nonzero__),对象在布尔上下文中视为假(就像空列表 空元组 空字典)
# ② __getitem__(self,key):这个方法返回与指定键相关联的值。对序列而言,键就是0~n-1的整数(也可以为负数),
#    对字典而言,键可以是任意类型
# ③ __setitem__(self,name,value):这个方法应以与键相关联的方式储值。以便以后可以通过__getitem__来获取
#    仅当对象可变时,这个方法才需要实现
# ④ __delitem__(self,key): 这个方法在对对象的组成部分使用__del__语句时调用,应删除与key相关联的值
#    仅当对象可变且允许其删除时,这个方法才需要实现

# 对于这些方法,还有一些额外的要求
# ①对于序列,键可以为负数,应从末尾往前数,即x[-n] = x[len(x)-n]
# ②如果键的类型不合适(如对序列使用字符串键),可能引发TypeError
# ③对于序列,如果所用的键类型正确但不在允许范围内,应引发IndexError异常


# 自定义无穷序列
# 思考:
# ① 由于是无穷序列,键应该是非负整数,如果为负数,应引发IndexError 如果键不是整数应引发TypeError
# ② 由于是无穷序列,键相关的值应该是动态计算的 __getitem__  self.start + self.step * key
# ③ 应设置一个change字典去存储 修改后的值 __setitem__

# 检查键key是否正确
def check_index(key):
    """
    由于是无穷序列,键应该是非负整数,
    如果为负数,应引发IndexError
    如果键不是整数应引发TypeError

    """
    if not isinstance(key, int):raise TypeError
    if key < 0:raise IndexError


class ArithmeticSequence:
    def __init__(self, start=0, step=1):
        """
        :param start: 序列中的第一个值
        :param step: 两个相邻值得差
        changed 一个字典,存储修改后的值
        """
        self.start = start
        self.step = step
        self.changed = {}

    def __getitem__(self, key):
        '''

        从算数序列中获取一个元素

        '''

        check_index(key)

        try:
            return self.changed[key] # 如果没被修改过,key会不存在 会引发KeyError错误
        except KeyError:
            return self.start+key*self.step

    def __setitem__(self, key, value):
        '''

        修改序列中的元素

        '''
        check_index(key)

        self.changed[key] = value

s = ArithmeticSequence(1,2)
print(s[4])
s[4] = 2
print(s[4])
# print(s[-1])  #IndexError
# print(s['A']) #TypeError
print()


# 2.2 从list dict str派生

# 带访问计数器的列表

class CounterList(list):
    def __init__(self, *args):
        super().__init__(*args)
        self.counter = 0

    def __getitem__(self, item):
        self.counter +=1
        return super(CounterList, self).__getitem__(item)

# CounterList类继承于其超类list,CounterList没有重写的方法(如append extend index等)都可以直接调用
# 在两个被重写的方法中,使用super来调用超类的相应方法,并添加了必要的行为:
# 初始化属性counter(在__init__)和更新属性counter(在__getitem__中)

c1 = CounterList(range(10))
print(c1)
c1.reverse()
print(c1)
c1[4] + c1[2]
print(c1.counter)
print()


# 重写__getitem__并不能保证一定会捕捉用户的访问操作,因为还有其他访问列表的操作没有捕捉到,如通过方法pop

print(c1.pop())
print(c1.counter)
print()


###############################################################################

# 3. 特性
# 存取方法
# 如果访问给定属性时必须采取特定的措施,可以通过存取方法来封装状态变量(属性)

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

r = Rectangle()
r.width = 20
r.height =10
print(r.get_size())
print()

r.set_size((10, 20))
print(r.width)
print()


# get_size 和set_size 是假想属性的size的存取方法,这个属性是由width和height组成的元组。
# 但存在一个缺陷:如果要让size称为真正的属性(r.size),而width和height是动态计算出来的,
# 就需要提供width,height的存取方法 这个类就要重写
# 函数property可以隐藏存取方法



# 3.1 函数property

class Rectangle():
    def __init__(self):
        self.width = 0
        self.height = 0
    def set_size(self, size):
        self.width, self.height = size
    def get_size(self):
        return self.width, self.height
    size = property(get_size,set_size)

r = Rectangle()
r.width = 10
r.height =15
print(r.size)
print()

# 通过调用函数property,并将存取方法作参数(获取方法在前,设置方法在后)创建了一个特性,
# 然后将名称size关联到这个特性,这样就可以以同样的方法访问width height size了

# 函数property 有4个参数
# 如果没有指定任何参数,创建的特性既不可读也不可写,
# 如果只指定一个参数(获取方法),则创建的特性只可读
# 第三个参数是可选,指定用于删除属性的方法(这个方法不接受任何参数)
# 第四个参数也是可选,指定一个字符串文档。
# 这些参数分别名为fget fset fdel doc
# 如果你要创建一个只可写且带文档字符串的特性,可使用它们作为关键字参数来实现

class Rectangle:
    def __init__(self):
        self.width = 0
        self.height = 0
    def get_size(self):
        return self.width, self.height
    def doc(self):
        print("HHHHHHH")
    # 只可读 不可写
    size = property(fget= get_size, doc=doc)

r = Rectangle()
r.width =16
r.height =20
# r.size =20 # 报错 can't set attribute
print(r.size)
print()

# property 实际上并不是一个函数,而是一个类。它的实例包含一些魔法方法,而所有的魔法都是由这些方法完成的
# 这些魔法方法为__get__ __set__ 和__delete__,它们一起定义了所谓的描述符协议
# 只要对象实现了这些方法中的任何一个,它就是一个描述符。描述符的独特性在于它的访问方式
# 例如,读取属性(具体来说,是在实例中访问类定义的属性)时,如果它关联的是一个实现了__get__对象,
# 将不会返回这个对象,而是调用__get__方法并将结果返回


# 3.2 静态方法和类方法
# 静态方法:静态方法是包转在staticmethod类的对象中,静态方法没有参数self,可直接通过类来调用
# 类方法:类方法是包装在classmethod类的对象中,类方法包含一个类似于self的参数,通常被命名为cls。
#       对于类方法,也可通过对象来直接调用,但参数cls将直接关联到类

class MyClass:
    def smeth():
        print("this is a staticmethod")
    smeth = staticmethod(smeth)

    def cmeth(cls):
        print("this is a classmethod of",cls)
    cmeth = classmethod(cmeth)

c = MyClass()
c.smeth()
c.cmeth()
print()


class MyClass:
    @staticmethod
    def smeth():
        print("this is a staticmethod")


    @classmethod
    def cmeth(cls):
        print("this is a classmethod of",cls)


MyClass.smeth() # 方法调用 别忘了()
MyClass.cmeth() # 方法调用 别忘了()
print()



# 3.3 __getattr __setattr等方法
# 可以拦截对对象属性的访问企图
# __getattribute__(self, name):在属性被访问时自动调用(只适用于新式类)
# __getattr__(self, name):在属性被访问而对象没有这样的属性时自动调用
# __setattr__(self, name, value):试图给属性赋值时自动调用
# __delattr__(self, name):试图删除属性时自动调用

class Rectangle:
    def __init__(self):
        self.width = 0
        self.height = 0
    def __setattr__(self, key, value):
        if key == 'size':
            self.width, self.height = value
        else:
            self.__dict__[key] = value

    def __getattr__(self, item):
        if item == 'size':
            return self.width, self.height
        else:
            raise AttributeError


c = Rectangle()
# print(c.name) # 报错
c.name = 'Tana'
print(c.name) # 不报错
print()

# ① 即便涉及的属性不是size,也将调用__setattr__方法。因此这个方法必须考虑如下两种情况,
# 如果涉及的实行为size,就执行和之前一样的操作,否则,就使用魔法属性__dict__,
# __dict__是一个字典,其中包含所有的实例属性,之所以使用它而不是执行常规的赋值操作,
# 是为了避免再次调用__setattr__,导致无限循环
# ② 仅当没有找到指定的属性时,才会调用方法__getattr__。这意味着如果指定的名称不是size,这个方法将引发AttributeError
#  这在要让类能够正确的支持hasattr和getattr等内置函数时很重要

# 在编写方法__setattr__时要避免无限循环,编写__getattribute__时也要这样。由于它拦截对所有属性的访问(在新式类中)
# 因此将拦截对__dict__的访问。在__getattribute__中访问当前实例的属性时,
# 唯一安全的方式是使用超类的方法__getattribute__(super)





###############################################################################

# 4. 迭代器
# 4.1 迭代器协议
# 迭代(iterate)意味着重复多次。实现了方法__iter__ 的对象都可以进行迭代
# 方法__iter__返回一个迭代器,它是包含方法__next__的对象,而调用这个方法可不提供任何参数。
# 当你调用__next__时,迭代器返回下一个值。如果迭代器没有返回值,应引发StopIteration异常
# 可使用内置的便利函数next,在这种情况下,next(it)与it.__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

# 找出斐波那契数列中第一大于1000的值
fibs = Fibs()
for f in fibs:
    if f>1000:
        print(f)
        break
print()

# 通过对可迭代对象调用内置函数iter,可获得一个迭代器
it = iter([1,2,3,4])
print(next(it))
print(next(it))
print()


# 4.2 从迭代器创建序列
# 除了对迭代器和可迭代对象进行迭代之外,还可以把他们转成序列

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

ti = TestIterator()
print(list(ti))
print()

###############################################################################

# 5. 生成器
# 5.1 创建生成器

def flatten(nested):
    for sublist in nested:
        for element in sublist:
            yield element

# 包含yield语句的函数都称为生成器
# 生成器不是使用return返回一个值,而是可以生成多个值,每次一个。
# 每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待重新唤醒,
# 重新唤醒后,函数将从停止的地方开始继续执行

nested = [[1,2],[5,6,7]]
for num in flatten(nested):
    print(num)

print()

print(list(flatten(nested)))
print()

# 简单生成器
# 生成器推导(也叫生成器表达式)其工作原理和列表推导相同,
# 但不是创建一个列表(即不立即执行循环),而是返回一个生成器,让你能够逐步计算
# 生成器推导,推导完成后,就没有啦
g = ((i+2) ** 2 for i in range(2,27))
print(next(g))

# 计算生成器的所有值的和
from  functools import reduce
a = reduce(lambda x,y:x+y, g)
print(a)
print()

g = ((i+2) ** 2 for i in range(10,27))
print(list(g))
print()




# 5.2 递归式生成器
def flatten(nested):
    try:
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError:
        yield nested

print(list(flatten([[[1,2,4],[3,7],[8]],10])))

# 这个递归函数还有一个问题,如果nested是字符串或类似字符串的对象,它就属于序列,因此不会引发TypeError
# 但在函数flatten中,不应该对类似于字符串的对象进行迭代,主要原因有两个:首先,应该把类似于字符串的对象视为原子值
# 其次,对这样的对象迭代,将会导致无穷递归,因为字符串的第一元素是长度为1的字符串,
# 而长度为1的字符串的第一个元素也是字符串本身

def flatten(nested):
    try:
        # 不迭代类似于字符串的对象
        try:
            nested + ''
        except TypeError:
            pass
        else:
            raise TypeError

        for sublist in nested:
            for element in flatten(sublist):
                yield element

    except TypeError:
        yield nested

print(list(flatten(['foo',['bar',['baz']]])))
print()


# 5.3 通用生成器
# 生成器由两个单独部分组成:生成器的函数与生成器的迭代器
# 生成器的函数是有def语句定义的,其中包含yield
# 生成器的迭代器是这个函数返回的结果。
# 用不太正确的话说,这两个实体通常被视为一个,通称为生成器

# 5.4 生成器方法
# 在生成器开始运行后,可使用生成器和外部之间的通信渠道向它提供值,这个通信渠道包含两个端点:
# ① 外部世界:外部世界可访问生成器的方法send,这个方法类似于next,但接受一个参数(要发出的消息,可以是任何对象)
# ② 生成器:在挂起的生成器内部,yield可能用作表达式而不是语句,
# 换而言之,当生成器重新运行时,yield返回一个值---通过send从外部世界发送的值。如果使用的是next,yield将返回None
# 仅当生成器被挂起(即遇到第一个yield)后,使用send(而不是next)才有意义,
# 要在此之前向生成器提供消息,可使用生成器的函数的参数

def repeator(value):
    while True:
        new = (yield value)
        if new is not None:value =new

r = repeator(42)
print(next(r))
print(r.send('Hello Tana'))

# 生成器另外两个方法:
# ① 方法throw:用于在生成器中(yield表达式处)引发异常,调用时可提供一个异常类型 一个可选值 和一个traceback对象
# ② 方法close:用于停止生成器,调用时无需提供任何参数
# 方法close(由python垃圾收集器在需要时调用)也是基于异常的,在yield处引发的GeneratorExit异常。
# 因此,如果要提供一些清理代码,可将yield放在一条try/finally语句中。如果愿意,也可捕获GeneratorExit异常
# 但随后必须重新引发它(可能在清理后)、引发其他异常或直接返回
# 对生成器调用close后,再试图从它那里获取值将导致RuntimeError异常


# 5.5 模拟生成器

def flatten(nested):
    result = []
    try:
        try:
            nested + ""
        except TypeError:
            pass
        else:
            raise TypeError

        for sublist in nested:
            for element in flatten(sublist):
                result.append(element)

    except TypeError:
        result.append(nested)
    return result

l = [[1,2, [3]],[5,[6]],7]
print(list(flatten(l)))
print()


###############################################################################

# 6. 八皇后问题

# 检测冲突
# nextX表示下一个皇后的列 nextY表示下一个皇后的行
# abs(state[i]-nextX) in (0, nextY - i) 表示水平距离为0或垂直距离相等
def conflict(state, nextX):
    nextY = len(state)
    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


print(list(queens(8)))

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容