12_面向对象
Posted q121211z
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了12_面向对象相关的知识,希望对你有一定的参考价值。
面向对象
一、面向对象
- 类(Class): 具有相同的属性和方法的对象的集合。定义每个对象所共有的属性和方法。对象是类的实例。
- 实例化:创建一个类的实例,类的具体对象(实例化的时候先开辟空间,再调用init,调用init的时候,总是把新开的空间作为参数传递给self)。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
- 方法:类中定义的函数。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量:定义在方法中的变量,只作用于当前实例的类。
命名空间
在类的命名空间里 :静态变量,绑定方法
在对象的明敏空间里: 类指针,对象的属性(实例变量)
-
静态变量:类中的变量就是静态变量
-
实例变量:在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
-
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
? 和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。
对象可以包含任意数量和类型的数据。
1、类定义
语法格式如下:
class ClassName:
<statement-1>
.
.
.
<statement-N>
类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。
二、类对象
类对象支持两种操作
? 属性引用和实例化。
? 属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name。类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:
class MyClass:
"""一个简单的类实例"""
i = 12345
def f(self):
return ‘hello world‘
# 实例化类
x = MyClass()
# 访问类的属性和方法
print("MyClass 类的属性 i 为:", x.i)
print("MyClass 类的方法 f 输出为:", x.f())
# 以上创建了一个新的类实例并将该对象赋给局部变量 x,x 为空的对象。执行以上程序输出结果为:
# MyClass 类的属性 i 为: 12345
# MyClass 类的方法 f 输出为: hello world
1、__init__(self):方法
类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:
def 类名:
#初始化函数,用来完成一些默认的设定
def __init__():
pass
__init__()方法
-
在创建一个对象时默认被调用,不需要手动调用
-
__init__(self)中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么__init__(self)中出了self作为第一个形参外还需要2个形 参,例如__init__(self,x,y)
-
__init__(self)中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递进去
-
类定义了 __init__() 方法,类的实例化操作会自动调用 __init__() 方法。如下实例化类 MyClass,对应的 __init__() 方法就会被调用:
x = MyClass()
当然, __init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。例如:
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i) # 输出结果:3.0 -4.5
- self代表类的实例,而非类,类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
class Test:
def prt(self):
print(self)
print(self.__class__)
t = Test()
t.prt()
# 以上实例执行结果为:
# <__main__.Test instance at 0x100771878>
# __main__.Test
从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的:
class Test:
def prt(runoob):
print(runoob)
print(runoob.__class__)
t = Test()
t.prt()
# 以上实例执行结果为:
# <__main__.Test instance at 0x100771878>
# __main__.Test
2、类的属性两种方式查看
- dir(类名):查出的是一个名字列表
- 类名.__dict__:查出的是一个字典,key为属性名,value为属性值
3、特殊的类属性
- 类名.__name__# 类的名字(字符串)
- 类名.__doc__# 类的文档字符串
- 类名.__base__# 类的第一个父类(继承)
- 类名.__bases__# 类所有父类构成的元组(继承)
- 类名.__dict__# 类的字典属性
- 类名.__module__# 类定义所在的模块
- 类名.__class__# 实例对应的类(仅新式类中)
三、类的方法
类定义
- 在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。
- 调用方法时,总是:实例化名.方法
class people:
#定义基本属性
name = ‘‘
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
# 实例化类
p = people(‘runoob‘,10,30)
p.speak()
# 执行以上程序输出结果为:
# runoob 说: 我 10 岁。
1、普通方法
- 默认有个self参数,且只能被对象调用。
2、类方法
- 默认有个 cls 参数,可以被类和对象调用,需要加上 @classmethod 装饰器。
什么时候用:定义一个方法,默认传self,但self没被使用。 - 想修改类中间的某一个变量,并且在实例化之前。
- 你在这个方法里用到了当前的类名,或者准备使用这个类的内存空间的名字的时候。
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def today(cls):
struct_t = time.localtime()
date = cls(struct_t.tm_year, struct_t.tm_mon, struct_t.tm_mday)
return date
date = Date.today()
print(date.year) # 2020
print(date.month) # 6
print(date.day) # 4
3、静态方法
- 用 @staticmethod 装饰的不带 self 参数的方法叫做静态方法,类的静态方法可以没有参数,可以直接使用类名调用。
- 用途:在函数内部既不会用的self变量,也不会用到cls类时使用
class Classname:
@staticmethod
def fun():
print(‘静态方法‘)
C = Classname()
Classname.fun()
C.fun()
总结:
class Method:
method = ‘静态变量‘ # 所有对象共享的变量 由对象/类调用,不能重新赋值
class Method:
method = ‘静态变量‘ # 所有对象共享的变量 由对象/类调用,不能重新赋值
def func(self): # 自带self参数的函数 由对象调用
print("绑定方法")
@classmethod
def Cls_func(cls): # 自带cls参数的函数,由对象/类调用
print(‘类方法‘)
@staticmethod
def static_func(): # 普通函数 不带参数的函数,由对象/类调用
print(‘静态方法‘)
@property
def hello(self): # property属性 是个伪装成属性的方法,由对象调用,不加括号
return ‘Hello‘
四、魔法方法
类的专有方法:
__init__ : 构造函数,在生成对象时调用
__call__: 函数调用
__len__: 获得长度
__repr__ : 打印,转换
__eq__: 相等,判断两个对象是否相等,执行(a == b)时会调用__eq__()方法
__gt__: 判断大于
__lt__: 判断小于
__del__ : 析构函数,释放对象时使用
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__cmp__: 比较运算
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__truediv__: 除运算
__mod__: 求余运算
__pow__: 乘方
__init__(): 初始化魔术方法
- 触发时机:初始化对象是触发(不是实例化对象,但和实例化在一个操作中)
def __init__(self,name,age):
print("----------------->init")
self.name = name
self.age = age
__call__():对象调用方法
- 触发时机:当对象被当成函数使用时,会默认调用此函数的内容
def __call__(self,name):
print("This is a function",name)
__len__() : 获取对象长度
- 触发机制:使用len()函数获取对象长度时,默认调用__len__()方法,没有就无法获取长度
def __len__(self):
return len(self.name)
__new__():实例化的魔术方法(构造方法)
-
触发时机:在实例化时触发,(在创建一块对象空间时,有一个指针指向类,会先调用__new__(),再调用__init__())
-
用途:设计模式中的单例模式
def __new__(cls,*args,**kwargs): # 向内存要空间 --> 地址
print("------------->new")
return object.__new__(cls)
__str__():打印
- 触发时机:打印对象名时自动触发,注意一定要在__str__()中加return
- 单纯打印对象名称时,会出来一个地址
- 如果想在打印对象名是给开发者更多的信息时,使用
def __str__(self):
return "对象名字是:" + self.name + ",年龄是:" + str(self.age)
__repr__() : 打印,转换
触发机制:类似__str__(),str(对象)总是调用对象的__str__方法,如果类中有__str__,优先返回__str__的内容,没有在根据__repr__()的内容返回
如果想直接返回__repr__()中的内容,可以使用 %r 字符串拼接,或者repr(对象)
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name
def __repr__(self):
return self.name + ‘,年龄:‘ + str(self.age)
student = Student(‘张三‘, 18)
print(str(student)) # 张三
print(‘学生:%s‘ % student) # 学生:张三
print(repr(student)) # 张三,年龄:18
print(‘学生:%r‘ % student) # 学生:张三,年龄:18
del():析构魔术方法
- 触发时机:当对象没有用(没有任何变量引用)时被触发
运算符重载
Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return ‘Vector (%d, %d)‘ % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
# 以上代码执行结果如下所示:
# Vector(7,8)
五、封装
- 广义:把属性和方法装起来,外面不能直接调用,要通过类的名字调用
- 狭义:在属性和方法藏起来,外面不能调用,只能在内部调用
数据的数据级别有哪些:
- Public:共有的,类内类外都将能用,父类子类都能用(Python支持)
- Project:保护的,类内能用,父类子类能用,类外不能用(python不支持)
- Private:私有的,本类的类内能用,其他地方不能用(Python支持)
1、私有属性和私有方法
应用场景
- 在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到。
- 私有属性就是对象不希望公开的属性
- 私有方法就是对象不希望公开的方法
定义方式
在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有对象属性或方法(私有实例变量),self.__age = 18就是私有属性,而self.age = 18就不是私有属性,同样def __secret(self):是私有绑定方法,def secret(self):不是私有方法
class Women:
def __init__(self, name):
self.name = name
# 不要问女生的年龄
self.__age = 18
def __secret(self):
print("我的年龄是 %d" % self.__age) # 在类内部使用时,自动把类的名字拼接在私有变量前完成变形
xiaofang = Women("小芳")
# 私有属性,外部不能直接访问
# print(xiaofang.__age)
# 私有方法,外部不能直接调用
# xiaofang.__secret()
私有内容不能被子类使用
class Foo(object):
def __init__(self):
self.__func() # __func()已经被类内部改名为_Foo__func()
def __func(self):
print(‘in Foo‘)
class Son(Foo): # 初始化时到父类找__init__方法,调用的是_Foo__func()
def __func(self): # __func()已经被改名为:_Son__func()
print(‘in Son‘)
Son() # in Foo
2、伪私有属性和私有方法
? 访问对象的 私有属性 或 私有方法Python 中,并没有 真正意义 的 私有,在给 属性、方法 命名时,实际是对 名称 做了一些特殊处理,使得外界无法访问到,处理方式:在 调用的属性名或者方法名称 前面加上 _类名 => _类名__名称,在日常开发中,不要使用这种方式,
class Women:
def __init__(self, name):
self.name = name
# 不要问女生的年龄
self.__age = 18
def __secret(self):
print("我的年龄是 %d" % self.__age)
xiaofang = Women("小芳")
# 私有属性,外部不能直接访问(加上`_Women`就可以了)
# print(xiaofang._ Women__age)
# 私有方法,外部不能直接调用(加上`_Women`就可以了)
# xiaofang._Women__secret()
# 提示
# print(xiaofang.__age) => print(xiaofang._ Women__age)
# xiaofang.__secret() => xiaofang._Women__secret()
3、@property
用途:
- 用途一 : 把一个方法伪装成一个属性,在调用这个方法时不需要加()就可以调用(property装饰的方法.不能有参数)
- 用途二 : 和私有属性合作,使可以原名访问但不可修改
- 用途三 : 限制用户输入内容合理的更改属性
例一:
class Goods:
discount = 0.8
def __init__(self, origin_price):
self.__price = origin_price
@property
def price(self):
return self.__price * self.discount
@price.setter
def price(self, new_price):
self.__price = new_price
return self.__price
goods = Goods(10)
print(goods.price) # 8.0 调用的是被@property装饰的price
goods.price = 12 # 调用的是被setter装饰的price
print(goods.price)
删除被@property装饰的函数的属性时会报错
class Goods:
discount = 0.8
def __init__(self, origin_price):
self.__price = origin_price
@property
def price(self):
return self.__price * self.discount
@price.deleter
def price(self, new_price):
del self.__price
goods = Goods(10)
del goods.price # 并不能真正删除什么,只是调用@price.deleter函数
- 对于私有属性,可以在类内部定义get和set方法来获取私有属性的值,这样在设置年龄的时候就不能随意设置,可以在函数里写限制条件
def setAge(self,age):
self.__age = age
def getAge(self):
return self.__age
- 即想有限制条件,又想像以前一样直接调用则需要装饰器@property,来装饰get函数,在通过装饰的get函数调用相应的setter方法
@property
def age(self):
return self.__age
@age.setter
def age(self,age):
If age>0 and age<=120:
self.__age = age
else:
print("输入不符合要求!")
六、继承
1、has-a概念
has-a说的是一种包含关系,意思是说父类包含子类,比如人和心脏的关系,心脏属于人,但心脏并不是人。
# 学生类Student 和 书类Book
class Student:
def __init__(self,name,book):
self.name = name
self.book = book
class Book:
def __init__(self,name,author):
self.name = name
self.author = author
# 学生类中有book,学生和书直接的关系就是has a
2、is-a概念
is-a的意思是说:子类即父类。也就是子类在继承父类之后,并没有做任何异于父类的操作,比如并未添加新的内容。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print("{}正在吃饭.....".format(self.name))
class Doctor(Person):
pass
class Employee(Person):
pass
3、is-like-a概念
is-like-a的意思是说:子类继承父类,但是有添加了新的内容,子类并不完全等同于父类,而是与父类相似。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print("{}正在吃饭.....".format(self.name))
class Student(Person):
def __init__(self,name,age,clazz):
super().__init__(name,age) # 继承父类的__init__方法,重写子类__init__
self.clazz = clazz
def number(self):
print("班级名为:",self.clazz)
s = Student("Jack",18,"本计153班")
s.eat()
s.number()
4、多继承
class P1:
def foo(self):
print("p1---foo")
class P2:
def foo(self):
print("p2---foo")
def bar(self):
print("p2---bar")
class C1(P1,P2):
pass
class C2(P1,P2):
def bar(self):
print("c2---bar")
class D(C1,C2):
pass
?
查看多继承搜索顺序(C3算法)
- 方法一,导入 import inspect,inspect.getmro(“类名”)
- 方法二,D.__mro__
(<class ‘main.D‘>, <class ‘main.C1‘>, <class ‘main.C2‘>, <class ‘main.P1‘>, <class ‘main.P2‘>, <class ‘object‘>)- 元组,从左到右依次搜索,广度优先
5、super方法
- super是按照mro顺序来寻找当前类的下一个类
- 在python3中不需要传参数,自动寻找当前类的mro顺序的下一个类中的同名方法
- 在python2的新式类中,需要主动传递参数super(子类的名字,子类的对象).函数名()
- 在python2的经典类中不支持使用super
在新式类中可以写super().func()
? 也可以写super(D,self).func()
在单继承中,super就是找父类
class User:
def __init__(self, name):
self.name = name
class VIPUser(User):
def __init__(self, name, level):
# super().__init__(name) # 推荐
super(VIPUser, self).__init__(name)
self.level = level
6、抽象类
- 为了规范子类必须实现和父类同名方法
- 约束子类必须实现的方法
a、第一种方式实现,不需要模块(归一化时会抛出异常)
class Payment: # 抽象类 约束子类必须拥有重名方法
def pay(self, money):
raise NotImplementedError("没有实现相应的支付方法")
class WeChat(Payment):
def __init__(self, name):
self.name = name
def fuqian(self, money):
print(f"{self.name}支付了{money}")
def cost(name, price, kind):
if kind == "wechat":
wechat = WeChat(name)
wechat.pay(price)
cost(‘Jack‘, 200, ‘wechat‘) # 会抛出异常
b、第二种方式实现,需要模块abc(在实例化时没实现抛出异常)
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta): # 抽象类 约束子类必须拥有重名方法
@abstractmethod
def pay(self, money):
pass
class WeChat(Payment):
def __init__(self, name):
self.name = name
def fuqian(self, money):
print(f"{self.name}支付了{money}")
def cost(name, price, kind):
if kind == "wechat":
wechat = WeChat(name)
wechat.pay(price)
cost(‘Jack‘, 200, ‘wechat‘)
七、多态
归一化设计:
将同名功能进行统一调用
class A:
def 同名功能(self):
pass
class B:
def 同名功能(self):
pass
def 函数名(obj):
obj.同名功能()
1、多态
- 一个类型表现出来的多种状态(实际上使用过继承来完成的)。
支付表现出微信支付和苹果支付
在java中:一个参数必须制定类型,所以想让两个类型的对象多可以传,那么必须让两个类基础自一个父类,在确定类型时使用父类来指定。
2、在Python中并没有这类限制
- 首先要对数据类型说明。当我们定义一个class的时候,实际上就定义了一种数据类型。定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:
比如:
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型
- 判断一个变量是否是某个类型可以用isinstance()判断
isinstance(a, list) # True
isinstance(b, Animal) # True
isinstance(c, Dog) # True
# 看来a、b、c确实对应着list、Animal、Dog这3种类型。
isinstance(c, Animal) # True
-
c不仅仅是Dog,c还是Animal!因为Dog是从Animal继承下来的,当创建了一个Dog的实例c时,认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!
-
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:
b = Animal()
isinstance(b, Dog) # False
-
Dog可以看成Animal,但Animal不可以看成Dog。
-
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
def run_twice(animal):
animal.run()
animal.run()
# 当我们传入Animal的实例时:
run_twice(Animal())
# run_twice()就打印出
# Animal is running...
# Animal is running...
# 当我们传入Dog的实例时:
run_twice(Dog())
# run_twice()就打印出
# Dog is running...
# Dog is running...
当我们传入Cat的实例时:
run_twice(Cat())
# run_twice()就打印出
# Cat is running...
# Cat is running...
# 如果我们再定义一个Tortoise类型,也从Animal派生:
class Tortoise(Animal):
def run(self):
print(‘Tortoise is running slowly...‘)
# 当我们调用run_twice()时,传入Tortoise的实例:
run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...
# 会发现,新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
- 多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思
3、鸭子类型
-
形似:只要它走路像鸭子,有脚蹼,...那它就是鸭子类型
-
定义:满足特定的约定,就是鸭子类型
-
tupel:元组类 是可以哈希的,又不依赖继承哈希类来判定是不是可哈希类型,元组类是可哈希类型的鸭子类型
- 是可迭代的,不是依赖继承迭代类型来判定是不是迭代类型
-
所有实现了__len__方法的类,在调用len函数的时候,obj就说是鸭子类型
-
迭代器协议: 拥有 __iter__ __next__ 的方法就是迭代器
鸭子类型:
-
子类继承父类,子类也是父类这个类型
-
Python中一个类是不是属于某一个类型,不仅仅可以通过继承来完成
-
还可以不继承,但如果这个类满足了某些类型的特征条件,我们就说它长得像某个类型,那它就是这个类型的鸭子类型
以上是关于12_面向对象的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段12——JavaScript的Promise对象