# 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)))
Python 魔法方法 特性和迭代器
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- (一)魔法方法 在python中,有的名称在前后会加上两个下划线,具有特殊的含义,大部分会在某些条件下自动被调用。...
- 本章会(1)讨论一些重要的魔法方法,(2)然后会讨论两个相关的主题:属性和迭代器,(3)最后介绍一个相关的示例来处...