Python 零基础入门到实战(三)笔记:面向对象编程:类、继承、封装、多态、自定义对象类型、控制属性访问、迭代器、生成器、斐波那切数列
面向对象编程
对象(object):表示客观世界问题空间中的某个具体事务,又表示软件系统解空间中的基本元素
面向对象程序设计(object-oriented programming,OOP):是一种程序设计泛型,也是一种程序设计开发的方法
创建类
关键词 类的名称:
[缩进]初始化方法():
[缩进][缩进]…
[缩进]普通方法():
[缩进] [缩进]…
练习:编写一个程序,用于判断学生是否已经完成作业。如果完成,老师会表扬,否则要进行批评、
class Student:
def __init__(self,name,grade,subject):
self.name=name
self.grade=grade
self.subject=subject
def doHomeWork(self,time):
if self.grade>3 and time>2:
return True
elif self.grade<3 and time>0.5:
return True
else:
return False
class Teacher:
def __init__(self,name,subject):
self.name=name
self.subject=subject
def evaluate(self,result=True):
if result:
return "you are great."
else:
return "you should work hard"
s_ye=Student('ye',5,'math')
t_w=Teacher('wang','math')
teacher_said=t_w.evaluate(s_ye.doHomeWork(1))
print("Teacher{0} said: {1},{2}".format(t_w.name,s_ye.name,teacher_said))
s_zd=Student('zd',4,'math')
t_w=Teacher('wang','math')
teacher_said=t_w.evaluate(s_ye.doHomeWork(5))
print("Teacher{0} said: {1},{2}".format(t_w.name,s_zd.name,teacher_said))
测试结果
Teacherwang said: ye,you should work hard
Teacherwang said: zd,you are great.
>>> class people:
... def __init__(self):
... print("this is init")
... self.lesson='python'
...
>>> f=people()
this is init
>>> f.lesson
'python'
类属性(静态属性)
类属性,又称静态属性(不会因为你创建实例改变属性的值),只有通过类才能修改。
实例也拥有类属性,但不能修改类属性。
>>> class foo:
... lang='python'
>>> dir(foo)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'lang']
>>> f=foo()
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'lang']
观察得到,不论是类还是实例都多了lang属性
>>> foo.group='social'
>>> dir(foo)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang']
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang']
可以观察到,对类新赋予一个属性,实例中也会具有lang属性
>>> f.name='brian'
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang', 'name']
>>> dir(foo)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang']
可以观察到,对实例赋予新属性,会导致类中不会增添新属性
拿工厂作比喻,比如一个工厂生产汽车,工厂就是类模板、
你买来一个汽车,就是一个实例、你自己给汽车做DIY加的并不会影响到工厂流水线,而工厂里的工艺增减配则会导致每台到用户手上的车多或少了某个功能(属性)
>>> foo.group#之前新建的属性并赋值
'social'
>>> f.group#实例也跟着变化了
'social'
>>> f.group='DIY'#修改实例
>>> f.group
'DIY'
>>> foo.group#类没变
'social'
实例属性 (动态属性)
实例属性,又称动态属性。通过实例创建,不同实例的实例属性不同,实例的__dict__显示当前实例的所有属性。】
>>> f.__dict__
{'name': 'brian', 'group': 'DIY'}
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'group', 'lang', 'name']
静态属性lang在f.__dict__中并不显示、
self的作用
类中的方法,如无特别规定,都是以self作为第一参数
self引用当前实例
方法与函数
以上类的属性仅仅描述了是什么的问题,还没描述对象能干些什么具体的动作和交互(messaging)
需要注意名称的命名、代码块的编写方式都一样
(实例)方法不能单独调用、只能通过实例/类调用,如下代码所展示
>>> class Peo:
... def study(self,x):
... return x*2
...
>>> bob=Peo()
>>> bob.study(2)
4
>>> Peo(2)#需要通过实例调用
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Peo() takes no arguments
>>> Peo.study(bob,2)
4
使用装饰器:@staticmethod静态方法不与实例绑定
@classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
练习:创建类,能通过“年-月-日”字符串创建实例,并检查年、月、日是否合法
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@classmethod
def fromStr(cls,dataString):
year,month,day=map(int,dataString.split('-'))
date1=cls(year,month,day)
return date1
@staticmethod
def staticCheck(dataString):
year,month,day=map(int,dataString.split('-'))
return day<=31 and month<=12 and year<=2030
def dynamicCheck(self):
return self.day<=31 and self.month<=12 and self.year<=2030
s=(str)(input("input date:year-month-day:"))
today=Date.fromStr(s)
if today.dynamicCheck():
print("格式正确")
else:
print("格式错得一塌糊涂")
测试结果:
input date:year-month-day:2021-4-23
格式正确
input date:year-month-day:2021-4-33
格式错得一塌糊涂
单继承
复用,减少冗余代码
>>> class People:
... def __init__(self,name):
... self.name=name
... def eat(self):
... return "eat meat&rice"
...
>>> class Student(People):
... def __init__p(self,grade):
... self.grade=grade
...
>>> tom=Student('brian',1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() takes 2 positional arguments but 3 were given
看到报错了,需要两个参数,但却给了3个,因为隐含参数Self不需要提供,隐含参数由编译器默认提供、加上括号内的2个,一共是给了3个参数、
>>> tom=Student('brian')
>>> dir(tom)
['_Student__init__p', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'eat', 'name']
上述代码中可以发现子类中也有父类的eat,name方法,这便是继承下来的
>>> class Student(People):
... def __init__(self,grade):
... self.grade=grade
...
>>> a=Student('brian')
>>> a.grade
'brian'
其实上上一个方法多写了一个p,是想测试当父类和子类中同时具有相同的方法时,子类方法是覆盖父类方法的!正如上代码结果中显示,自动调用了student类中的初始化方法而非父类人类中的初始化方法
那么如果子类覆盖了父类的同名方法,可是还想使用父类的方法,那该怎么做呢?有两种方法
方法一 在方法前加上父类名称.即可。
>>> class Student(People):
... def __init__(self,name,grade):
... self.grade=grade
... People.__init__(self,name)
...
>>> alice=Student("alice",19)
>>> dir(alice)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'eat', 'grade', 'name']
其中,grade是子类的方法,而eat,name是父类的方法,下面是另一种写法
方法二 在方法前加上super().
>>> class Student(People):
... def __init__(self,name,grade):
... self.grade=grade
... super().__init__(name)
...
>>> bob=Student("bob",20)
>>> dir(bob)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'eat', 'grade', 'name']
多继承
在括号里加个,继续写父类名称即可,如:class E(J1,J2):即为E类继承了J1和J2类!
Python的每一个有父类的类都有一个与方法解析顺序相关的特殊属性:mro, 它是一个tuple, 装着方法解析时的对象查找顺序: 越靠前的优先级越高.
多态
python语言天然具有多态的特性、此处与( 零基础入门到实战一中的)呼应,比如支持对整数,字符串等相加,且不需明确声明变量的类型,这是与强类型语言所不同的!
Q:Thinking java作者Bluce说不应该注重强类型和弱类型问题,应该注重强测试(Strong testing)后续了解
封装
私有化手段进行封装:类定义中,在属性或方法名前加上__后, 实现了私有化,在作用域外调用即为失败。即实现了封装!
>>> class Car:
... def __init__(self):
... self.name="五菱宏光"
... self.__factory="shanghai"
... def produce(self):
... return self.__factory
... def __brand(self):
... return self.name
...
>>> myCar=Car()
>>> myCar.produce
<bound method Car.produce of <__main__.Car object at 0x000001A2D0A23970>>
>>> myCar.produce()
'shanghai'#调用成功,但是__factory是私有数据在初始化时创建
>>> myCar.__brand()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Car' object has no attribute '__brand'
>>> myCar.__factory
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Car' object has no attribute '__factory'
发现想直接从外部调用私有化的成员,都是失败!
类和类型
>>> a=3.4
>>> type(a)
<class 'float'>
>>> class RoundFloat:
... def __init__(self,val):
... self.value=round(val,2)
... def __str__(self):#str:用户易读的表达形式
... return "{0:.2f}".format(self.value)
... __repr__=__str__#repr:解释器容易理解的表达形式
...
>>> r=RoundFloat(3.1415926)
>>> print(r)
3.14
>>> print(type(r))
<class '__main__.RoundFloat'>
可以观察到,a=3.4,是float类,而r=3.14也是RoundFloat类。考虑类型的时候,其实就是在考虑是由哪一个类实例化而来的!
>>> class test:
... def __repr__(self):
... return "this is repr"
... def __str__(self):
... return "this is str"
...
>>> t=test()
>>> t #对解释器友好
this is repr
>>> print(t) # 对用户友好
this is str
掌握自定义对象类型的方法
在Python中想写分数,比如2/3会自动算出结果、无法进行恰当的表示、加减,约分等操作,故可以自定义特别的对象类型来进行描述!例如
class show:
def __init__(self,n,m=1):
self.n=n
self.m=m
def __str__(self):
return str(self.n)+"/"+str(self.m)
__repr__=__str__
a=show(2,3)
print(a)
输出:
2/3
当然,上述写的不够严密和完整,也未考虑月份等情况,然而很多工具和库都是开源的,在遇到新问题时,先找看有没有能拿来即用的,其次再考虑自己写。
如下代码直接import,还有加法等功能
>>> from fractions import Fraction
>>> m,n=Fraction(1,6),Fraction(3,6)
>>> m
Fraction(1, 6)
>>> n
Fraction(1, 2)
>>> m+n
Fraction(2, 3)
控制属性访问
>>> class People:
... name="brian"
...
>>> People.__dict__
mappingproxy({'__module__': '__main__', 'name': 'brian', '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None})
>>> m=People()
>>> m.__dict__
{}
>>> m.age=22
>>> m.job='stu'
>>> m.__dict__
{'age': 22, 'job': 'stu'}
优化内存
>>> class a:
... __slots__=('name','id')
...
>>> a.name='lalal'
>>> a.id=1233
>>> b=a()
>>> b.id
1233
>>> b.name
'lalal'
>>> b.city='shanghai'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'a' object has no attribute 'city'
>>> b.id=211
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'a' object attribute 'id' is read-only
可以发现,实例不能新建别的属性,对象属性也是只读,无法修改
掌握与属性相关的特殊方法
getattr/setattr
如果访问实例的一个属性,若不存在,就会调用_getattr_
如果属性存在,就不会被_getattr_拦截给name赋值,就走_setattr_
>>> class Car():
... def __getattr__(self,wheel):
... print("wrong,wheel")
... def __setattr__(self,wheel,value):
... print("right,setattr")
... self.__dict__[wheel]=value
...
>>> a=Car()
>>> a.x
wrong,wheel
>>> a.x='four'
right,setattr
>>> a.x
'four'
迭代器
迭代器简单用法
>>> hasattr(list,"__iter__")
True
>>> lst=[1,2,3,4]
>>> iter_lst=iter(lst)
>>> iter_lst
<list_iterator object at 0x000001B37E0D9490>
>>> lst
[1, 2, 3, 4]
>>> hasattr(iter_lst,"__iter__")
True
>>> hasattr(iter_lst,"__next__")
True
>>> hasattr(list,"__next__")
False
>>> iter_lst.__next__()
1
>>> iter_lst.__next__()
2
>>> iter_lst.__next__()
3
>>> iter_lst.__next__()
4
>>> iter_lst.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
迭代器通过next往后挪动指针
>>> for i in iter_lst:
... print(i)
...# 打印不出来是因为指针已经在最后一位
>>> iter_lst=iter(lst)
>>> for i in iter_lst:print(i)
...
1
2
3
4
迭代器工具
>>> import itertools
>>> c=itertools.count(start=5)
>>> next(c)
5
>>> next(c)
6
>>> next(c)
7
>>> next(c)
8
#指定5开始,每一次next一下
自己实现一个迭代器
class iterDIY():
def __init__(self,n):
self.i=1
self.n=n
def __iter__(self):
return self
def __next__(self):
if self.i<=self.n:
i=self.i
self.i+=1
return i
else:
raise StopIteration()
print("range[5]:",list(range(5)))
print("iterDIY[5]:",[i for i in iterDIY(5)])
range[5]: [0, 1, 2, 3, 4]
iterDIY[5]: [1, 2, 3, 4, 5]
练习: 迭代器求斐波那契数列
class fibIter():
def __init__(self,max):
self.a=0
self.b=1
self.max=max
def __iter__(self,):
return self
def __next__(self):
fib=self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
fibs = fibIter(1000000)
lst = [fibs.__next__() for i in range(10)]
print(lst)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
生成器
生成器简单用法
>>> def g():
... yield 0
... yield 1
... yield 2
...
>>> gg=g()
>>> gg
<generator object g at 0x000001B37E0BBE40>
>>> type(gg)
<class 'generator'>
>>> dir(gg)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
>>> gg.__next__()
0
>>> gg.__next__()
1
>>> gg.__next__()
2
>>> gg.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
return->yield
>>> def r(n):
... print("begin")
... while n>0:
... print("before return")
... return n
... n-=1
... print("after return")
...
>>> rr=r(3)
begin
before return
>>> print(rr)
3
>>>
>>> def y(n):
... print("begin")
... while n>0:
... print("before yield")
... yield n
... n-=1
... print("after yield")
...
>>> yy=y(3)
>>> yy.__next__()
begin
before yield
3
>>> yy.__next__()
after yield
before yield
2
>>> yy.__next__()
after yield
before yield
1
>>> yy.__next__()
after yield
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
生成器解析
可以把列表解析转化为生成器解析的方法,如下代码
>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> ge=(x**2 for x in range(10))
>>> ge
<generator object <genexpr> at 0x000001B37E54BAC0>
>>> ge.__next__()
0
>>> ge.__next__()
1
>>> ge.__next__()
4
>>> ge.__next__()
9
>>> ge.__next__()
16
练习:生成器求斐波那切数列
def fibGen():
p, c= 0, 1
while True:
yield p
p, c= c, p + c
import itertools
print(list(itertools.islice(fibGen(), 10)))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
未完待续…