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 = [12]
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学习之旅--封装继承多态

Python学习之旅--封装继承多态

Python面向对象特性

Python类与对象最全总结大全(类实例属性方法继承派生多态内建函数)

面向对象(封装继承多态抽象)

oldboy 21th day. I love Python. 面向对象之封装, 多态, 继承 三大特性