Python——类、面向对象,封装 继承 多态

在面向对象的方法中,可以根据某些事物或者情景创建类,并基于这些类来创建对象。编写类时,定义一大类对象都有通用行为,基于类创建的对象,每个对象都自动具备这个类的通用行为。
根据类来创建对象被称为实例化。
在Python中编写类的方式如下:

class Dog():
    '''创建小狗的类'''
    def __init__(self,name,age):
        self.name = name
        self.age = age

    '''动作方法 蹲下'''
    def sit(self):
        print(self.name+'蹲下了'+self.age)

方法init()

init()是一个特殊的方法,每当根据类创建类的实例时,Python都会自动运行这个方法。在这个方法中开头和末尾各有两个下划线。这是一种约定。避免Python默认方法与普通方法发生名称冲突。
init()定义中我们传递了三个参数:self、name和age。这个初始化方法中self是必须的。而且还必须位于其他的形参前面。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用。让实例能够访问类中的属性和方法
init()方法中定义的两个变量都有前缀self。以self为前缀的变量都可供类中的所有方法使用。
init()没有并没有显式地包含return语句,但Python自动返回类对象的实例。

Python2.7创建类的方法

  • 在Python2.7中创建类
class ClassName(Object):
...

根据类创建实例

my_dog = Dog('dog1',22)
my_dog.sit()

访问属性

可以根据实例访问实例的属性。在执行时,Python会先找到实例,再查找与这个实例相关联的属性name.

print(my_dog.name)

为属性指定默认值

类中的每个属性都必须有初始值,我们可以为类的属性指定默认值。

class Car():
    def __init__(self, name, year, price):
        self.name = name
        self.year = year
        self.price = price
        self.owner = 'dos auto'
    def get_car_name(self):
        return self.name
    def get_car_woner(self):
        return self.owner
my_car = Car('benz', 11, 2000000)
print(my_car.get_car_name())
print(my_car.get_car_woner())

类的继承

在上面总结的类的定义的时候,我们声明类的时候是从空白开始的,如果我们要声明定义的类是继承某一个类可以在此指定要继承的父类

  • 定义子类时,必须在括号内指定父类的名称
  • 在创建子类的实例时,Python首先需要给父类的所有属性赋值。
  • 创建子类时,父类必须包含在当前文件中,且位于子类前面。
  • 子类中可以使用super()代表父类的引用。
class Car():
    def __init__(self, name, year, price):
        self.name = name
        self.year = year
        self.price = price
        self.owner = 'dos auto'

    def get_car_name(self):
        return self.name

    def get_car_woner(self):
        return self.owner
'''子类,电动汽车'''
class ElectricCar(Car):
    def __init__(self, name, year, price):
        super().__init__(name, year, price)
my_tesla = ElectricCar('tesla', 2, 200000)
print(my_tesla.get_car_woner())
print(my_tesla.get_car_name())

隐含的超类——object

每一个python类都隐含了一个超类:object,这个类是一个非常简单的定义,这个类几乎不做任何事情。对象生命周期的基础是创建、初始化和销毁。所有类的超类object都有一个默认包含pass的init()方法的实现。

继承中的init 方法

  • 子类可以不重写init方法,实例化子类时,会自动调用父类中已经定义的init
  • 如果子类中定义了init,也就是说子类重写了init,则不会再去自动调用父类中已定义的init方法
  • 最好显式调用超类的init方法

看几个栗子

class father1:
    def say_something(self,args):
        print(args)

class father2:
    def say_something2(self,args):
        print(args)

class son:
    def __init__(self):
        print(self)

s = son()
>>>
<__main__.son object at 0x101a45908>

上面的程序中,在构造son的实例对象时会自动调用son的已定义的init方法

'''
继承中的__init__
'''
class father1:
    def say_something(self,args):
        print(args)

class father2:
    def __init__(self):
        print("father2 init")
    def say_something2(self,args):
        print(args)

class son(father2):
    def __init__(self):
        print(self)

s = son()

上面的方法中,父类定义了init方法,但是子类重写了父类的init方法,这样程序就不会再调用父类的init方法了。除非显式调用父类的init方法:

class son(father2):
    def __init__(self):
        super(son,self).__init__()
        print(self)
s = son()

当子类中没有定义init方法,在构造子类实例的时候就会执行父类已经定义的init,注意:这里只会执行父类中已经定义的init方法,如果父类中没有定义init或者说没有重写init方法,会继续寻找父类中定义的init方法,一直到所有的父类Object

class father1:
    def say_something(self,args):
        print(args)

class father2:
    def __init__(self):
        print("father2 init")
    def say_something2(self,args):
        print(args)

class son(father2):
    pass

s = son()

多继承

Python支持多继承,我们知道子类会继承父类中的属性和方法。python中需要继承多个类的时候,使用如下的格式

class 子类(父类1,父类2):
    pass

那么当多个父类都含有相同的方法,会如何执行呢?

class father1:
    def say(self,args):
        print(args+'father1')


class father2:
    def say(self,args):
        print(args+'father2')

class son(father1,father2):
    pass
s = son()
s.say('it say something')

>>>
it say somethingfather1

可以看到,python会执行第一个父类中的相关方法
我们用图解来解释一下执行顺序


duojicheng.jpg

在有多个父类的情况下,会先去继承的第一个父类寻找 然后再去第2个寻找
那么,在有多重继承的情况下,并且有公共的基类的情况下呢?

class base:
    def say_base(self, args):
        print(args + 'base')


class father1(base):
    def say(self, args):
        print(args + 'father1')

    def say_base(self, args):
        print(args + 'father1')


class father2(base):
    def say(self, args):
        print(args + 'father2')
    def say_base(self, args):
        print(args + 'father2')
class son(father1, father2):
    pass
s = son()
s.say_base('it say something')

我们根据执行结果,可以看到程序会现在子类中查找要执行的方法和函数,然后会在第一个父类中寻找如果没有找到,会在第二个父类中寻找。如果也没有找到会去父类的公共基类找。图示

gonggongjilei.jpg

程序的执行顺序是 1 2 3

类成员

类成员修饰符

共有成员

  • 共有成员包括静态字段,普通字段,普通对象方法,静态方法,类方法

私有成员

  • 类的成员,如果使用__filename 作为定义,那么这个成员是私有的,无法通过外部对象直接访问,私有成员可以修饰字段、方法
  • 使用私有成员修饰的无法被继承,只能通过间接来访问私有的成员
class F:
    # 静态成员变量
    staticvar = 'static var'
    # 私有静态成员变量
    __privatevar = 'private var'

    def __init__(self, name, value):
        self.__objprivatevalue = name
        self.normalvalue = value

    def __privateMethod(self):
        print('private method')

    def visitPrivateFile(self):
        print(self.__objprivatevalue)

    @staticmethod
    def visitStaticFile():
        print(F.__privatevar)


# 直接通过对象访问普通字段
f = F('F class', 'normalValue')
print(f.normalvalue)  # 正常访问
# 直接访问静态字段
print(F.staticvar)  # 正常访问
# 直接访问普通私有字段
# print(f.__objprivatevalue) 无法访问
# 直接访问静态私有字段
# print(F.__privatevar) 无法访问
# 间接访问普通私有字段
print(f.visitPrivateFile())
# 间接访问静态私有字段
print(F.visitStaticFile())
  • 在继承中的私有字段
class s(F):
    def __init__(self):
        # 构造父类
        super(s, self).__init__('s of father', 'father value')
        self.s_name = 'son_name'

    def visitParent(self):
        # 访问子类普通字段
        print(self.s_name)
        # 访问父类普通字段
        print(self.normalvalue)
        # 访问父类私有普通字段
        # print(self.__objprivatevalue) 无法访问
        # 访问父类私有静态字段
        # print(F.__privatevar) 无法访问



# 直接通过对象访问普通字段
s = s()
s.visitParent()
# 调用继承的父类普通方法
s.visitPrivateFile()
# 调用继承的父类私有普通方法
# s.__privateMethod() 无法调用

字段 属性

普通字段

定义子类的属性和方法(普通字段 普通方法)

普通字段属于对象,保存在对象中,只有在创建对象的时候才会建立,每一个对象都会存储一份

  • 普通字段的创建
    新增子类的属性使用self代表指向当前对象的引用,使用self定义的属性创建的所有实例都将包含这个属性。但所有父类都不包含子类定义的属性和方法
class ElectricCar(Car):
    def __init__(self, name, year, price):
        super().__init__(name, year, price)
        self.battery = 70
    def get_currentbattery(self):
        print('current barrery is' + str(self.battery))
my_tesla = ElectricCar('tesla', 2, 200000)
my_tesla.get_currentbattery()
'''创建父类的对象'''
my_car = Car('farrire', 1, 50000)
  • 普通字段的访问
    • 普通对象只能通过对象访问,每一个对象都有自己的字段

静态字段

静态字段属于类,在内存中只保存一份
Python解释器是从上到下解释代码的,静态字段在解释类的时候会创建并且创建到类中。

  • 静态字段的创建
class class_file:
    name = '静态字段'
    
    def __init__(self,name):
        self.objname = name
    
  • 静态字段的访问
    • 通过类访问
print('静态字段'+class_file.name)
# print(class_file.objname)  报错,实例成员变量无法通过类访问
* 通过对象访问

可以通过对象访问静态的字段,通过对象更改静态字段的话,由于静态字段只有一份,所以通过对象更改静态字段会改变所有的

# 通过类对象访问
clf = class_file('类对象')
print(clf.name)

特殊成员

  • Python中有一个特殊的成员 str 可以将一个 对象转换为字符串,方便我们打印对象的相关信息,有点类似Java中的toString
  • 其它的特殊成员还包括
    • init 构造类对象的时候会自动执行
    • call 对象() 类名()() 会自动调用
    • int int(对象名)会自动调用
    • del 析构方法 会在对象被销毁时调用
class SpecialMembers:
    def __init__(self):
        self.arg1 = '123'
        self.arg2 = 'abc'
        self.arg3 = True
    '''
    在对象销毁的时候调用
    '''
    def __del__(self):
        print('obj destory')
sm = SpecialMembers()
print(sm.__dict__)
del sm
>>> 
obj destory
  • dict 将对象中封装的所有的内容通过字典的形式返回
class SpecialMembers:
    def __init__(self):
        self.arg1 = '123'
        self.arg2 = 'abc'
        self.arg3 = True
sm = SpecialMembers()
print(sm.__dict__)
>>> {'arg1': '123', 'arg2': 'abc', 'arg3': True}
  • str str() 返回字符串形式描述
class to_string:
    def __init__(self):
        self.__private_name =  'toString'
        self.name = 'toString_name'
    def __str__(self):
        return "%s:%s" % (self.__private_name,self.name)
# 创建 to_string 对象
ts = to_string()
print(ts)
  • getitem 获取item
  • setitem 为指定item设置值
  • delitem 删除某项

Python 中的特殊方法可以为普通对象赋予类似列表之类的功能,能够设置或获取某些项的值


class SpecialMembers:
    def __init__(self):
        self.arg1 = '123'
        self.arg2 = 'abc'
        self.arg3 = True

    def __getitem__(self, item):
        return item

    def __setitem__(self, key, value):
        print(key,value)

    def __delitem__(self, key):
        print(key)


sm = SpecialMembers()
# 此处会调用 __getitem__
print(sm[20])
# 调用__setitem__
sm[22] = 222
# 调用__delitem__
del sm[50]

  • iter

python中我们循环一个可迭代对象的过程是:执行对象的类中的iter方法,并获取返回值,然后循环上一步中返回的对象
如果一个类中有iter方法,那么这个类就是可迭代对象
循环时,可迭代对象会调用对象.iter()方法,然后再执行next方法进行下一次的迭代

class SpeciaIter:
    def __iter__(self):
        return iter(['a','b','c','d'])


si = SpeciaIter()
# 迭代这个可迭代对象
for i in si:
    print(i)

方法

普通方法

在类的定义中,以self作为作为第一个参数的方法都是实例方法。实例方法的首个参数是self,当这个方法被调用时,Python会把调用该方法的对象作为self参数传入

  • 定义普通方法
class func_obj:
    def bar(self):
        print('bar func')
  • 调用普通方法
    • 由对象调用
obj = func_obj()
obj.bar()
* 由类调用

由类调用时需要传入对象self

obj = func_obj()
# obj.bar()
func_obj.bar(obj)

静态方法

  • 静态方法的定义
    出现在类中既不会影响类也不会影响类的对象。这种类型的方法被称为静态方法。静态方法用@staticmethod修饰,当方法被@staticmethod修饰时,方法中的self参数就不是必须的。
class staticdemo():
    @staticmethod
    def show():
        print("it's staticdemo")
staticdemo.show()
  • 静态方法的调用
    静态方法可以使用类名调用
func_obj.static_bar()

类方法

  • 类方法的定义
    类方法会作用于整个类,在类定义内部,用前缀修饰符@classmethod指定的方法都是类方法,类方法的第一个参数是类本身。在Python中,这个参数常被协写作cls,class为保留字无法使用。
class A():
    count = 0
    @classmethod
    def kids(cls):
        print("Class A is call" + str(cls.count))
  • 类方法的调用
    类方法是作用域整个类的方法,可以使用类调用
func_obj.class_bar()

属性

属性定义时和方法类似,可以有参数可以有返回值,调用时和字段类似,可以使得方法的调用和访问字段同等效果,某种意义上可以简化代码结构

  • 属性的定义
    '''定义属性'''
    @property
    def propert(self):
        print('propert')
        return 111;
  • 属性的调用
obj = func_obj()
print(obj.propert)

利用属性模拟一个简单分页的栗子

class pageinfo:
    def __init__(self, current_page):
        self.page = int(current_page)

    @property
    def start(self):
        val = (self.page - 1) * 10
        return val

    @property
    def end(self):
        val = self.page * 10
        return val


# 定义一个空列表 用于存储分页内容
li = []

# 填充数据

for i in range(1000):
    li.append(i)

# 与用户交互
while True:
    p = input('请输入要查看的页码')
    obj = pageinfo(p)
    print(li[obj.start:obj.end])

重写父类的方法

对于父类的方法,只要它不符合子类模拟的实物的行为,都可以对其重写。
在car类中我们定义了一个返回该车拥有者的方法
get_car_woner,但是每辆车的拥有者是不一样的,所以我们可以使用子类重写父类的方法

class ElectricCar(Car):
    def __init__(self, name, year, price):
        super().__init__(name, year, price)
        self.battery = 70
    def get_currentbattery(self):
        print('current barrery is' + str(self.battery))
    def get_car_woner(self):
        print('this is tesla')
my_tesla = ElectricCar('buggadi', 1, 200000)
'''调用重写后的父类的方法'''
my_tesla.get_car_woner()

Python中的 metaclass 超类对象

  • Python中一切事物都是对象
  • Python中的类也是一个对象,是Type类的对象。Python在编译器编译class的时候也会被编译为一个对象
print(type(SpeciaIter))
>>> <class 'type'>

metaclass

  • mateclass可以指定自定义的Type类,由自定义的Type类来创建类

我们已经知道,Python中的类都是Type类的对象,也就是说Python中的类都是由Type创建的,那么如果我们自己创建一个类继承Type 那么这个类也可以进行创建类。

class MyTpe(type):
    def __init__(self,*args,**kwargs):
        print(123)
class Foo(object, metaclass=MyTpe):
    def __init__(self):
        pass
f = Foo()
>>> 123
  • 创建对象的流程


    创建过程.png

命名元组

命名元组时元组的子类,既可以通过名称(使用.name)来访问其中的值,也可以通过位置进行访问(使用[offset])

将一个类的实例用作另一个类的属性

在我们使用代码模拟实物时,可以封装一些类的属性细节,我们可以将类的一部分作为一个独立的类提取出来,这样我们可以将大型类拆分成多个协同工作的小类。
例如上述的demo中,对于电动车,有很多针对于汽车电瓶的属性和方法,我们可以创建一个Battery的类,来描述这些类的属性。并将Battery的实例用作ElectricCar类的一个属性

class Battery():
    def __init__(self, size=70):
        '''初始化电池容量'''
        self.size = size

    def get_battery_size(self):
        print('当前电池容量' + str(self.size))

  • 在调用ElectricCar的Battery属性
my_tesla = ElectricCar('buggadi', 1, 200000)
my_tesla.battery.get_battery_size()

Python会在实例my_tesla中查找属性battery,并对存储在该属性中的Battery实例调用get_battery_size()方法

导入类

我们可以将类封装成模块,在其他类中使用,这样就需要使用导入类的功能

导入单个类

from part2.classdemo.car import Car

import会让Python打开模块car并导入其中的Car类

  • 在一个模块中存储多个类
  • 从一个模块导入多个类
    可根据需要在程序文件中导入任意数量的类。从一个模块中导入多个类时,用逗号分隔导入的个各类。导入必要的类后,就可以根据需要创建每个类的任意数量的实例
from part2.classdemo.car import Car, ElectricCar
  • 导入整个模块
    我们还可以导入整个模块,再用句点表示法访问需要的类
import car
  • 导入模块中的所有类
from module_name import *

类编码风格

  • 类名应采用驼峰命名法,将类名中的每个单词的首字母都大写,而不使用下划线。实例名和下划线都使用小写格式,并在单词之间加下划线
  • 对于每个类,都应紧跟在类定义后面包含一个文档字符串,这个字符串要简单的描述类的功能
  • 在类中可使用一个空行来分隔方法,在模块中,使用两个空行来分隔类
  • 先导入标准库,再导入自己编写的模块

使用属性对特性进行访问和设置

有一些面向对象的语言支持私有特性。这些特性无法从对象外部直接访问。需要编写getter和setter
python不需要getter和setter方法,Python中所有特性都是公开的。如果需要直接访问对象的特性,可以为对象编写setter和getter方法。但是比较好的方法时使用属性property
举个例子,我们定义一个Duck类,仅包含一个hidden_name特性,我们不希望别人直接访问这个特性,因此需要定义两个方法 getter和setter方法。

class duck():
    def __init__(self, inputname):
        self.hiddenname = inputname

    '''getter & setter'''
    
    
    def get_name(self):
        print('get_name')
        return self.hiddenname

    def set_name(self, name):
        print('set_name')
        self.hiddenname = name

    name = property(get_name, set_name)

在最后一行中,我们使用property()方法将get_name和set_name定义为了name属性。当尝试访问duck类对象的name特性时,get_name()会被自动调用:

test = duck('test123')
print(test.name)
>>> get_name
>>> test123

显式调用get_name()方法:

test = duck('test123')
print(test.get_name())
  • 还可以使用修饰符
    • @property 用于指示 getter方法
    • @function.setter 用于指示setter方法

    @property
    def get_name(self):
        print('get_name')
        return self.hiddenname

    @get_name.setter
    def set_name(self, name):
        print('set_name')
        self.hiddenname = name

  • 调用get方法
test = duck('test123')
print(test.get_name)
  • 调用setter方法
test = duck('test123')
test.set_name = '123test'
print(test.get_name)

使用名称重整保护私有特性

在前面的Duck例子中,如果直接访问hidden_name还是可以看到对应属性的值。Python对那些需要可以隐藏在类内部的特性有自己的命名规范:由连续的两个下划线开头(__)

 def __init__(self, inputname):
        self.__hiddenname = inputname

    '''getter & setter'''

    @property
    def get_name(self):
        print('get_name')
        return self.__hiddenname

    @get_name.setter
    def set_name(self, name):
        print('set_name')
        self.__hiddenname = name

这样我们就无法在外部访问__name特性了
这种命名规范本质上并没有把特性变成私有。Python其实是将它的名字重整了,让外部的代码无法使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容