Python编程:类与对象

Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。

Posted by zhangshun on August 16, 2019
类与对象

对象是特征(变量)与技能(函数)的结合体,类是一系列对象共有的特征与技能的结合体。
在现实生活中:先有对象,再总结归纳出的类
在程序中:一定是先定义类,再实例化对象


1
2
3
4
5
6
x=1 ===> x=int(1)
#id
#tyoe
#value
print(id(x))
print(type(x))

is、==的区别
is表示比较对象的id
==表示比较对象的value


类的用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Chinese:
	'''注释'''
	country = 'China'
	def __init__(self,name):
		print('===>')
		self.name = name

	def talk(self):
		print('say Chinese')

#类的第一种用法:实例化
p1 = Chinese('zhangsan')	#__init__(p1,'zhangsan'),当类被绑定到对象时,首先会触发__init__函数
#类的第二种用法:属性引用
print(Chinese.country)	#类的数据属性
print(Chinese.__init__(p1,'zhangsan'))	#类的函数属性

print(Chinese.__dict__)	#查看类的属性字典,或者说名称空间

print(Chinese.country)
print(Chinese.__dict__['country'])

#对象
p1 = Chinese('zhangsan')
print(p1.name)

print(p1.__dict__)	#查看对象的数据字典
print(p1.__dict__['name'])

#类型与类是统一的
p1 = Chinese('zhangsan')
p2 = Chinese('lisi')
print(type(p1))

print(id(p1.country))	#country是共有的数据属性,即id相同,因为不涉及到self参数
print(id(p2.country))

p1 = Chinese('zhangsan')
print(p1.talk())	#对象的函数属性,每次绑定对象时,id不同,因为在绑定时会将p1作为第一个参数传入talk()中

#总结
#定义在类内部的变量,是所有对象共有的,id全部一样
#定义在类内部的函数,是绑定到所有对象的,是给对象来用的,obj.func()会把obj本身当作第一个参数传入

print(p1.country)	#先从p1.__dict__中找country,找不到再找类Chinese.__dict__,再找不到就会报错

练习: 一、每次实例化对象就记一次数,最后输出总共实例化多少次

1
2
3
4
5
6
7
8
9
class Foo:
	count = 0
	def __init__(self,name):
		Foo.count += 1	#更改的是类的数据属性count
		self.name = name
		print(Foo.count)

obj1 = Foo('zhangsan')
obj2 = Foo('lisi')

类的继承
1
2
3
4
5
6
7
8
9
10
11
class ParentClass1:
	pass
class ParentClass2:
	pass
class SubClass1(ParentClass1):
	pass
class SubClass2(ParentClass1,ParentClass2):
	pass

print(SubClass1.__bases__)	#输出对象的全部父类
print(SubClass2.__bases__)

寻找继承关系

继承的好处一:减少冗余代码 在子类定义新的属性,覆盖掉父类的属性,称为派生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Animal:
	def __init__(self,name,age,sex):
		self.name = name
		self.age = age
		self.sex = sex

	def Eat(self):
		print('eating......')
	
	def Talk(self):
		print('{} 正在叫'.format(self.name))

class People(Animal):
	def __init__(self,name,age,sex,education):
		Animal.__init__(self,name,age,sex)
		self.education = education
	def Talk(self):
		Animal.Talk(self)
		print('{} say hello'.format(self.name))

class Pig(Animal):
	pass

class Dog(Animal):
	pass

下列代码的结果是?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Parent:
	def foo(self):
		print('Parent.foo')
		self.bar()	#s.bar()

	def bar(self):
		print('Parent.bar')

class Sub(Parent):
	def bar(self):
		print('Sub.bar')

s = Sub()
s.foo()

s.foo会先在对象s.__dict__中找foo,如果没找到,会再Sub类中找foo,如果还没有找到,那会在父类Parent中找foo
s.foo结果打印的是Sub.bar,父类foo函数中的self.bar()指的是s.bar()


继承反映的是一种什么是什么的关系

组合也可以解决代码冗余问题,但是组合反映是一种什么有什么的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
class People:
    def __init__(self,name,age,sex):
        self.name=name
        self.age=age
        self.sex=sex

class Teacher(People):
    def __init__(self,name,age,sex,salary):
        People.__init__(self,name,age,sex)
        self.salary=salary

class Student(People):
    pass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day

    def tell(self):
        print('%s-%s-%s' %(self.year,self.mon,self.day))

class Teacher(People):
    def __init__(self,name,age,sex,salary,year,mon,day):
        self.name=name
        self.age=age
        self.sex=sex
        self.salary=salary
        self.birth=Date(year,mon,day)    #在类中组合其他类

class Student(People):
    def __init__(self,name,age,sex,year,mon,day):
        self.name=name
        self.age=age
        self.sex=sex
        self.birth=Date(year,mon,day)

# t=Teacher('egon',18,'male',3000,1995,12,31)
# t.birth.tell()

抽象类

定义接口,统一标准

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import abc
class File(metaclass=abc.ABCMeta):#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
    @abc.abstractmethod    #装饰器,子类在继承时必须定义read函数,否则实例化时会报错
    def read(self): #定接口函数read
        pass

    @abc.abstractmethod
    def write(self): #定义接口函数write
        pass

class Process(File):
    def read(self):
        # print('进程数据的读取方法')
        pass
    def write(self):
        print('进程数据的读取方法')

    # def xie(self):
        # pass
#
    # def du(self):
        # pass
p=Process()
p.read()

总结:用abc模块装饰后,子类在实例化的时候就会报错,那么当我们代码很长的时候,就可以早一点预知错误,所以以后在接口类类似问题中用这个模块接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。


经典类:深度优先,F->D->B->A->E->C->H (python2、python3)
新式类:广度优先,F->D->B->E->C->H->A (python3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class A:
    # def test(self):
        # print('from A')
        pass
class B(A):
    # def test(self):
        # print('from B')
        pass
class C(A):
    # def test(self):
        # print('from C')
        pass

class D(B):
    # def test(self):
        # print('from D')
        pass

class E(C):
    # def test(self):
        # print('from E')
        pass

class H(A):
    def test(self):
        print('from H')
        pass
class F(D,E,H):
    # def test(self):
        # print('from F')
        pass
f=F()
f.test()
print(F.mro())

super()调用父类方法

子类中可以使用super()方法来调用父类中的函数,防止写死影响程序的可扩展性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Foo1:
    # def test(self):
        # print('from foo1.test')
        pass
class Foo2:
    def test(self):
        print('from foo2.test')

class Bar(Foo1,Foo2):
    def test(self):
        # Foo1.test(self)
        # Foo2.test(self)
        super().test()    #用super()来调用父类中的test()函数,防止写死影响程序的可扩展性,解耦
        print('bar')

封装
  1. 封装数据:保护隐私
  2. 封装方法:隔离复杂度(比如你不必知道你自己的尿是怎么流出来的,你直接掏出自己的接口就能用尿这个功能)
1
2
3
4
class Foo:
    __x=1 #_Foo__x,隐藏变量,内部变量
    def __test(self): #_Foo__test,隐藏方法,内部函数
        print('from test')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class People:
    __country='China' #定义的是类的内部变量
    def __init__(self,name,age,sex):
        self.__name=name #self._People__name=name,定义的是对象的内部变量
        self.__age=age
        self.__sex=sex

    def tell_info(self):
        print('人的名字是:%s ,人的性别是:%s,人的年龄是:%s' %(
        self.__name, #p._People__name
        self.__age,
        self.__sex))

p=People('alex',18,'male')
p.talk_info()
print(p.__dict__)

在程序中,如果父类中不想被子类调用的函数,可以命名为隐藏函数,即内部函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class People:
    def __init__(self,name,age):
        self.__name = name
        self.__age = age

    def tell_info(self):
        print('name: {},age: {}'.format(self.__name,self.__age))

    def set_info(self,x,y):
        if not isinstance(x,str):
            raise TypeError('名字必须是字符串类型')
        if not isinstance(y,int):
            raise TypeError('年龄必须是整数类型')

        self.__name = x
        self.__age = y

p1 = People('zhangshun',25)
p1.tell_info()
p1.set_info('lixuan',26)
p1.tell_info()

tell_info:查询接口,查询类中的内部变量
set_info:修改接口,修改类中的内部变量


isinstance()函数

isinstance() 函数来判断一个对象是否是一个已知的类型,例如:isinstance(10,int)或者isinstance(‘zhangshun’,str)


raise自定义异常

用raise语句来引发一个异常,例如:

1
2
if not isinstance(name,str):
    raise TypeError('名字必须是字符串类型')

如果名字不是字符串类型,抛出类型错误


property装饰器

将函数属性变为数据属性,对客户眼中调用的是数据属性

基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Foo:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise TypeError('name must string!')
        self.__name = value

p1 = Foo('zhangshun')
print(p1.name)
p1.name = 'lixuan'
print(p1.name)

例子:计算体脂比例

1
2
3
4
5
6
7
8
9
10
11
12
13
class People:
    def __init__(self,name,weight,height):
        self.name = name
        self.weight = weight
        self.height = height

    @property
    def bmi(self):
        return self.weight / (self.height ** 2)

p1 = People('zhangshun',65,1.89)
# print(p1.bmi())
print(p1.bmi)

我们给bmi方法加上@property,于是我们可以把bmi当成一个属性来用, @property把函数属性变成了数据属性


绑定方法与非绑定方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
类中定义的函数分成两大类:

  一:绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):

    1. 绑定到类的方法:用classmethod装饰器装饰的方法。

                为类量身定制

                类.boud_method(),自动将类当作第一个参数传入

              (其实对象也可调用,但仍将类当作第一个参数传入)

    2. 绑定到对象的方法:没有被任何装饰器装饰的方法。

               为对象量身定制

               对象.boud_method(),自动将对象当作第一个参数传入

             (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)

  二:非绑定方法:用staticmethod装饰器装饰的方法

     1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已

    注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import hashlib
import time
import settings
class MySQL:
    def __init__(self,host,port):
        self.id=self.create_id()
        self.host=host
        self.port=port
        print('conneting...')

    @staticmethod
    def create_id(): #非绑定方法,就是类中的普通工具包
        m=hashlib.md5(str(time.clock()).encode('utf-8'))
        return m.hexdigest()

    @classmethod
    def from_conf(cls):    #哪个子类来调用父类的方法,就修改哪个子类的HOST、PORT
        return cls(settings.HOST,settings.PORT) #MySQL('127.0.0.1',3306)

    def select(self): #绑定到对象的方法
        print(self)
        print('select function')

conn=MySQL('192.168.1.3',3306)
conn.select()

conn1=MySQL('192.168.1.3',3306)
conn1=MySQL.from_conf()
conn2=MySQL.from_conf()
conn3=MySQL.from_conf()
conn4=MySQL.from_conf()

print(conn1.id)
print(conn2.id)
print(conn3.id)
print(conn4.id)

__str__方法
1
2
3
4
5
6
7
8
9
10
11
12
class People:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __str__(self):
        return 'name:%s age:%s' %(self.name,self.age)

p=People('egon1','18')
p1=People('egon2','28')

print(p)
print(p1)

__str__方法,使print时显示更多有效的信息,必须返回字符串


isinstance(obj,cls)和issubclass(sub,super)

issubclass(sub,super)检查sub类是否是super类的派生类

1
2
3
4
5
6
class Foo:
    pass
class Bar(Foo):
    pass

print(issubclass(Bar,Foo))

反射

反射,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射),类也是对象并可以使用反射

  1. hasattr(object,name) 判断object中有没有一个name字符串对应的方法或属性
  2. getattr(object,name,default=None) 获取object中的name字符串对应的方法或属性,可以用来绑定实例,如果不存在,则输出第三个参数
  3. setattr(object,name,zhangsan) 修改object中的name字符串对应的方法或属性为zhangsan
  4. delattr(object,name) 删除object中的name字符串对应的方法或属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class BlackMedium:
    feature='Ugly'
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr

    def sell_house(self):
        print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name)
    def rent_house(self):
        print('%s 黑中介租房子啦,傻逼才租呢' %self.name)

b1=BlackMedium('万成置地','回龙观天露园')

#检测是否含有某属性
print(hasattr(b1,'name'))
print(hasattr(b1,'sell_house'))

#获取属性
n=getattr(b1,'name')
print(n)
func=getattr(b1,'rent_house')
func()

# getattr(b1,'aaaaaaaa') #报错
print(getattr(b1,'aaaaaaaa','不存在啊'))

#设置属性
setattr(b1,'sb',True)
setattr(b1,'show_name',lambda self:self.name+'sb')
print(b1.__dict__)
print(b1.show_name(b1))

#删除属性
delattr(b1,'addr')
delattr(b1,'show_name')
delattr(b1,'show_name111')#不存在,则报错

print(b1.__dict__)

为什么用反射之反射的好处 实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FtpClient:
    def __init__(self,ip):
        self.ip = ip
        print('connecting......')

    def run(self):
        while True:
            inp = input('>>:')
            inp_l = inp.split()
            if hasattr(self,inp_l[0]):
                func = getattr(self,inp_l[0])
                func(inp_l[1])

    def get(self,arg):
        print('download file {}'.format(arg))

f = FtpClient('192.168.0.111')
f.run()

__del__ 析构方法

在绑定的对象被删除之前执行,一般用来关闭连接、文件等操作

1
2
3
4
5
6
7
8
class Foo:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def __del__(self): #析构方法
        print('del--->')

obj = Foo('zhangshun',18)

__setattr__,__getattr__,__delattr__

当对象修改、获取、删除其属性时,触发__setattr__、__getattr__、__delattr__内置方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __getattr__(self, item):
        print('----> from getattr:你找的属性不存在')

    def __setattr__(self, key, value):
        print('----> from setattr')
        # self.key=value #这就无限递归了,你好好想想
        # self.__dict__[key]=value #应该使用它

    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item #无限递归了
        self.__dict__.pop(item)

#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)

#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)

#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx