什么是面向对象?
面向对象是一种编写代码的思维方式:程序是由什么构成的。对于面向对象,肯定是基于类进行编码,与之前基于过程进行编码相对。
什么是类?
所谓类就是对客观世界一类事物的抽象化,把同一类事物的共有属性,共有行为抽取出来。例如:床类,都有长宽高属性,有用于提供睡觉场所的作用。这里的作用用类里面的方法来代替。属性表示类的特点,方法表示类的功能。通常类就是由属性和方法组成。
什么是类的实例化?
就是在某一类中找出一个具体的个体,如你家里那张特定的席梦思床,就是床类中一个特定的个体。在床类中找出你这张床的过程,就是类的实例化。而你的这张床通常也称为类的一个对象。
类的声明
通常类的简单声明如下:
class Cat:
# 分配内存空间
def __new__(cls, *args, **kwargs):
print('___new___')
return super().__new__(cls)
# 构造方法,该方法在创建对象(实例)的时候在,会自动调用
def __init__(self, color, name):
"""函数在类里面称为方法,self指的是当前对象
即调用方法的对象
"""
self.color = color
self.name = name
def catch_Rat(self):
print(self.name + '抓到了老鼠')
如上,定义一个Cat类, 其中new方法通常不改写该方法应当省略,这个方法是为了系统分配内存空间,如不写,系统也会默认创建调用该方法。而init方法相当于c语言的构造函数。其方法第一个参数self是系统默认的名称,不会初始化成类对象的属性。self起的作用是后面进行类的实例化时,表示进行实例化的对象,以初始化对象的属性,后面的color,name均为有效参数,用于赋值
对象成员
凡是通过init函数所声明的成员(也叫属性)均属于对象的属性,不属于类的属性,因为同一类事物不同对象个体间属性会有所不同,用于区分个体。如下:
class Cat:
# 构造方法,该方法在创建对象(实例)的时候在,会自动调用
def __init__(self, color, name):
"""函数在类里面称为方法,self指的是当前对象
即调用方法的对象
"""
self.color = color
self.name = name
littleCat = Cat('white', 'nasi')
print(littleCat.color, littleCat.name)
运行效果如下:
white nasi
在初始化时,littleCat = Cat('white', 'nasi')语句调用了init函数。其中'white'和'nasi'分别传递给init函数的第二个和第三个参数,而第一个参数’self‘指的就是这里的littleCat对象,当然self也可以改成其他任何名字,只不过这里大家已经习惯了用self,是一种约定俗成的用法。
类成员
前言引出:
根据上面代码,我们会想,有些属性是所有类的对象都是完全一样的,比如正常情况下,只要是猫都有四条腿,我们总不可能每次声明一个对象都要往init函数里面加一个参数4吧?这样的话每次初始化岂不是还要写成littleCat = Cat('white', 'nasi', 4),每次声明对象都要加个4,很麻烦,可不可以简单一点,让接littleCat = Cat('white', 'nasi'达到相同的效果?当然可以,查看第一种:
class Cat:
def __init__(self, color, name):
self.color = color
self.name = name
self.footNumber = 4
littleCat = Cat('white', 'nasi')
print(littleCat.color, littleCat.name,littleCat.footNumber)
white nasi 4
这样当然可以实现我们想要的要求,但我们仔细观察会发现,其实这没有多大意义,因为footNumber所有对象的值都完全相同,他应该是所有猫的属性,而不只是这一个猫,所以引出了类成员。如下写更合适:
class Cat:
footNumber = 4
def __init__(self, color, name):
self.color = color
self.name = name
littleCat = Cat('white', 'nasi')
print(littleCat.color, littleCat.name, Cat.footnumber)
white nasi 4
类成员应该用类名.属性名来访问。此时类变量是所有对象共享,用类名.属性名更改类变量后,所有对象会同步更改,但一旦使用了对象名.类变量,则该变量在该对象中变成了对象变量了,不再与其他对象的该属性同步了,此时类和该对象可以看成两个不同的对象,各自访问自己的。
如下:
class Cat:
count = 0
def __init__(self, color, name):
self.color = color
self.name = name
cat1 = Cat('white', 'nasi')
print(Cat.count)
Cat.count += 1
print(cat1.count)
print(Cat.count)
cat1.count += 1
print(cat1.count)
print(Cat.count)
cat2 = Cat('black', 'qiongsi')
print(Cat.count)
print(cat2.count)
#
# print(Cat.footNumber)
# Cat.footNumber += 1
# print(Cat.footNumber)
0
1
1
2
1
1
1
类变量的用途举例,可以用于统计创建的对象个数。
类方法
类中方法的创建同c++,调用方法类似于调用成员,用对象.函数()即可。如下:
class Cat:
def __init__(self, color, name):
self.color = color
self.name = name
def catch_Rat(self):
print(self.name + '抓到了老鼠')
cat = Cat('white', 'nasi')
cat.catch_Rat()
nasi抓到了老鼠
方法在类里面创建时,会自动生成第一个参数,名为self,指向调用次方法的对象,该参数强制生成。所以,类中的方法至少有一个参数self,当然,self也可以更名为其他名字。
属性私有化
- 用双下划线开头
双下划线开头之所以能够隐藏变量,是因为双下划线能够自动更改原属性名为“_类名__属性名”。调用方法'.__dict__'即可看出。
class Cat:
def __init__(self, color, name):
self.color = color
self.__name = name
def catch_Rat(self):
print(self.__name + '抓到了老鼠')
cat = Cat('white', 'nasi')
print(cat.__dict__)
{'color': 'white', '_Cat__name': 'nasi'}
此时name属性被隐藏,既不能用cat.name调用,也不能用cat.__name调用。如下:
class Cat:
def __init__(self, color, name):
self.color = color
self.__name = name
def catch_Rat(self):
print(self.__name + '抓到了老鼠')
cat = Cat('white', 'nasi')
print(cat.name)
print(cat.__name)
AttributeError: 'Cat' object has no attribute 'name'
但是,当我们知道了隐藏了变量的规则后,我们仍然可以通过cat._Cat__name来调用。
class Cat:
def __init__(self, color, name):
self.color = color
self.__name = name
def catch_Rat(self):
print(self.__name + '抓到了老鼠')
cat = Cat('white', 'nasi')
print(cat._Cat__name)
nasi
私有变量部分开放
可以在类里面声明一个调用私有变量的函数,外部要调用私有变量时,就通过调用函数,间接调用私有变量,实现私有变量的有限开放。如下:
class User:
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
li = User('li')
print(li.get_name())
li
但这样总感觉每次调用属性有点麻烦,所以可以考虑语法糖修饰器@。所谓修饰器就是:
在Python的函数中偶尔会看到函数定义的上一行有@functionName的修饰,当解释器读到@的这样的修饰符之后,会先解析@后的内容,直接就把@下一行的函数或者类作为@后边的函数的参数,然后将返回值赋值给下一行修饰的函数对象。
比如:
@a
@b
def c():
…
python会按照自下而上的顺序把各自的函数结果作为下一个函数(上面的函数)的输入,也就是a(b(c()))
所以接下来介绍几个在类中常用的修饰器
- @property 返回其后第一个函数的返回值
class User:
def __init__(self, name, age):
self.__name = name
self.__age = age
@property
def name(self):
return self.__name
@property
def age(self):
return self.__age
@property
def test(self):
return 1
li = User('li', 18)
print(li.age)
print(li.name)
print(li.test)
18
li
1
其实这里property的作用就是把下面的函数装饰成一个属性。
- @property.setter 对property装饰器后面函数返回的属性进行赋值
要求:setter前的property与property装饰器后的函数名,以及setter装饰器后面的函数名都要一致。使用setter时必须先有property。
class User:
def __init__(self, name, age):
self.__name = name
self.__age = age
@property
def name(self):
return self.__name
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
self.__age = age
print('赋值成功')
def get_age(self):
return self.__age
li = User('li', 18)
li.age = 15
print(li.get_age())
赋值成功
15
这里实现了对私有属性的赋值操作
- @classmethod
修饰的方法是类方法,类方法可以用类名调用,且必须有一个类型参数,如下面的cls,cls指的就是该类,类型参数可以任意命名,在其下的函数创建对象时可以增加属性
class User:
def __init__(self, name):
self.name = name
@classmethod
def create_user(cls, name, age):
print(cls)
user = cls(name)
user.name = name
user.age = age
return user
li = User.create_user('li', 18)
print(li.name)
print(li.age)
print(li)
main.User'>
li
18
main.User'>
<main.User object at 0x000001FEF4C9A160>
这里实现了新建一个对象,而且这个对象比原类多了一个age属性
- @staticmethod
该装饰器装饰的方法会变成静态方法,可以通过类名直接调用,常用于写工具类
class User:
def __init__(self, name, age):
self.__name = name
self.__age = age
@staticmethod
def sum(a,b):
return a+b
print(User.sum(1, 2))
3
类中的str方法
正常情况下直接打印对象返回的是对象的内存地址,若类中存在该方法,打印类的对象时会自动执行该方法。所以通常在该方法中执行一些语句,打印出更多关于该对象的信息
class User:
def __init__(self, name):
self.name = name
@classmethod
def create_user(cls, name, age):
print(cls)
user = cls(name)
user.name = name
user.age = age
return user
def __str__(self): # 类中若是存在该函数,打印类对象的时候会自动执行该函数
_str = ''
for k,v in self.__dict__.items():
_str += str(k)
_str += ':'
_str += str(v)
_str += ','
print('__str__')
print(_str)
return _str
li = User.create_user('li', 18)
print(li)
main.User'>
str
name:li,age:18,
name:li,age:18,
继承
所有的继承不指定父类都默认指定object,python 支持多继承,继承内容与顺序相关,所有的类,都会默认继承object类,继承通常会继承父亲的所有方法和子类,也可以子类重写
- 单继承
class A:
def __init__(self):
self.name = 'zs'
def print_test(self):
print('AAAAAAAAAAAAA')
class C(A):
def __init__(self):
super().__init__() # 先调用父亲的初始化方法,super()表示调用的是父亲的
# 若删除该语句,则应该c里面的初始化方法,可能导致父类部分属性消失
self.age = 20
test = C()
print(test.name, test.age)
zs 20
子类继承了父类的name属性,同时自己新增了age属性。
- 多继承
class A:
def __init__(self):
self.name = 'zs'
def print_test(self):
print('AAAAAAAAAAAAA')
class B:
def __init__(self):
self.name = 'B'
def print_test(self):
print('BBBBBBBBBBB')
class D(B,A):
name = 'D'
def __init__(self):
super().__init__()
self.age = 21
def print_test(self):
super().print_test()
print('DDDDDDDDDD')
test = D()
print(test.name)
test.print_test()
B
BBBBBBBBBBB
DDDDDDDDDD
通过观察,我们发现他是按照顺序继承的,查看调用属性或方法的查找顺序:print(类名.mro),先自己,然后第一个,第二个。。
组合
在编程时,组合优于继承,但是效果相同,所谓组合,就是类的嵌套(个人理解),如下:
class A:
def __init__(self):
self.name = 'zs'
def print_test(self):
print('AAAAAAAAAAAAA')
class E:
def __init__(self):
self.a = A()
def print_test(self):
self.a.print_test()
print("DDDDDDDDDD")
test = E()
print(test.a.name)
test.print_test()
zs
AAAAAAAAAAAAA
DDDDDDDDDD
编程常用技巧:鸭子类型
简单来说,就是用不同的类给同一个变量赋值,这些类之间有同名的方法,用变量.方法调用时,变量是用哪个类的对象就调用哪个类里面的方法。这样可以优化程序,可读性也更高。
class Programer:
def dowork(self):
print("biancheng")
class Manger:
def dowork(self):
print("guanli")
employee = Manger()
employee.dowork()
employee = Programer()
employee.dowork()
类的单例
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。这里我们讲解一个单例模式的实现方法——通过重写系统创建对象时分类内存的函数new
class S:
instance = None
# __new__方法用来制作单例
def __new__(cls, *args, **kwargs):
if S.instance == None:
S.instance = super().__new__(cls) # 这句话是用来分配内存的,
return S.instance
s1 = S()
print(id(s1))
s2 = S()
print(id(s2))
2159709448232
2159709448232