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_面向对象的主要内容,如果未能解决你的问题,请参考以下文章

python之路之前没搞明白4面向对象(封装)

面向对象_2-12选择题

js 面向对象代码

VSCode自定义代码片段12——JavaScript的Promise对象

VSCode自定义代码片段12——JavaScript的Promise对象

12_面向对象