Python中面向对象的概念
1、语言的分类
1)面向机器
抽象成机器指令,机器容易理解。代表:汇编语言。
2)面向过程
做一件事,排除步骤,第一步做什么,第二步做什么,如果出现A问题,做什么处理,出现b问题,做什么处理。问题规模小,步骤化,按部就班处理。 代表:c语言。
(按照步骤进行处理的。)
面向对象和面向过程的差异(一步一步的走,都有谁做or抽象成为不同的类,谁能做。)
编程是多范式的,面向对象只是一种范式。
3)面向对象ocp
随着计算机需要解决的问题规模扩大,情况越来越复杂,需要很多人,很多部门协作,面向过程编程太不适合了。代表:c++ java python等
三要素:封装,父类有的,子类直接继承,多继承少修改,
继承是为了多复用。单一继承、多继承。Mixin技术
2、面向对象
1)定义 (是一种认识世界,分析世界的方法论,用类来表现实实在在的对象。)
c语言是面向过程的,Python和Java是面向对象的。(大型项目的话利用类抽象)面向对象一种是抽象的,一种是具体的。
一种认识世界、分析世界的方法论,将万事万物抽象为类。
2)类class
类是抽象的概念,是万事万物的抽象,是一类实物的共同特征的集合。
用计算机语言来描述类,就是属性和方法的集合。
3)对象instance、object
对象是类的巨象,是一个实体。
对于每个人这个个体,都是抽象概念人类的不同的实体。
(你吃鱼,你就是对象。鱼,也是对象;吃是动作。你是具体的人,是具体的动作。你属于人类,人类是个抽象的概念,是无数具体的个体的抽象。鱼也是具体的对象,就是说你吃的是一条具体的鱼,这条鱼属于鱼类,是无数的鱼抽象出来的概念。)
(吃,是具体的动作,也是操作,也是方法,这个吃是你的动作,也就是人类具有的方法,如果说的是鱼吃人,吃就是鱼类的动作了)
(吃是个动作,许多动物都具有的动作,人类和鱼类都属于动物类,而动物类是抽象的概念,是动物都有吃的动作,但是吃法不同而已)
(驾驶车,这个车也是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的,是人类具有的方法。)
4)属性
是对象状态的抽象,用数据结构来描述。
5)操作
他是对象行为的抽象,用操作名和实现该操作方法的描述。
(每个人都有名字,身高,体重等信息,这些信息都是个人的属性,但是,这些信息不能保存在人类中,因为他是抽象的概念,不能保留具体的值。)
(而人类的实例,就是具体的人,他可以存储这些具体的特性,而且可以不同的人有不同的属性)
6)哲学
一切皆对象
对象是数据和操作的封装
对象是独立的,但是对象之间可以相互作用。
目前oop是最接近人类认知的编程范式。
3、面向对象3要素****
1)封装
*组装:将数据和操作组装到一起。
*对外只是暴露一些接口,通过接口访问对象。(可供操作的属性,暴漏的属性)
2)继承
*多复用,继承来的就不用自己写了。(用到父类已经做好的实现和操作,在自己内部无需实现了,相同的就不写了,写自己独有的特点。)
*多继承少修改,ocp(open-closed princile),使用继承来改变,来体现个性。(父类的基础上少修改)。
3)多态
*面向对象编程最灵活的地方,动态绑定。
人类就是封装:
人类继承自动物类,孩子继承父母的特征,分为单一继承、多继承;
多态,继承自动物类的人类,猫类的操作吃的不同。
(类对象)类的定义
(类的对象)类的实例。
4、Python的类
1)定义
Class ClassName:
语句块 (放属性和方法,实例的属性。实例化过程。)
(1)必须使用class关键字。
(2)类名必须是用大驼峰命名。
(3)类定义完成后,就产生了一个类对象,绑定了标示符ClassName上。
(4)举例
class MyClass:
"""A example class"""
x = 1 # 类属性
def foo(self): #类属性foo,也是方法
return " My Class"
print(MyClass.x)
print(MyClass.foo)
print(MyClass.foo(1))
print(MyClass.__doc__)
No1 print: 1
No2 print: <function MyClass.foo at 0x0000002E421852F0> (说是个函数类在某个地址里面)
No3 print: My Class
No4 print: A example class
|
(标示符就是一个属性)
5、类对象及类属性
1)类对象
类的定义就会产生一个类对象。
2)类属性
类定义中的变量和类中定义的方法都是类的属性。
3)类变量
上类中的x是类MyClass的变量。
(加括号调用方法,不加是调用的属性。)
4)总结
MyClass中 x、foo都是类的属性。
foo方法都是类的属性,如同吃是人类的方法。但是每个具体的人才能吃东西,也就说吃人的实例才能调用的方法。
foo 是方法对象method,不是普通的函数对象function了,他一般要求至少有一个参数。第一个参数可以是self(self是标示符),这个参数位置就留给了self
6、类的实例
调用MyClass().Show() (new函数和init函数都调用了) 带有self的都是方法。
调用 a = MyClass()
背后是要进行绑定的。
前后都有下划线的称为魔术方法。初始化方法。
7、实例化
a = MyClass()
1) 定义方法:
使用上面的语法,在类对象名称后面加上一个括号,就是调用类的实例化方法,完成实例化。实例化就是真正创建一个该类的对象(实例)。
tom = MyClass()
Jerry = MyClass()
tom /jerry都是MyClass的实例,通过实例化生成了2个实例。
每次实例化后获得实例,是不同的实例,即使是使用同样的参数实例化,也得到不同的对象。
Python类实例化后,会自动调用__init__,这个方法第一个参数都必须留给self,其它参数随意。
2)__init__
3)MyClass()实际上调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用。
作用:对实例进行初始化(self当前对象的本身)。实例带初始化。
初始化函数可以多个参数,第一个参数位置必须是self。
__init__()方法不能有返回值,也就是只能是None。
class MyClass:
"""A example class"""
x = 1 # 类属性
def foo(self): #类属性foo,也是方法
return " My Class"
# print(MyClass) #不会被调用
print(MyClass()) #会调用
a = MyClass() # 会被调用。。结果和2一样。
print(a)
No1 print:不会被调用
No2 print :<__main__.MyClass object at 0x0000001101903908>
No2 print :<__main__.MyClass object at 0x0000001101903908>
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def showage(self):
print('{} is {}'.format(self.name,self.age))
tom = Person('Tom',20)
cat = Person('cat',12)
tom.showage() # Tom is 20
cat.showage() # cat is 12
print(tom.name,cat.name) # Tom cat
cat.age += 1
print(cat.age) # 13
cat.showage() # cat is 13
每次实例化后都会生成不同的对象。(id地址不同)
8、实例对象instance
1)类实例化后一定会获得一个对象,就是实例对象。
tom cat 就是Person类的实例。
__init__ 方法的第一参数self就是指代某一实例。
类实例化后,得到一个实例对象,实例对象都会绑定方法,调用时采用tom.showage()的方式。函数签名是showage(self)。这个self就是tom,python中会把方法的调用者作为第一参数self作为实参传入。
Self,name就是tom对象的name,name是保存在tom对象上面,而不是Person的类上面,所有称为实例变量。
2) Self 实例化。
class MyClass:
def __init__(self):
print('self in init = {}'.format(id(self)))
c = MyClass()
print('c = {}'.format(id(c)))
self in init = 583263140104
c = 583263140104
Self就是调用者,就是c对应的实例对象。
Self这个名字只是一个惯例,可以修改,最好不要修改,影响可读性。
9、实例变量和类变量
class Person:
age = 3
def __init__(self,name):
self.name = name
tom = Person('tom')
cat = Person('cat')
print(tom.name,tom.age)
print(cat.name,cat.age)
print(Person.age)
Person.age = 30
print(Person.age,tom.age,cat.age)
tom 3
cat 3
3
30 30 30
实例变量是每一个实例自己的变量,是自己独有的。类变量是累的变量,是类的所有实例共享和使用的方法。
特殊属性 |
含义 |
__name__ |
对象名 |
__class__ |
对象的模型 |
__dict__ |
对象的属性的字典 |
__qualname__ |
类的限定名 |
Python中每一种对象都拥有不同的属性,函数、类、都是对象,类的实例也是对象。
class Person:
age = 3
def __init__(self,name):
self.name = name
print('class-----------')
print(Person.__class__)
print(sorted(Person.__dict__.items()),end='\n\n')
tom = Person('tom')
print('----instance tom ----')
print(tom.__class__)
print(sorted(tom.__dict__.items()),end='\n\n')
print('-----tom class------')
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()),end= '\n\n')
class-----------
<class 'type'>
[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000000FD0DFF52F0>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]
----instance tom ----
<class '__main__.Person'>
[('name', 'tom')]
-----tom class------
Person
[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000000FD0DFF52F0>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]
class Person:
age = 3
hight = 170
def __init__(self,name,age=18):
self.name = name
self.age = age
tom = Person('tom')
cat = Person('cat',20)
Person.age = 30
print(Person.age,tom.age,cat.age)
print(Person.hight,tom.hight,cat.hight)
cat.hight = 175
print(Person.hight,tom.hight,cat.hight)
tom.hight +=10
print(Person.hight,tom.hight,cat.hight)
Person.hight +=15
print(Person.hight,tom.hight,cat.hight)
Person.weight = 70
print(Person.weight,tom.weight,cat.weight)
print(tom.__dict__['hight'])
# print(tom.__dict__['weight']) 不可执行,因为后来定义的字典内没有
30 18 20
170 170 170
170 170 175
170 180 175
185 180 175
70 70 70
180
总结:
是类的,也就是这个类的所有实例,其实例都可以访问的到,是实例的,就是这个实例自己,通过类访问不到。
类变量是属于类的变量,这个类的所有实例可以共享这个变量。
实例可以动态的给自己增加一个属性。实例.__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找。
注意,如果实例使用__dict__[变量名]访问变量,将不会按照上面的顺序查找变量了,这是指明使用字典的key查找,不是属性来查找。
一般来说,类变量使用全部大写来命名。
特殊属性
实例的字典里面放的是和self有关的信息,类字典里面放的是类的信息,类的字典的items。
属性访问的:的找自己的找不到自己的找类的。
字典访问的:指定的是自己的字典。
赋值即定义,定义属性。
Person().normal()实例调用。必须传参数。不用。
Person.normal()类调用。
10、装饰一个类
def add_name(cls):
cls.NAME = name
return cls
@add_name('tom')
class Person:
AGE = 3
def add_name(name):
def wrapper(cls):
cls.NAME = name
return cls
return wrapper
@add_name('tom')
class Person:
AGE = 3
11、类方法和静态方法。
__init__等方法,这些方法的本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类必须实例化以后,由实例来调用这个方法。
1)普通函数:
class Person:
def normal_method():
print('normal')
# Person.normal_method() #可以调用
# Person().normal_method() #不可以调用
print(Person.__dict__)
Person.normal_method()
可以被调用,因为这个方法只是被Person这个名词空间管理的一个普通方法,normal只是一个属性而已。
由于normal_method在定义的时候没有指定self。所以不能完成实例对象的banding,不能用,Person().Normal_method()绑定。 虽然语法对的,但是禁止这么写。
2)类方法
class Person:
@classmethod
def class_method(cls):
print('class={0.__name__}({0})'.format(cls))
cls.HIGHT = 170
Person.class_method()
print(Person.__dict__)
class=Person(<class '__main__.Person'>)
{'__module__': '__main__', 'class_method': <classmethod object at 0x000000C59D4A3908>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HIGHT': 170}
(1)在类定义中,使用@clasmethod装饰器修饰的方法,
(2)必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身。
(3)cls这个标示符可以是任意合法名称,但是为了易读,不建议修改。
(4)通过cls可以直接操作类的属性。
(3)静态方法。
class Person:
@classmethod
def class_method(cls):
print('class={0.__name__}({0})'.format(cls))
cls.HIGHT = 170
@staticmethod
def static_method():
print(Person.HIGHT)
Person.class_method()
Person.static_method()
print(Person.__dict__)
class=Person(<class '__main__.Person'>)
170
{'__module__': '__main__', 'class_method': <classmethod object at 0x000000E7C4993908>, 'static_method': <staticmethod object at 0x000000E7C49938D0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HIGHT': 170}
(1)staticmethod 静态方法
(2)不需要传参。
(3)在类定义中,使用@staticmethod装饰器修饰的方法。
(4)调用时不会隐身传入参数,静态方法,只是表明这个方法属于这个名词空间,函数归在一起,方便管理。
12、方法的调用
class Person:
def normal_method():
print('normal')
def method(self):
print('{} method'.format(self))
@classmethod
def class_method(cls):
print('class={0.__name__}({0})'.format(cls))
cls.HIGHT = 170
@staticmethod
def static_method():
print(Person.HIGHT)
print('---类访问---')
print(1,Person.normal_method()) # 可以调用,返回None。
# print(2,Person.method()) #不可以被调用,必须传入参数,一个self
print(3,Person.class_method()) ##返回None
print(4,Person.static_method()) #返回None
print(Person.__dict__)
print('实例访问')
print('tom')
tom = Person()
# print(1,tom.normal_method()) #不能调用,报错 需要参数
print(2,tom.method()) #可以
print(3,tom.class_method()) #可以
print(4,tom.static_method()) #可以
总结:
类几乎可以调用所有内部定义的方法,但是调用普通方法的时候会报错,原因是第一参数必须是类的实例。
实例几乎可以调用所有,普通的函数的调用一般不可能出现,因为不允许定义。
类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。
实例可以调用所有类中定义的方法,(静态,类方法),普通方法传入实例本身,静态方法和类方法需要找到实例的类。
13、访问控制
1)私有属性(private)
class Person:
def __init__(self,name,age=18):
self.name = name
self.age = age
def growup(self,i=1): #控制逻辑
if i>0 and i <150:
self.age +=1
p1 = Person('tom')
p1.growup(20)
p1.age =160
print(p1.age)
想通过方法来控制属性,但是由于属性在外部可以访问,就直接绕过方法,直接修改属性。私有属性则解决了这个问题。
私有属性:
使用双下划线开头的属性名。就是私有属性。
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def growup(self,i=1): #控制逻辑
if i>0 and i <150:
self.__age +=1
p1 = Person('tom')
p1.growup(20)
# p1.age =160
print(p1.__age)
print(p1.__age)
AttributeError: 'Person' object has no attribute '__age'
访问不到 __age ..
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def growup(self,i=1): #控制逻辑
if i>0 and i <150:
self.__age += 1
def getage(self):
return self.__age
p1 = Person('tom')
print(p1.getage())
Print 19
2)私有属性的本质,
外部访问不到,能够动态增加一个__age?
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def growup(self,i=1): #控制逻辑
if i>0 and i <150:
self.__age += 1
def getage(self):
return self.__age
p1 = Person('tom')
p1.__age =28
print(p1.__age)
# print(p1.getage())
年龄没有被覆盖,全部存在__dict__的字典里面了。
私有变量本质:
类定义的是,如果生命一个实例变量的时候,使用双下划线,python的解释器就会将其改名,转换为_类名__变量名 的名称了,所以用原来的名字访问不到了。
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def growup(self,i=1): #控制逻辑
if i>0 and i <150:
self.__age += 1
def getage(self):
return self.__age
p1 = Person('tom')
p1.__age =28
print(p1.__age)
# print(p1.getage())
p1._Person__age =15
print(p1.getage())
print(p1.__dict__)
28
15
{'name': 'tom', '_Person__age': 15, '__age': 28}
##
3)保护变量
在变量名前面使用一个下划线,称为保护变量。
class Person:
def __init__(self,name,age = 18):
self.name = name
self._age = age
tom = Person('tom')
print(tom._age)
print(tom.__dict__)
18
{'name': 'tom', '_age': 18}
_age属性没有改变名称,和普通属性一样,解释器不做任何处理,只是开发者共同的约定,看见这样的变量,就如同私有变量,不直接使用。
4)私有方法
参展保护变量,私有变量,使用单下划线,双下划线命名方法。
class Person:
def __init__(self,name,age =18):
self.name = name
self._age = age
def _getname(self):
return self.name
def __getage(self):
return self._age
tom = Person('tom')
print(tom._getname()) ##没改名
# print(tom.__getage()) ##m没有次属性
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._Person__getage()) #改名了
tom
{'name': 'tom', '_age': 18}
{'__module__': '__main__', '__init__': <function Person.__init__ at 0x000000F1ECDB52F0>, '_getname': <function Person._getname at 0x000000F1ECDB5620>, '_Person__getage': <function Person.__getage at 0x000000F1ECDB56A8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
18
5)私有方法的本质
当下划线的方法只是开发者之间的约定,解释器不做任何改变
双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,_类名__昂发名。
方法变量都在类的 __dict__ 里面可以找到。
6)私有成员总结;
在python中使用_单下划线或者_双下划线来标示一个成员被保护或者被私有化隐藏起来,但是不管使用什么样的访问控制,都不能真正的组织用户修改类的成员,python中没有决定的安全环保成员或者私有成员。
因此,前道的下划线只是一种警告或者提醒,要遵守这个约定,除非有必要,否则不能修好或者使用保护成员或者私有成员,更不能修改。
14、属性装饰器
一般很好的设计是:把实例的属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age =age
@property
def age(self):
return self.__age
@age.setter
def age(self,age):
self.__age = age
tom = Person('tom')
print(tom.age)
tom.age = 20
print(tom.age)
使用property装饰器的时候,三个方法必须同名。
Property装饰器:
后面跟的函数名就是以后的属性名,他就是getter,这个必须有,有了它至少有了只读属性。
Setter装饰器
与属性名同名,且接受两个参数,第一个是self,第二个是将要赋值的值,有了它,属性可写。
Deleter装饰器
可以控制是否删除属性,很少用。
Property装饰器必须在前,setter 和 delter装饰器在后面。
Property装饰器能通过简单的方式,吧对方法的操作变成对属性的访问,并起到了一定的隐藏效果。
其它写法:
class Person:
def __init__(self,name,age =20):
self.name = name
self.__age = age
def getage(self):
return self.__age
def setage(self,age):
self.__age =age
def delage(self):
del self.__age
print('del')
age = property(getage,setage,delage,'age property')
tom = Person('tom')
print(tom.age)
tom.age = 25
del tom.age
class Person:
def __init__(self,name,age=20):
self.name =name
self.__age = age
age = property(lambda self:self.__age)
tom = Person('tom')
print(tom.age)
可读、可写。
15、补丁。
可以通过修改或者替换类的成员。使用着调用的方式没有改变,但是,类提供的功能可以已经改变。
猴子补丁:
在运行时,对属性,方法,函数等进行动态替换。
其目的是为了替换,修改来增强,扩展原有的代码的能力。
慎重使用。
上例中,假设Person类和get_score方法是从数据库中拿数据,但是测试的时候不方便。
使用猴子补丁的方法,替换了get_score方法,返回模拟的数据。
16、对象的销毁。
__del__ 方法,称为析构函数(方法)
作用:销毁类的时候调用,以释放占用的资源,其中就放些清理资源的代码。比如释放链接。
这个方法不能让对象真正的小虎,只是对象销毁的时候会自动调用它。
使用del的语句删除实例,引用计数减1,当引用计数为0时候,会idong调用__del__方法。
import time
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def __del__(self):
print('delete{}'.format(self.name))
def test():
tom = Person('tom')
tom.__del__()
tom.__del__()
tom.__del__()
print('=======')
tom1 = tom
tom3 = tom1
print(1,'del')
del tom
time.sleep(3)
print(2,'del')
del tom3
time.sleep(3)
print('-----')
del tom3
time.sleep(3)
print('........')
test()
由于垃圾回收对象销毁时候,才会真正清理对象,还会再之间自动调用__del__ 方法。除非知道自己的目的,否则不会手动清理;不会手动调用这个方法。
17、方法重载。
Python没有重载,不需要重载
Python中,方法(函数)定义中,形参非常灵活,不需要指定类型(就算指定了也只是说明一个说明而非约束,),形参个数也不固定(可变参数),一个函数的定义可以实现很多种不同形式实参的调用,所哟python不需要方法重载。
或者说python本身就是实现了其他语言的重载。
18、封装:
面向对象的三要素之一,封装。
封装:
将数据和操作组织到类中,即属性和方法。
将数据隐藏起来,给使用者提供操作(方法),使用者通过操作就可以获取或者修改数据。
Getter和setter。
通过控制访问,暴漏给适当的数据和操作给用户,该隐藏的隐藏起来,例如保护成员或者私有成员。
19、习题:
1)随机整数生成类
##初步代码:
import random
class Ran:
def __init__(self,start=0,end=100,length=10):
self.start = start
self.end = end
self.length = length
def array(self):
lst= [random.randint(self.start,self.end)for i in range(self.length)]
print(lst)
j = Ran()
j.length=5
j.array()
###第二步改进控制打印长度的
import random
class Ran:
def __init__(self,start=0,end=100,length=10):
self.start = start
self.end = end
self.length = length
def array(self,length=0):
length = self.length if length<=0 else length
lst= [random.randint(self.start,self.end)for i in range(length)]
print(lst)
j = Ran()
j.array(20)
####三,利用工具实现的方式
import random
class Ran:
@classmethod
def array(cls,start = 1,end =100,length =10):
return [random.randint(start,end)for _ in range(length)]
a = Ran()
print(a.array())
###利用生成器
import random
class Ran:
def __init__(self,start=0,end=100,length=10):
self.start = start
self.end = end
self.length = length
self._gen = self._array()
def _array(self):
while True:
yield random.randint(self.start,self.end)
def array(self,length=0):
if length <= 0:
return [next(self._gen)for _ in range(self.length)]
else:
return [next(self._gen)for i in range(length)]
j = Ran()
print(j.array(20))
print(j.array())
import random
class Ran:
def __init__(self,start=0,end=100,length=10):
self.start = start
self.end = end
self.length = length
self._gen = self._array()
def _array(self):
while True:
yield [random.randint(self.start,self.end)for _ in range(self.length)]
def array(self,length=0):
if length > 0:
self.length = length
return next(self._gen)
j = Ran()
print(j.array(20))
print(j.array())
2)利用上次的随机数生成20次,两两组和,打印坐标。
import random
class Ran:
def __init__(self,start=0,end=100,count=20):
self.start = start
self.end= end
self.count = count
def array(self,count=0):
count = self.count if count<=0 else count
lst = [random.randint(self.start,self.end)for i in range(count)]
return lst
a = Ran()
a.array()
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
# points = zip(a.array(),a.array())
# for x in points:
# print(x)
points = [Point(x,y)for x,y in zip(a.array(),a.array())]
for p in points:
print('{}:{}'.format(p.x,p.y))
3)车辆信息
class Car:
def __init__(self, mark, color, price, speed, **kwargs):
# self.id = genid
self.mark = mark
self.color = color
self.price = price
self.speed = speed
self.__dict__.update(kwargs)
class CarInfo:
cars = []
def addcar(self,car:Car):
self.cars.append(car)
def getall(self):
return self.cars
c1 = CarInfo()
car = Car('audi','red',100,100)
c1.addcar(car)
print(c1.getall())
4、实现温度的处理。
class Temp:
def __init__(self,t,unit='c'):
self._c = None
self._f = None
self._k =None
#都先转换为摄氏温度,然后以后访问调用计算其他的温度的值
if unit == 'k':
self._k = t
self._c = self.k2c(t)
elif unit == 'f':
pass
else:
self._c = t
@property
def c(self):
return self._c
@property
def k(self):
pass
@property
def f(self):
pass
#温度转换
@classmethod
def c2f(cls,c):
return 9*c/5 +32
@classmethod
def f2c(cls,f):
return 5*(f-32)/9
@classmethod
def c2k(cls,c):
return c +273.15
@classmethod
def k2c(cls,k):
return k -273.15
@classmethod
def f2k(cls,f):
return cls.c2k(cls.f2c(f))
@classmethod
def k2f(cls,k):
return cls.c2f(cls.k2c(k))
print(Temp.c2f(40))
print(Temp.c2k(40))
print(Temp.f2c(40))
print(Temp.k2c(40))
print(Temp.k2f(40))
print(Temp.f2k(40))
t = Temp(37)
print(t.c,t.k,t.f)
5、模拟购物车购物
class Item:
def __init__(self,**kwargs):
self.__spec = kwargs
def __repr__(self):
return str(sorted(self.__spec.items()))
class Cart:
def __init__(self):
self.items = []
def additem(self,item:Item):
self.items.append(item)
def getallitems(self):
return self.items
mycart = Cart()
myphone = Item(mark='apple',menory='4G')
mycart.additem(myphone)
print(mycart.getallitems())
一、类的继承
1、概念;
1)面向对象的三要素之一,继承inheritance
人类和猫类都是继承动物类。
个体继承自父母,继承了父母的一部分特征,但也可以有自己的个性。
在面向对象的世界中,从父类中继承,就可以直接拥有父类的属性和方法,这个可以减少代码,增加多复用,子类可以定义自己的属性和方法。
class Animal:
def shout(self):
print('Animal shouts')
class Cat:
def shout(self):
print('Cat shouts')
class Dog:
def shout(self):
print('Dog shouts')
a = Animal()
a.shout()
c = Cat()
c.shout()
d = Dog()
d.shout()
Animal shouts
Cat shouts
Dog shouts
上面的例子的类虽然有关系,但是定义时候并没有建立这种关系,而是各自完成定义。。
class Animal:
def __init__(self,name):
self._name = name
def shout(self):
print('{} shouts'.format(self.__class__.__name__))
@property
def name(self):
return self._name
a = Animal('monster')
a.shout()
class Cat(Animal):
pass
cat = Cat('garfield')
cat.shout()
class Dog(Animal):
pass
dog = Dog('ahuang')
dog.shout()
print(dog.name)
上例可以看出,通过继承,猫类,狗类,直接继承了父类的属性和方法。
2)继承
Class cat(Animal)这种形式就是从父类继承,括号中写继承的类的列表。
继承可以让子类从父类获取特征(属性和方法)
父类Animal 就是cat的父类,也称为基类,超类。
子类
Cat 就是Animal的子类,也成为派生类。
2、定义
格式:
Class 子类名(基类1,[基类2,...........]):
语句块
如果类定义的时候,没有基类列表,等同于继承自object,在python3种,object类是所有对象的根基类。
Class A:
Pass
#等价于
Class A(object):
Pass
Python支持多继承,继承也可以多级。
查看继承的特殊属性和方法:
特殊属性和方法 |
含义 |
举例 |
__base__ |
类的基类 |
|
__bases__ |
类的基类元组 |
|
__mro__ |
显示方法查找顺序,基类的元组 |
|
mro()方法 |
同上 |
Int.mro() |
__subclasses__() |
类的子类列表 |
Int.__subclasses__() |
3、继承中的访问控制
class Animal:
__COUNT = 100
HEIGHT = 0
def __init__(self,age,weight,height):
self.__COUNT += 1
self.age = age
self.__weight = weight
self.HEIGHT = height
def eat(self):
print('{}eat'.format(self.__class__.__name__))
def __getweight(self):
print(self.__weight)
@classmethod
def showcount1(cls):
print(cls.__COUNT)
@classmethod
def __showcount2(cls):
print(cls.__COUNT)
def showcount3(self):
print(self.__COUNT)
class Cat(Animal):
NAME = 'CAT'
__COUNT = 200
# c = Cat() 函数参数错误,传参错误。
c = Cat(3,5,15)
c.eat() ##cat eat
print(c.HEIGHT) ##15
# print(c.__COUNT) ##私有不可访问
# c.__getweight() #私有不可访问
c.showcount1() #100因为__是改名的,在实例的类找不到,所以利用继承的。
# c.__showcount2() #不可以,私有的不能访问
c.showcount3() #101
print(c.NAME) # CAT
print('{}'.format(Animal.__dict__))
print('{}'.format(Cat.__dict__))
print(c.__dict__)
print(c.__class__.mro())
后四个print
1/{'_Animal__getweight': <function Animal.__getweight at 0x000000D34976B0D0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, 'HEIGHT': 0, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, 'showcount3': <function Animal.showcount3 at 0x000000D34976B268>, 'showcount1': <classmethod object at 0x000000D349769748>, 'eat': <function Animal.eat at 0x000000D349764BF8>, '_Animal__COUNT': 100, '__module__': '__main__', '__init__': <function Animal.__init__ at 0x000000D349764D08>, '_Animal__showcount2': <classmethod object at 0x000000D349769780>, '__doc__': None}
2/{'NAME': 'CAT', '__module__': '__main__', '_Cat__COUNT': 200, '__doc__': None}
3/{'_Animal__COUNT': 101, 'HEIGHT': 15, '_Animal__weight': 5, 'age': 3}
4/[<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]
从父类继承,自己没有的,就到父类中去找。
私有的都是不可以访问的,但是本质上依然是改了名字放在这个属性的类的__dict__ 中,知道这个新名称就可以直接找到这个隐藏的变量。
(类外部是不会被访问的,私有属性,内部可以。)
继承顺序:实例,自己的类,父类。继承类;
总结:
继承时候,公有的,子类和实例都可以随意访问,私有成员被隐藏,子类和实例不可直接访问,当私有变量所在的类内的方法都可以访问这个私有变量。
属性查找顺序:
实例的__dict__>> 类__dict__如果有继承==〉父类__dict__.
dir()来查看继承属性。
4、方法的重写、覆盖override
class Animal:
def shout(self):
print('Animal shouts')
class Cat(Animal):
#覆盖了父类的方法
def shout(self):
print('miao')
a = Animal()
a.shout()
c =Cat()
c.shout()
print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)
{}
{}
{'__dict__': <attribute '__dict__' of 'Animal' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None, 'shout': <function Animal.shout at 0x000000E832DF4D08>}
{'__module__': '__main__', '__doc__': None, 'shout': <function Cat.shout at 0x000000E832DF4BF8>}
class Animal:
def shout(self):
print('Animal shouts')
class Cat(Animal):
#覆盖了父类的方法
def shout(self):
print('miao')
#覆盖本身的方法,调用父类的方法
def shout(self):
print(super())
print(super(Cat,self))
super().shout()
super(Cat,self).shout() #等价于super()
# self.__class__.__bases__.shout(self)
a = Animal()
a.shout()
c =Cat()
c.shout()
print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)
Animal shouts
<super: <class 'Cat'>, <Cat object>>
<super: <class 'Cat'>, <Cat object>>
Animal shouts
Animal shouts
{}
{}
{'__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Animal' objects>, 'shout': <function Animal.shout at 0x0000001852BD4D08>, '__doc__': None, '__dict__': <attribute '__dict__' of 'Animal' objects>}
{'__module__': '__main__', '__doc__': None, 'shout': <function Cat.shout at 0x0000001852BDB0D0>}
Super()可以访问到父类的属性。
class Animal:
@classmethod
def class_method(cls):
print('class_method_animal')
@staticmethod
def static_method():
print('statice_method_animal')
class Cat(Animal):
@classmethod
def class_method(cls):
print('class_method_cat')
@staticmethod
def static_method():
print('statice_method_animal')
c = Cat()
c.class_method()
c.static_method()
class_method_cat
statice_method_animal
这些都可以进行被覆盖,原因是字典的搜索顺序。
5、继承中的初始化
上图中类B定义时候生命继承自类A,则在类B中__base__中是可以看到A的
但是这是和调用类A的构造方法是两回事。
如果B中调用了A的构造方法,就可以拥有父类的属性了。
class A:
def __init__(self,a,b=1):
self.a = a
self.__b = b
class B(A):
def __init__(self,b,c):
A.__init__(self,b + c,b -c )
self.b = b
self.c = c
def printv(self):
print(self.b)
print(self.a)
f = B(4,5)
print(f.__dict__)
print(f.__class__.__bases__)
f.printv()
{'c': 5, 'a': 9, '_A__b': -1, 'b': 4}
(<class '__main__.A'>,)
4
9在B中调用了父类的__init__函数。 如果父类中定义了__init__,子类中就应该调用他。
如果利用自动调用父类的__init__方法呢。
class A:
def __init__(self):
self.a1 = 'a1'
self.__a2 = 'a2'
class B:
pass
b =B()
print(b.__dict__)
子类没有自己的属性和方法,直接继承。。。会自动调用的。
此种类型不会自动调用,需要手动处理调用:
class A:
def __init__(self):
self.a1 = 'a1'
self.__a2 = 'a2'
print('Ainit')
class B(A):
def __init__(self):
self.b1 ='b1'
print('B init')
b =B()
print(b.__dict__)
B init
{'b1': 'b1'}
正确初始化。
class A:
def __init__(self,age):
print('Ainit')
self.age = age
def show(self):
print(self.age)
class B(A):
def __init__(self,age,weight):
print('Binit')
self.age = age +1
self.weight = weight
super().__init__(age)
b = B(10,5)
b.show()
调用父类的__init__方法,出现在不同的位置,导致出现的结果不同。
class A:
def __init__(self,age):
print('Ainit')
self.__age = age
def show(self):
print(self.__age)
class B(A):
def __init__(self,age,weight):
print('Binit')
self.__age = age +1
self.__weight = weight
super().__init__(age)
b = B(10,5)
b.show()
打印出10,原因在__dict__,父类A的show方法中__age h会被解释为_A__age因此显示的就是10,而不是11.
自己私有的属性,就该自己读取和修改,不要借助其他类的方法,即使是父类或者派生类。
继承中的初始化
(继承加覆盖就是多态。)
二、多继承
1、Python不同的版本的类
Python2.2之前是没有共同祖先的,之后,引入object的类,他是所有类的共同 的祖先object。
Python2中为了兼容,分为古典类(旧式类)和新式类。
Python3中全部都是新式类。
新式类都是继承自object的,新式类可以使用super。
2、Ocp原则
多用继承,少用修改。
继承的用途,增强基类,实现多态。
多态:
在面向对象中,父类,子类通过继承联系在一起,如果通过一套方法,就可以实现不同的表现,就是多态。
一个类继承自多个类就是多继承,他将会有多个类的特征。
3、多继承弊端
多继承很好的模拟了世界,因为事物很少是单一继承的,但是舍弃简单,必然引入复杂性,带来了冲突。
如同一个孩子继承了来自父母双方的特征,那么到底眼睛像爸爸还是妈妈呢,孩子究竟该谁多一点呢。
多继承的实现会导致编译器设计的复杂度增加,所以很多语言舍弃了类的多继承。
C++支持多继承,JAVA也舍弃了多继承。
Java中,一个类可以实现多个接口,一个接口也可以继承多个解耦,Java的接口很纯粹,只是方法的生命。继承者必须实现这些方法,就具有了这些能力,就能干什么。
多继承可能会带来二义性,例如,猫和狗都继承自动物类,现在如果一个类多继承了猫和狗类,猫和狗都有shout方法,子类究竟继承谁的shout呢。
解决的方案。
实现多继承的语言,要解决二义性,深度优先或者广度优先。
4、Python多继承实现。
Class ClassName(基类列表)
类体
左图是多继承,右图是单一继承。
多继承带来路径选择问题,究竟继承哪个父类的特征呢。
Python使用MRO(method resolution order)解决基类搜索顺序问题。
历史原因:MRO有三个搜索算法:
经典算法,按照定义从左到右,深度优先策略,2.2之前左图的MRO是MYclass dbaca
新式算法,经典算法的升级,重复的是只保留最后一个,2.2左图的mro是myclassd,b,c,a,object
C3算法,在类被创建出来的时候,就计算出一个MRO有序列表,2.3之后,Python3唯一支持的算法。
左图中MRO是myclass,d,b,c,a,object的列表。
C3算法解决了多继承的二义性。
5、多继承的缺点
当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。
Python语法是允许很多继承的,但Python代码是解释执行的,只有执行到的时候,才会发现错误。
团队协作开发,如果引入多继承,name代码将会不可控。
不管编程语言是否支持多继承,都应避免使用多继承。
6、Mixin类 (继承加覆盖等于多态。)
类有下面的继承关系。
本质上是:多继承的来实现,体现的是组合的设计模式。
文档Document类是其他所有文档类的抽象基类;
Word PDF类是Document的子类。
为document子类提供打印能力:
方法:
1)在Document中提供print方法。
class Document:
def __init__(self,content):
self.content = content
def print(self):
raise NotImplementedError()
class Word(Document):pass
class Pdf(Document):pass
基类提供的方法不应该具体实现,因为他未必适合子类的打印,子类中需要覆盖重写。
Print是一种能力 ——打印功能,不是所有的Document的子类都需要,所有,从这个角度出发,会有问题的。
2)需要打印的子类上增加。
如果在现有子类上直接增加,违反了OCP原则,所以应该继承后增加。
##3采用多继承形式。
class Printable:
def print(self):
print(self.content)
class Document:
def __init__(self,content):
self.content = content
class Word(Document):pass
class Pdf(Document):pass
class PrintableWord(Printable,Word):pass
print(PrintableWord.__dict__)
print(PrintableWord.mro())
pw = PrintableWord('test str')
pw.print()
{'__doc__': None, '__module__': '__main__'}
[<class '__main__.PrintableWord'>, <class '__main__.Printable'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
test str
应用于网络,文档应该具备序列化的能力,类上就应该实现序列化。
可序列化还分为使用pickle,json,messagepack等
发现,类可能太多了,继承的方式很好。
功能太多,A需要几样功能,B需要几样功能,很繁琐。
3)装饰器。
使用装饰器增强一个类,把功能给类附加上去,那个类需要他,就去装饰他。
class Document:
def __init__(self,content):
self.content = content
class Word(Document):pass
class Pdf(Document):pass
def printable(cls):
cls.print = lambda self: print(self.content)
return lcs
@printable #printableWord=printable(printableword)
class PrintableWord(Word):pass
a = PrintableWord()
a.print('ls')
class Document:
def __init__(self,content):
self.content = content
class Word(Document):pass
class Pdf(Document):pass
def printable(cls):
def _print(self):
print(self.content)
cls.print = _print
return cls
@printable #printableWord=printable(printableword)
class PrintableWord(Word):pass
a = PrintableWord()
a.print('ls')
优点:简单方便,在需要的地方动态增加,直接使用装饰器。
4)Mixin
class Document:
def __init__(self,content):
self.content = content
class Word(Document):pass
class Pdf(Document):pass
class PrintableMixin:
def print(self):
print(self.content,'Mixin')
class PrintableWord(PrintableMixin,Word):pass
print(PrintableWordMixin.__dict__)
print(PrintableWordMixin.mro())
def printable(cls):
def _print(self):
print(self.content)
cls.print = _print
return cls
@printable #printableWord=printable(printableword)
class PrintableWord(Word):pass
pw = PrintableWord('test str')
pw.print()
6、Mixin类(混合) 实现的是组合。解决的是应该的是缺少什么样的功能。场景和使用范围是不一样的。不出现除了功能以外的属性方法等。(组合模式)
Mixin本质上就是多继承。
Mixin体现的是一种组合的设计模式。
Mixin体现在mro中。
在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能来自不同的类提供,需要好多类组合在一起。设计角度说多组合,少继承。
Mixin类的使用原则。
不应该显示的出现__init__初始化方法。
通常不能单独工作,因为他是准备混入别的类中的功能的实现。
Mixin类的祖先类应该是Mixin类。
使用时候,Mixin类通常在继承列表的第一个位置,class PrintableWord(PrintableMixin,Word):pass
人是具体的实例,必须进行实例化。登录网站,先抽象用户类,每个用户是独立的个体,每个人操作都是不同的。利用面向过程是难以实现的。
Mixin类和装饰器
这两种方式都可以使用,看个人喜好。
如果还需要继承就使用Mixin类的方式。