PythonPython is BestPython

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

2017-10-10  本文已影响160人  So_ProbuING

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

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())

类的继承

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

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 方法

看几个栗子

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

类成员

类成员修饰符

共有成员

私有成员

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() 无法调用

字段 属性

普通字段

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

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

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)

特殊成员

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
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}
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)

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]

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)

静态方法

class staticdemo():
    @staticmethod
    def show():
        print("it's staticdemo")
staticdemo.show()
func_obj.static_bar()

类方法

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 超类对象

print(type(SpeciaIter))
>>> <class 'type'>

metaclass

我们已经知道,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

命名元组

命名元组时元组的子类,既可以通过名称(使用.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))

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
    def get_name(self):
        print('get_name')
        return self.hiddenname

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

test = duck('test123')
print(test.get_name)
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其实是将它的名字重整了,让外部的代码无法使用

上一篇下一篇

猜你喜欢

热点阅读