Python学习5(生成器类属性方法私有化继承多态)
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python学习5(生成器类属性方法私有化继承多态)相关的知识,希望对你有一定的参考价值。
生成器
通过列表推导式得到生成器
要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )
In [15]: L = [ x*2 for x in range(5)]
In [16]: L
Out[16]: [0, 2, 4, 6, 8]
In [17]: G = ( x*2 for x in range(5))
In [18]: G
Out[18]: <generator object <genexpr> at 0x7f626c132db0>
创建 L 和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是一个列表,而 G 是一个生成器。我们可以直接打印出列表L的每一个元素,而对于生成器G,我们可以按照迭代器的使用方法来使用,即可以通过builtins内置函数next()函数、for循环、list()等方法使用。
generator.__next__()也可以
In [19]: next(G)
Out[19]: 0
In [20]: next(G)
Out[20]: 2
In [21]: next(G)
Out[21]: 4
In [22]: next(G)
Out[22]: 6
In [23]: next(G)
Out[23]: 8
In [24]: next(G)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-24-380e167d6934> in <module>()
----> 1 next(G)
StopIteration:
In [25]:
In [26]: G = ( x*2 for x in range(5))
In [27]: for x in G:
....: print(x)
....:
0
2
4
6
8
G = (x*2 for x in range(5))
print(G.__next__()) # 0
print(G.__next__()) # 2
通过函数得到生成器
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。
函数中出现了yield关键字,就说明不是函数了,变成生成器了
yield关键字有两点作用:
- 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
- 将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
yield相当于return+暂停的操作,下一次进入继续运行
步骤:
1.定义函数,使用yield关键字
2.调用函数,接收调用的结果
3.得到的结果就是生成器
4.借助next()或者g.__next__()得到元素
当生成器没有元素可以生成的时候,就会报错,返回return后的结果
def func():
n = 0
while n < 4:
n += 1
yield n
return '没有元素了'
g = func()
print(g) # <generator object func at 0x00000249EAC076D8>
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
print(next(g)) # 4
print(next(g)) # StopIteration: 没有元素了
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
In [39]: g = fib(5)
In [40]: while True:
....: try:
....: x = next(g)
....: print("value:%d"%x)
....: except StopIteration as e:
....: print("生成器返回值:%s"%e.value)
....: break
....:
value:1
value:1
value:2
value:3
value:5
生成器返回值:done
使用send唤醒
def func():
n = 0
while n < 4:
temp = yield n
print(temp)
n += 1
return '没有元素了'
g = func()
print(g) # <generator object func at 0x00000249EAC076D8>
# 输出的temp都是None
print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
print(next(g)) # StopIteration: 没有元素了
send可以将值传入temp中,注意第一次传入的时候,因为没有东西接收,所以传入的值必须是None(因为执行到yield的时候,返回并暂停了;第二次进入的时候,才会有temp=yield i 这样的赋值操作,所以第一次不能赋值,就不能传值)
send函数的返回值就是yield暂停输出的值,next()相当于send(None)
def func():
n = 0
while n < 4:
temp = yield n
print('temp',temp)
n += 1
return '没有元素了'
g = func()
print('h0',g.send(None)) # send第一个值必须是None
print('h1',g.send('hh'))
print('h2',g.send('xx'))
# h0 0
# temp hh
# h1 1
# temp xx
# h2 2
生成器的应用(多任务)
进程>线程>协程,在协程中使用
例子:两个生成器,然后协同完成一个任务,而不是一个函数执行完再执行另一个函数
下面是交替打印奇偶数
def func1():
n = 0
while n < 10:
yield n
n += 2
return '没有元素了'
def func2():
n = 1
while n < 10:
yield n
n += 2
return '没有元素了'
g1 = func1()
g2 = func2()
while True:
try:
print(g1.__next__())
print(g2.__next__())
except:
break
迭代器
迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
如何判断一个对象是否是可迭代的
isinstance(x, A) x是否是A对象
from collections import Iterable
list = [1,2]
print(isinstance(list, Iterable)) # True
我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在for…in…中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator)。
可迭代对象的本质就是可以向我们提供一个这样的中间“人”即迭代器帮助我们对其进行迭代遍历使用。
可迭代对象通过__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据.
那么也就是说,一个具备了__iter__方法的对象,就是一个可迭代对象。
for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
我们已经知道,迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__方法(Python3中是对象的__next__方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的next方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。
一个实现了iter方法和next方法的对象,就是迭代器。
class MyIterator(object):
def __init__(self, n):
self.n = n
self.current = 0
# 自定义迭代器需要重写__iter__和__next__方法
def __iter__(self):
return self
def __next__(self):
if self.current < self.n:
value = self.current
self.current += 1
return value
else:
raise StopIteration
my_it = MyIterator(10)
for i in my_it: # 迭代器重写了__iter__方法,它本身也是一个可迭代对象
print(i)
可以按next()函数不断调用并返回下一个元素的对象被称作迭代器,显然生成器是迭代器,那么列表是迭代器吗
用列表调用next()方法会报错,所以列表不是迭代器,所以可迭代的并不一定是迭代器
调用一个对象的__iter__方法,或者调用iter()内置函数,可以获取到一个可迭代对象的迭代器。
from collections.abc import Iterable
list1 = [1,2]
print(isinstance(list1, Iterable)) # True
# next(list1) # TypeError: 'list' object is not an iterator
list1 = iter(list1)
print(next(list1)) # 1
names = ['hello', 'good', 'yes']
print(names.__iter__()) # 调用对象的__iter__()方法
print(iter(names)) # 调用iter()内置函数
面向对象
类、对象、属性、方法
多个对象–>提取共同特征和动作–>封装到一个类中
所有的类要求首字母大写,多个单词使用驼峰式命名法
方括号里面的可加可不加
class 类名[(父类)]:
属性
方法
class Phone:
pass
print(Phone) # <class '__main__.Phone'>
p1 = Phone()
print(p1) # <__main__.Phone object at 0x0000018C71AE0860>
类和属性
python支持动态属性,当一个对象创建好了以后,直接使用 对象.属性名 = 属性值 就可以很方便的给对象添加一个属性。这种方法很方便,但是,不建议使用这种方式给对象添加属性。
class Phone:
# 类属性
pinpai = 'huawei'
price = 4000
p1 = Phone()
print(p1.pinpai) # huawei
p1.pinpai = 'iphone' # 对象属性
print(p1.pinpai) # iphone
p1.size = 64 # 可以动态赋予属性
print(p1.size) # 64
普通方法
种类:普通方法、类方法、静态方法、魔术方法
在Python中要定义一个只包含方法的类,语法格式如下:
class 类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
方法的定义格式和之前学习过的函数一样
方法里的第一个参数必须是self,暂时先记住,稍后介绍 self.
类名要遵守大驼峰命名法。
self是当前对象,调用方法的时候,将当前对象本身传入方法中
实例方法间可以相互调用,需要加self
class Cat:
"""这是个猫类"""
def eat(self):
print(self) # <__main__.Cat object at 0x000001E265DF3BA8>
print("小猫在吃东西")
def drink(self):
print("小猫在喝水")
self.eat() # 实例方法的调用,必须加self
tom = Cat() # 创建了一个Cat对象
print(tom) # <__main__.Cat object at 0x0000023544533BA8>
tom.eat()
tom.drink()
初始化方法__init__()
Python 里有一种方法,叫做魔法方法。Python 的类里提供的,两个下划线开始,两个下划线结束的方法,就是魔法方法,魔法方法在恰当的时候就会被激活,自动执行。 魔法方法的两个特点:
两侧各有两个下划线;
"咒语"名字已经由 Python 官方定义好,我们不能乱写。
刚刚在属性的时候看到,如果方法中有一个特定的属性,而在类中没有定义,那么创建对象如果没有赋予这个属性意义,那么调用这个方法就会出现问题。
那么如何能够解决这个问题呢?
__init__()方法,在创建一个对象时默认被调用,不需要手动调用。在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对 __init__ 方法进行改造。
class Cat:
"""这是一个猫类"""
def __init__(self,name): # 重写了 __init__ 魔法方法
self.name = name
def eat(self):
return "%s爱吃鱼"%self.name
def drink(self):
return '%s爱喝水'%self.name
"""
tom = Cat()
TypeError: __init__() missing 1 required positional argument: 'name'
这种写法在运行时会直接报错!因为 __init__ 方法里要求在创建对象时,必须要传递 name 属性,如果不传入会直接报错!
"""
tom = Cat("Tom") # 创建对象时,必须要指定name属性的值
tom.eat() # tom爱吃鱼
__init__()方法在创建对象时,会默认被调用,不需要手动的调用这个方法。
__init__()方法里的self参数,在创建对象时不需要传递参数,python解释器会把创建好的对象引用直接赋值给self
在类的内部,可以使用self来使用属性和调用方法;在类的外部,需要使用对象名来使用属性和调用方法。
如果有多个对象,每个对象的属性是各自保存的,都有各自独立的地址。
方法是所有对象共享的,只占用一份内存空间,方法被调用时会通过self来判断是哪个对象调用了实例方法。
初始化方法__init__()执行是在创建对象空间后,进行初始化,然后给对象值,将空间地址赋值给对象,完成对象的创建
类属性和类方法
通过类创建的对象被称为 实例对象,对象属性又称为实例属性,记录对象各自的数据,不同对象的同名实例属性,记录的数据各自独立,互不干扰。
class Person(object):
def __init__(self,name,age):
# 这里的name和age都属于是实例属性,每个实例在创建时,都有自己的属性
self.name = name
self.age = age
# 每创建一个对象,这个对象就有自己的name和age属性
p1 = Person('张三',18)
p2 = Person("李四",20)
类方法需要在上面加上 @classmethod,并且类方法参数是cls,表示当前类,在类方法中无法使用实例属性,类方法无法调用实例方法
class Dog:
type = "狗" # 类属性
age = 12
@classmethod
def eat(cls): # 类方法
print(cls) # <class '__main__.Dog'>
print('狗在吃东西')
dog1 = Dog()
dog1.eat()
# 不管是dog1、dog2还是Dog类,都可以访问到type属性
print(Dog.type) # 结果:狗
print(dog1.type) # 结果:狗
类的实例记录的某项数据始终保持一致时,则定义类属性。
实例属性要求每个对象为其单独开辟一份内存空间来记录数据,而类属性为全类所共有 ,仅占用一份内存,更加节省内存空间。
注意:1. 尽量避免类属性和实例属性同名。如果有同名实例属性,实例对象会优先访问实例属性。
2.类属性只能通过类对象修改,不能通过实例对象修改
3.类属性也可以设置为私有,前边添加两个下划线。 如:
私有化以后,可以和java一样通过给出get和set方法对属性进行查看和修改
需要注意的是,如果用下面这种类方法对私有属性进行更改的话,修改的是类空间里的属性,对象空间里的属性也会跟着修改
所以一般不会定义私有的类属性
class Dog(object):
count = 0 # 公有的类属性
__type = "狗" # 私有的类属性
@classmethod
def show(cls, cat):
print(cls.__type, cat)
@classmethod
def update(cls, cat):
cls.__type = cat
print(cls.__type)
print(Dog.count) # 正确
# print(Dog.__type) # 错误,私有属性,外部无法访问。AttributeError: type object 'Dog' has no attribute '__type'
cat = '猫'
Dog.show(cat) # 狗 猫
Dog.update(cat) # 猫
类方法的作用:因为只能访问类属性和类方法,可以在对象创建前做一些功能动作
class Dog(object):
count = 0 # 公有的类属性
__type = "狗" # 私有的类属性
def __init__(self):
self.name = 'tom'
@classmethod
def show(cls, cat):
print(cls.__type, cat)
@classmethod
def update(cls, cat):
cls.__type = cat
print(cls.__type)
d = Dog()
d.count = 1
# d._Dog__type = 'mao'
d.update('mao') # mao
d.show('1') # mao 1
print(d._Dog__type) # mao
d2 = Dog()
print(d2._Dog__type) # mao
静态方法
类似于类方法,依赖于装饰器staticmethod,不带任何参数,无法使用self和cls,但是可以通过 【类名.】 的方式使用类属性和类方法,只能访问类的属性和方法,不能访问实例属性和方法;加载时机和类方法是一样的
实例对象也可以调用类方法和静态方法,但是不建议(其实和java中一样)
class Dog(object):
count = 0 # 公有的类属性
__type = "狗" # 私有的类属性
def __init__(self):
self.name = 'tom'
@classmethod
def show(cls, cat):
print(cls.__type, cat)
@classmethod
def update(cls, cat):
cls.__type = cat
print(cls.__type)
@staticmethod
def test():
print('我是静态方法,无法访问实例对象')
# self.name 报错
print(Dog.__type)
Dog.test() # 我是静态方法,无法访问实例对象 狗
注意:类中定义了同名的方法时,调用方法会执行最后定义的方法
class Dog:
def demo_method(self):
print("对象方法")
@classmethod
def demo_method(cls):
print("类方法")
@staticmethod
def demo_method(): # 被最后定义
print("静态方法")
dog1 = Dog()
DogPython学习之旅--封装继承多态