生成器
通过列表推导式得到生成器
要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )
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()
Dog.demo_method() # 结果: 静态方法
dog1.demo_method() # 结果: 静态方法
但是如果将普通方法写在最后面,那么在用类名调用的时候就会发生错误,因为最后一个的普通方法不能用类名调用,会报错:TypeError: demo_method() missing 1 required positional argument: ‘self’
可以将dog1对象传递进去,就可以运行了
其他魔术方法
普通方法需要调用,魔术方法会在特定时机自动触发
实例化的魔术方法__new__()
触发时机:实例化的时候触发
如下代码在运行时并没有执行init方法,p是一个空对象
系统默认有一个__new__()方法,如果在类中重写了这个方法,就会将系统的这个方法覆盖,下面例子第一个是错误的;第二个是调用了父类的new方法,正确
new方法的作用是申请内存,开辟空间
class Person:
def __init__(self):
self.name = 'jack'
print('---------init')
def __new__(cls, *args, **kwargs):
print('-----------new')
p = Person() # -----------new
print(p.name) # AttributeError: 'NoneType' object has no attribute 'name'
class Person:
def __init__(self):
self.name = 'jack'
print('---------init', self) # ---------init <__main__.Person object at 0x000001B387A73C50>
def __new__(cls, *args, **kwargs):
print('-----------new')
position = super(Person, cls).__new__(cls)
print(position) # <__main__.Person object at 0x000001B387A73C50>
return position
p = Person() #
print(p.name) # jack
注意:
- __new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供
- __new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例
- __init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值
class A(object):
def __init__(self):
print("这是 init 方法")
def __new__(cls):
print("这是 new 方法")
return object.__new__(cls)
A()
1.new方法用于开辟空间,将开辟好的空间返回,传递给init方法
2.init方法进行初始化,给参数赋值
3.用初始化好的空间地址给给实例对象赋值,让参数指向这个空间地址
对象调用方法__call__()
将对象如函数调用,需要重写__call__()方法
如下面例子的最后一行,将对象当做函数调用,执行__call__()方法
可以有参数
class Person:
def __init__(self):
self.name = 'jack'
print('---------init', self) # ---------init <__main__.Person object at 0x000001B387A73C50>
def __new__(cls, *args, **kwargs):
print('-----------new')
position = super(Person, cls).__new__(cls)
print(position) # <__main__.Person object at 0x000001B387A73C50>
return position
def __call__(self, *args, **kwargs):
print('-----------------call')
p = Person() # -----------new ---------init
print(p.name) # jack
p() # -----------------call
析构魔术方法__del__()
创建对象后,python解释器默认调用__init__()方法;
而当删除对象时,python解释器也会默认调用一个方法,这个方法为__del__()方法。
如果有两个不同的对象,删除引用以后,因为没有引用指向了地址空间,所以就会调用析构方法
而如果有两个相同的对象,删除了一个的引用,并不会调用析构方法,因为地址空间还有指向;需要都删除以后才会执行析构方法
即使不删除,因为程序运行结束,也会自动调用析构方法释放空间
class Student:
def __init__(self,name,score):
print('__init__方法被调用了')
self.name = name
self.score = score
def __del__(self):
print('__del__方法被调用了')
s1 = Student('lisi',95)
s2 = Student('lisi',96)
del s1
print('删除第一个')
del s2
print('删除第二个')
# __init__方法被调用了
# __init__方法被调用了
# __del__方法被调用了
# 删除第一个
# __del__方法被调用了
# 删除第二个
s3 = Student('lisi',97)
s4 = s3
del s3
print('删除第三个')
del s4
print('删除第四个')
# __init__方法被调用了
# 删除第三个
# __del__方法被调用了
# 删除第四个
__str__()
相当于java中的toString()方法
__str__()方法返回对象的描述信息,使用print()函数打印对象时,其实调用的就是这个对象的__str__方法。
class Cat:
def __init__(self,name,color):
self.name = name
self.color = color
tom = Cat('Tom','white')
# 使用 print 方法打印对象时,会调用对象的 __str__ 方法,默认会打印类名和对象的地址名
print(tom) # <__main__.Cat object at 0x0000021BE3B9C940>
一般情况下,我们在打印一个对象时,可能需要列出这个对象的所有属性。
注意:一定要加return,返回想要输出的内容
class Student:
def __init__(self,name,score):
self.name = name
self.score = score
def __str__(self):
return '姓名是:{},成绩是{}分'.format(self.name,self.score)
s = Student('lisi',95)
print(s) # 姓名是:lisi,成绩是95分
比较运算相关的魔术方法
做题常用!!!
定义比较的逻辑
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
# 等于
def __eq__(self, other):
return self.name == other.name and self.age == other.age
# 不等于
# def __ne__(self, other):
# 小于
def __lt__(self, other):
return self.age < other.age
# 大于
# def __gt__(self, other):
# 小于等于
def __le__(self, other):
return self.age <= other.age
# 大于等于
# def __ge__(self, other):
s1 = Student('zhangsan', 18)
s2 = Student('zhangsan', 18)
s3 = Student('lisi', 20)
print(s1 == s2) # True
print(s1 != s2) # False
print(s1 > s2) # False
print(s1 >= s2) # True
print(s1 <= s2) # True
print(s1 <= s2) # True
print(s1 < s3) # True
私有化(封装)
封装的一种表现形式,私有化属性动作,定义共有的set和get方法
在实际开发中,对象的某些属性或者方法可能只希望在对象的内部别使用,而不希望在外部被访问到,这时就可以定义私有属性和私有方法。
在定义属性或方法时,在属性名或者方法名前增加两个下划线__,定义的就是私有属性或方法。
私有属性不能直接使用,私有方法不能直接调用。但是,通过一些代码,我们也可以在外部访问一个对象的私有属性和方法。
可以直接访问:在私有属性名或方法名前添加 _类名
但是注意:在开发中,强烈不建议使用 对象名._类名__私有属性名 的方式来访问对象的私有属性!
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000
def __shopping(self, cost):
self.__money -= cost
p = Person('李四',20)
print(p._Person__money) # 使用对象名._类名__私有属性名 可以直接访问对象的私有属性
p._Person__shopping(100) # 使用对象名._类名__函数名 可以直接调用对象的私有方法
print(p._Person__money) # 1900
在实际开发中,如果对象的变量使用了__ 来修饰,就说明它是一个私有变量,不建议外部直接使用和修改。如果硬要修改这个属性,可以使用定义get和set方法这种方式来实现。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
self.__money = 2000 # __money 是私有变量,外部无法访问
def get_money(self): # 定义了get_money 方法,在这个方法里获取到 __money
return self.__money # 内部可以访问 __money 变量
def set_money(self,money): # 定义了set_money 方法,在这个方法里,可以修改 __money
self.__money = money
p = Person('王五',21)
# 外部通过调用 get_money 和 set_money 这两个公开方法获取和修改私有变量
print(p.get_money())
p.set_money(8000)
print(p.get_money())
使用内置函数dir可以查看一个对象支持的所有属性和方法,Python中存在着很多的内置属性。
用dir()和__dir__可以看到类和对象可以访问到的属性,而在里面可以看到在私有属性前面都加了 _类名,类中也无法直接访问私有属性
class Person:
def __init__(self,name,age):
self.__name = name
self.__age = age
self.__money = 2000
def __shopping(self, cost):
self.__money -= cost
def setName(self, name):
self.__name = name
def getName(self):
return self.__name
p = Person('李四',20)
print(p._Person__money) # 使用对象名._类名__私有属性名 可以直接访问对象的私有属性
p._Person__shopping(100) # 使用对象名._类名__函数名 可以直接调用对象的私有方法
print(p._Person__money)
print(dir(Person))
# ['_Person__shopping', '__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__', 'getName', 'setName']
print(dir(p))
# ['_Person__age', '_Person__money', '_Person__name', '_Person__shopping', '__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__', 'getName', 'setName']
@property装饰器
私有化以后,访问或者修改属性需要调用get和set方法,比较麻烦,那么有没有简单的办法使得属性的查看和修改变得和原来一样容易呢,还保留私有化的作用
property本身是一个类,类也可以当做装饰器
property属性的定义和调用要注意一下几点:
定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
调用时,无需括号
需要注意,先需要写get方法,加上@property,然后再写set方法
另外,不一定要局限于只在私有化属性上用这个装饰器,也可以在一般的方法上加这个装饰器,便于直接查看
class Person:
def __init__(self,name,age):
self.__name = name
self.age = age
self.money = 2000
# 现有getXXX
@property
def name(self):
return self.__name
# 再有setXXX
@name.setter
def name(self, name):
self.__name = name
p = Person('lisi', 20)
print(p.name) # list
p.name = 'ww'
print(p.name) # ww
还有第三个删除属性的方法
class Goods:
"""
只有在python3中才有@xxx.setter @xxx.deleter
"""
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deleter
def price(self):
del self.original_price
obj = Goods()
obj.price # 获取商品价格
obj.price = 200 # 修改商品原价
del obj.price # 删除商品原价
新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
继承
has a 关联关系
is a 继承关系
在程序中,继承描述的是多个类之间的所属关系。
如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里。
那么类A就是基类,也叫做父类;类B就是派生类,也叫做子类。
子类继承自父类,可以享受父类中已经封装好的方法,不需要再次定义
子类中应该根据职责,封装子类特有的属性和方法。
特别注意:父类私有的子类无法访问
继承语法:
class 类名(父类名):
pass
Python中创建的类默认继承Object
class Animal:
def __init__(self):
pass
"""动物类"""
def sleep(self):
print('正在睡觉')
class Dog(Animal):
"""Dog类继承自Animal类"""
def __init__(self):
pass
class Cat(Animal): # 定义类时,在括号后面传入父类的类名,表示子类继承父类
"""Cat类继承自Animal类"""
def __init__(self):
pass
# Dog 和 Cat 都继承自Animal类,可以直接使用Animal类里的sleep方法
dog = Dog()
dog.sleep()
cat = Cat()
cat.sleep()
在子类init方法中使用父类的方法,需要用到super关键字
如果父类和子类中有同名的方法,会调用子类的方法
如果子类方法中有参数和父类中的方法无参,在判断时也会走子类的方法,如果调用时不传参数会报错
子类的方法中也可以调用父类的方法,super().方法名
class Animal:
def __init__(self, name, color):
self.name = name
self.color = color
self.__mo = 50 # 父类私有的子类无法访问
"""动物类"""
def sleep(self):
print(self.name + '正在睡觉')
def catch(self):
print(self.name + '正在捉')
class Dog(Animal):
"""Dog类继承自Animal类"""
def __init__(self, name, color):
print('这是dog')
super().__init__(name, color) # 表示调用父类的init的方法
# 子类特有的方法
def wang(self):
print(self.name + '正在旺旺叫')
# print(self.__mo) # 私有的无法访问
class Cat(Animal): # 定义类时,在括号后面传入父类的类名,表示子类继承父类
"""Cat类继承自Animal类"""
def __init__(self, name, color, size):
print('这是dog')
# 这里默认提示的是这样写,底层是对当前类判断是否是Cat
# super(type, obj) -> bound super object; requires isinstance(obj, type)
super(Cat, self).__init__(name, color) # 表示调用父类的init的方法
self.size = size
# 子类特有的方法
def eat_fish(self):
print(self.name + '正在吃鱼')
# 重写方法,执行时调用子类的方法
def sleep(self):
print(self.color + '的' + self.name + '正在睡觉')
# 重写方法,但是多了一个参数,即使不传参也会调用这个方法,导致出错
def catch(self, an):
# 如果依赖于父类的方法,可以用super加过来
super().catch()
print(self.name + '正在捉' + an)
# Dog 和 Cat 都继承自Animal类,可以直接使用Animal类里的sleep方法
dog = Dog('tom', 'yellow')
dog.sleep()
cat = Cat('jack', 'red', 1)
cat.sleep() # red的jack正在睡觉
an = '老鼠'
# cat.catch() # TypeError: catch() missing 1 required positional argument: 'an'
cat.catch(an) # jack正在捉老鼠
在Python中,继承可以分为单继承、多继承和多层继承。
单继承:子类只继承一个父类
多继承:子类可以拥有多个父类,并且具有所有父类的属性和方法。
class 子类名(父类名1,父类名2...)
pass
如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪个父类的方法? 说明:开发中,应该尽量避免这种容易产生混淆的情况。如果多个父类之间存在同名的属性或者方法,应该尽量避免使用多继承。
Python中针对类提供了一个内置属性__mro__可以用来查看方法的搜索顺序。
MRO 是method resolution order的简称,主要用于在多继承时判断方法属性的调用顺序。
在python3中顺序是按广度优先,先找最下层,然后再往上找;在python2中是深度优先,从左至右
C继承了A和B
print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
或者
import inspect
inspect.getmro(C)
在调用方法时,按照__mro__的输出结果从左至右的顺序查找。
如果再当前类中找到方法,就直接执行,不再向下搜索。
如果没有找到,就顺序查找下一个类中是否有对应的方法,如果找到,就直接执行,不再继续向下搜索。
如果找到了最后一个类,依然没有找到方法,程序就会报错。
issubclass 用来判断两个类之间的继承关系。例子:issubclass(Student, Person)
instance内置函数,用来判断一个实例对象是否是由某一个类(或者它的子类)实例化创建出来的。例子:isinstance(s, Person)
A is B身份运算符用来比较两个对象的内存地址,看这两个对象是否是同一个对象。
python中没有java的方法重载
后面的方法将前面的覆盖
这个怎么理解呢,可能是因为python中参数的个数可以通过可变参数来传递多个,因此无法重载
class Bird:
def __init__(self, name, color):
self.name = name
self.color = color
def sleep(self):
print(self.name + '正在睡觉')
def sleep(self, place):
print(self.name + '正在' + place + '睡觉')
b = Bird('lili', 'pink')
b.sleep() # TypeError: sleep() missing 1 required positional argument: 'place'
多态
面向对象的三大特性:
封装:这是定义类的准则,根据对象的特点,将行为和属性抽象出来,封装到一个类中。
继承:这是设计类的技巧。父类与子类,主要体现在代码的重用,不需要大量的编写重复代码。
多态:不同的子类调用相同的父类方法,产生不同的执行结果,可以增加代码的外部灵活度。多态是以继承和重写父类方法为前提的,它是一种调用方法的技巧,不会影响到类的内部设计。
在python中没有严格的多态
例如在java中,方法的参数是有类型限制的,只能传递规定类型或者子类;创建变量的时候用子类创建父类的对象,就是多态
但是在python中,方法的参数并没有规定类型,传递进去的参数需要进行判断是否适用
教程文档里给的python多态:
定义:多态是一种使用对象的方式,子类重写父类方法,调用不同子类对象的相同父类方法,可以产生不同的执行结果
好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
实现步骤:
定义父类,并提供公共方法
定义子类,并重写父类方法
传递子类对象给调用者,可以看到不同子类执行效果不同
class Dog(object):
def work(self): # 父类提供统一的方法,哪怕是空方法
pass
class ArmyDog(Dog): # 继承 Dog
def work(self): # 子类重写方法,并且处理自己的行为
print('追击敌人')
class DrugDog(Dog):
def work(self):
print('追查毒品')
class Person(object):
def work_with_dog(self, dog):
dog.work() # 使用小狗可以根据对象的不同而产生不同的运行效果, 保障了代码的稳定性
# 子类对象可以当作父类来使用
dog = Dog()
ad = ArmyDog()
dd = DrugDog()
p = Person()
p.work_with_dog(dog)
p.work_with_dog(ad) # 同一个方法,只要是 Dog 的子类就可以传递,提供了代码的灵活性
p.work_with_dog(dd) # 并且传递不同对象,最终 work_with_dog 产生了不同的执行效果
最终效果
Person 类中只需要调用 Dog 对象 work() 方法,而不关心具体是 什么狗
work() 方法是在 Dog 父类中定义的,子类重写并处理不同方式的实现
在程序执行时,传入不同的 Dog 对象作为实参,就会产生不同的执行效果