1.python中类和对象的概念
类(class):简单来说就是某一类事物,它们具有相同的属性,例如猫有各种颜色,各种颜色就属于属性(也被叫做变量)。
对象(object):黑猫,白猫这些都是对象,这个对象就是类的实例(instance)。对象/实例只有一种作用,即属性引用。
对象内存空间里只存储对象的属性,而不存储方法和静态属性,方法和静态属性存储在类的内存空间中,这样多个对象可以共享类中的资源,便于节省内存(见下图)。
实例化:类到对象的过程(实例 = 类名(参数1,参数2))
字段(field):对象可以使用属于它的普通变量来存储数据,这种从属于对象或者类的变量叫做字段。它们属于某一类的各个实例或对象,或是从属于某一类本身。它们被分别称作实例变量(Instance Variables)与类变量(Class Variables)。有的地方称他们为静态属性和动态属性,静态属性针对的是类属性,动态属性针对的是定义在类中的方法。
方法(method):对象可以通过类的函数来实现相关功能,这个函数叫做类的方法。方法分为普通方法,类方法和静态方法。三种方法在内存中都属于类,区别在于调用方式不同,详见后面例题。
__init__方法:它会在类的对象被实例化时立即运。
class Person: role = \'路飞\' # 静态属性,对于静态属性的修改,必须使用类名直接修改,即类名.静态变量名 def eat(self): # 动态属性(方法) print(\'不够吃\') print(Person.__dict__) # 查看当中的静态变量的所有字典方法 # {\'role\': \'路飞\', \'__dict__\': <attribute \'__dict__\' of \'Person\' objects>, \'__module__\': \'__main__\', \'__doc__\': None, \'eat\': <function Person.eat at 0x0292BB70>, \'__weakref__\': <attribute \'__weakref__\' of \'Person\' objects>} print(Person.__dict__[\'role\']) # 查看静态变量,注意这里的role必须有引号 # 路飞 print(Person.role) # 查看静态变量的第二种方式 # 路飞 print(Person.eat) # 查看这个方法的内存地址 # <function Person.eat at 0x0509BB70> Person.eat(\'s\') # 调用这个方法,给self传递一个实参 # 不够吃 ACE = Person() # 创造一个对象(即实例),对象 = 类名() # 引用静态变量: # Person.__dict__[\'role\'],这种方式可以直接查看,但不能对其进行修改 # Person.role,这种方法可以查看,也可以修改,还能删除(直接del Person.role) # 引用动态变量: # Person.eat, 类名.方法名,查询这个方法的地址 # Person.eat(\'s\'), 类名.方法名(参数), 调用这个方法 class Person: role = \'person\' def __init__(self, name, sex, hp): print(self.__dict__) # {} 相当于告诉用户self里面内置了一个空字典 self.name = name # self.__dict__对象中存储的属性,被称为对象属性,一般被称为属性 self.sex = sex self.hp = hp print(self) # <__main__.Person object at 0x0572D790> print(self.__dict__) # {\'hp\': 120, \'name\': \'zoro\', \'sex\': \'man\'} def beat(self): print(\'%s beated\' % self.name) ACE = Person(\'zoro\', \'man\', 120)
# 实例化的过程:
# 1.创建一个实例/对象,将会作为一个实际参数(ACE)
# 2.自动触发一个__init__的方法,通过它给对象添加一些属性,对象默认名字是self
# 3.执行完__init__方法之后,会将self所指向的内存空间返回给实例化它的地方
print(ACE.__dict__[\'name\']) print(ACE.name) # zoro ACE.__dict__[\'name\'] = \'sanzhi\' # 对输入的实参可以进行修改 print(ACE.__dict__[\'name\']) # 对象名.__dict__[\'属性名\'] print(ACE.name) # 对象名.属性名 →推荐使用这种写法 # sanzhi Person.beat(ACE) # 类名.方法名(对象名) ACE.beat() # 对象名.方法名(),这样写相当于方法中的self参数直接指向这个对象 # zoro beated
# 这里对象ACE先找间自己的内存空间,再找见类对象指针,根据类对象指针找见person类,再通过类找见beat方法
print(ACE) # 可以看出,self和ACE所用是同一内存地址 # <__main__.Person object at 0x0572D790> print(ACE.__dict__) # 可以看出和上面self.__dict__值是一样的 # {\'name\': \'sanzhi\', \'sex\': \'man\', \'hp\': 120}
1.对象的内存空间中只存储对象的属性
而方法和静态属性存储在类的内存空间中,这样即使存在多个方法也只占用类开辟的空间,这样便于节省内存空间。
2.对象属性是独有的,静态属性的方法是共享的
3.对象使用名字,先找到自己的命名空间,如果当中存在,则优先执行它,而不会通过类对象指针去person类找beat方法
4.对于静态属性的修改,必须使用类名修改(如Person.role = \'索隆\')
class Foo: count = 0 def __init__(self): Foo.count += 1 f1 = Foo() f2 = Foo() --- print(Foo.count)
2.类的组合使用
一个类的对象作为另一个类的对象的属性
创建一个圆,以及圆环,通过组合,计算圆环的周长与面积(对象名.属性名,它表示另一个对象)
from math import pi class Ricle: def __init__(self, r): self.r = r def perimeter(self): return 2*pi*self.r def area(self): return pi*self.r**2 class Ring: def __init__(self, in_r, out_r): self.in_circle = Ricle(in_r) # 把小圆对象给了左边 self.out_circle = Ricle(out_r) # 上面这两步相当于在字典中添加了两个键值对 print(self.__dict__) # {\'out_circle\': <__main__.Ricle object at 0x04EDD3D0>, \'in_circle\': <__main__.Ricle object at 0x04EDD830>} def Ring_perimeter(self): return self.in_circle.perimeter() + self.out_circle.perimeter() def Ring_area(self): return self.out_circle.area() - self.in_circle.area() s = Ring(5,10) print(s.Ring_perimeter())
3.类属性的当中调用
类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性,key为属性名,value为属性值 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类
经典例题:
class Robot:
\'\'\'表示有一个带有名字的机器人\'\'\'
# 一个类变量,用来计数机器人数量
population = 0
def __init__(self, name):
\'\'\'初始化数据\'\'\'
self.name = name # name变量通过使用self分配,属于一个对象变量
print(\'initializng {}\'.format(self.name))
# 当有人被创建时,机器人增加数量
Robot.population += 1
def die(self):
\'\'\'挂了\'\'\'
print(\'{} is being destroyed\'.format(self.name))
Robot.population -= 1
if Robot.population == 0:
print(\'{} was the last one.\'.format(self.name))
else:
print(\'there are still {:d} robots working.\'.format(Robot.population))
def say_hi(self):
\'\'\'来自机器人的问候\'\'\'
print(\'greeting, my masters call me{}\'.format(self.name))
@classmethod
# 等价于调用how_many = classmethod(how_many)
def how_many(cls):
\'\'\'打印当前数量\'\'\'
print(\'we have {:d} robots.\'.format(Robot.population))
droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()
# droid2 = Robot("C-3PO")
# droid2.say_hi()
# Robot.how_many()
print("\nRobots can do some work here.\n")
print("Robots have finished their work. So let\'s destroy them.")
droid1.die()
# droid2.die()
Robot.how_many()
# 我们通过 Robot.population 而非 self.population 引用 population 类变量。
# 对于 name 对象变量采用 self.name 标记法加以称呼,这是这个对象中所具有的方法。
# 要记住这个类变量与对象变量之间的简单区别。
# how_many 实际上是一个属于类而非属于对象的方法。这就意味着我们可以将它定义为一个
# classmethod(类方法) 或是一个 staticmethod(静态方法) ,这取决于我们是否知道我们需不需
# 要知道我们属于哪个类.
说出打印顺序并解释原因:
class Foo: country = \'中国\' print(country) # 这里也有一个打印 def __init__(self,name,country): self.name = name self.country = country learning = Foo(\'learning\',\'印度\') Foo.country = \'泰国\' print(Foo.country) print(learning.country)
4.面向对象的三大特性:继承,多态,封装
4.1继承:python中,一个新建的类可以继承一个或多个父类
继承的作用:
- 减少代码的重用
- 提高代码的可读性
- 规范编程模式
python内继承分为单继承和多继承,基本形式:
class Parent1: # 定义父类 pass class Parent2: # 定义父类 pass class Son1(Parent1): # 单继承,基类(父类,超类)是Parent1,派生类(子类)是Son pass class Son2(Parent1, Parent2): # 多继承,用逗号分隔开多个继承的类 pass print(Son1.__bases__) # (<class \'__main__.Parent1\'>,) print(Son2.__bases__) #查看所有继承的父类 (<class \'__main__.Parent1\'>, <class \'__main__.Parent2\'>) print(Parent1.__base__) # 查看单个继承的父类<class \'object\'> # 在python3中,所有的类都会默认继承object类 # 继承了object类的所有类都是新式类 # 如果一个类没有继承任何父类,那么__base__就会显示<class \'object\'>
如果在开发程序过程中,我们已经定义一个类A,现在又定义了一个类B,如果B的类的大部分内容与A都相同,则可以使用继承来实现,实现代码的重用。
继承中的派生:子类在继承父类的属性基础下,也可以重新定义一些属性,如果重新定义的属性(或方法)与父类的相同,则会覆盖父类的属性(或方法),优先执行自己定义的。
class Animal:
def __init__(self, name, hp, ad):
self.name = name # 三个属性
self.hp = hp # 血量
self.ad = ad # 攻击力
def eat(self): # 方法
print(\'eating in Animal\')
self.hp += 20
class Person(Animal):
def __init__(self, name, hp, ad, sex):
# Animal.__init__(self, name, hp, ad)
#super(Person, self).__init__(name, hp, ad)
# 在单继承中,super负责找到当前类所在的父类,这个时候就不需要再手动传self
super().__init__(name, hp, ad) # 这个比较常用
self.sex = sex # 派生属性
self.money = 100 # 给属性中再增添一个新技能
def attack(self, dog): # 派生方法
print("%s攻击了%s" % (self.name, dog.name))
def eat(self): # 重写
# super().eat()
# 在类内使用super()方法找父类的方法,这样调用子类时可以同时实现父类eat方法
print(\'eating in Person\')
self.money -= 50
class Dog(Animal):
def __init__(self, name, hp, ad, kind):
super().__init__(name, hp,ad)
self.kind = kind # 派生属性
def bite(self, person): # 派生方法
print("%s咬了%s" % (self.name, person.name))
mark = Person(\'alex\', 100, 10, \'female\') # 实例化
tg = Dog(\'到哥\', 100, 50, \'藏獒\')
tg.bite(mark) # 到哥咬了alex
print(mark.__dict__)
# {\'money\': 100, \'name\': \'alex\', \'hp\': 100, \'sex\': \'female\', \'ad\': 10}
mark.eat() # eating in Person
super(Person, mark).eat() # 在外部通过调用super查看person父类中的方法,这时候不能用上面的简便方法了,必须写类名,对象名,明确调用者
# eating in Animal
# 这里使用super方法,实现了子类可以调用父类中的属性,通过super可以直接在子类执行父类的方法
看看super的相关用法(很经典)
class Fooparent(object):
def bar(self, message):
print("%s from parent" % message)
def __init__(self):
self.parent = "I\\'m the parent."
print("parent")
class Foochild(Fooparent):
def __init__(self):
super(Foochild, self).__init__() # 这里也可以直接写成super().__init__()
print("child")
def bar(self, message):
super(Foochild, self).bar(message) # 这里也是可以简写
print("child bar function")
print(self.parent)
if __name__ == "__main__":
foochild = Foochild()
foochild.bar("hello world")
# 当创建出foochild对象时,它会先去Foochild的类中自上而下执行,当遇到init下的super方法时,去执行父类的__init__方法,此时打印出parent,
接着执行自己的方法。当对象调用bar方法时,先在foochild中查找,遇到了super方法,又返回父类执行父类的bar,接着执行自己的方法,以及子类print(self.parent)
看看这个例子(调用了子类方法的同时又嵌套了父类方法):
class Schoolmember:
def __init__(self,name,age):
self.name = name
self.age = age
print(\'ininialized Schoolmember:{}\'.format(self.name))
def tell(self):
print(\'name:{},age:{}\'.format(self.name, self.age,end = \'\'))
class Teacher(Schoolmember):
def __init__(self,name, age, salary):
Schoolmember.__init__(self,name,age)
self.salary = salary
print(\'initialized teacher:{}\'.format(self.name))
def tell(self):
Schoolmember.tell(self)
print(\'salary:{}\'.format(self.salary))
class Student(Schoolmember):
def __init__(self,name, age,marks):
Schoolmember.__init__(self, name, age)
self.marks = marks
print(\'initialized students:{}\'.format(self.name))
def tell(self):
Schoolmember.tell(self)
print(\'marks:{}\'.format(self.marks))
# t = Teacher(\'Tom\', 40, 300)
s = Student(\'egg\',26, 80)
# t.tell()
s.tell()
再看看这个例子
class Parent: def func(self): print(\'in parent func\') def __init__(self): self.func() class Son(Parent): def func(self): print(\'in son func\') s = Son()
示意图:
钻石继承问题
多继承:遵循广度优先遍历顺序
例一:
class A:
def func(self):
print(\'A\')
class B(A):
pass
def func(self):
print(\'B\')
class C(A):
pass
def func(self):
print(\'C\')
class D(B):
pass
def func(self):
print(\'D\')
class E(B,C):
pass
def func(self):
print(\'E\')
class F(D,E):
pass
def func(self):
print(\'F\')
f = F()
f.func()
print(F.mro()) # 广度优先的遍历顺序
图例
例二:
class A:
def func(self):
print(\'A\')
class B(A):
def func(self):
super().func()
print(\'B\')
class C(A):
def func(self):
super().func()
print(\'C\')
class D(B,C):
def func(self):
super().func()
print(\'D\')
d = D()
d.func()
图例
这里的多继承是根据子节点去找的,执行d.func()的时候遇到super,去B中去找,B中也有Super,紧接着会去C,发现C也有super,由于广度有限,此时它会找到A,执行A,再返回C,执行C,再到B,再到D
# 这里你可以分别把B和C中的super注掉再看看它的打印
例三:
class A:
def test(self):
print(\'from A\')
class B(A):
def test(self):
print(\'from B\')
class C(A):
def test(self):
print(\'from C\')
class D(A):
def test(self):
print(\'from D\')
class E(B):
def test(self):
print(\'from E\')
class F(E,D,C):
def test(self):
print(\'from F\')
b= B()
b.test()
d = D()
d.test()
f = F()
f.test()
print(F.mro())
图例
例题:
class F3(object): def f1(self): ret = super().f1() print(ret) return 123 class F2(object): def f1(self): print(\'123\') class F1(F3, F2): pass obj = F1() obj.f1()
注意:这里会先去F3下,因为存在super,会到F2下打印f1,再返回F3去执行f1。这个跟上面一样,它是根据节点去查找的
例题:
class Init(object):
def __init__(self,value):
self.val = value
class Add2(Init):
def __init__(self,val):
super(Add2,self).__init__(val)
self.val += 2
class Mul5(Init):
def __init__(self,val):
super(Mul5,self).__init__(val)
self.val *=5
class Pro(Mul5,Add2):
pass
class Incr(Pro):
csup = super(Pro)
def __init__(self,val):
self.csup.__init__(val)
self.val += 1
p = Incr(5)
print(p.val)
例题:
class F1(object): def __init__(self, num): self.num = num def func(self, request): print(self.num, request) def run(self): self.func(999) class F2(F1): def func(self, request): print(666, self.num) objs = [F1(1), F2(2), F2(3)] objs[1].run() objs[2].run()
总结:(py3中全是新式类(默认都带有object),遵循广度优先。py2中不带object的属于经典类,遵循深度优先)
# 单继承中
在单继承中就是单纯的寻找父类
在多继承中就是根据子节点 所在图 的 mro顺序找寻下一个类
# 遇到多继承和super
# 对象.方法
# 找到这个对象对应的类
# 将这个类的所有父类都找到画成一个图
# 根据图写出广度优先的顺序
# 再看代码,看代码的时候要根据广度优先顺序图来找对应的super
4.2多态(指的是一类事物有多种形态。记住,python是自带多态效果的,且崇尚鸭子类型)
先来闲聊下python编程原则:
- 开放封闭原则:对于扩展是开放的,对于修改是封闭的
- 依赖倒置原则
- 接口隔离原则(python里没有接口类的概念,java类没有多继承,但是可以通过接口实现多继承)
python中接口类和抽象类:
接口类(在抽象类的基础上):python默认没有接口类,接口类不能被实例化,这也意味着接口类中的方法不能被实现。
抽象类:python中,默认是有的
父类的方法,子类必须实现
(抽象类)父类的方法也可以被实现
相同:这两者都是用来约束子类方法的,都不能被实例化
区别:接口类不能实现方法,抽象类可以实现方法里面的内容
只要是抽象类和接口类中被abstractmethod装饰的方法,都需要被子类实现。
当多个类之间有相同的功能也有不同的功能的时候,python应采用多个接口类来实现。
如果工作中遇到接口类,记住按照抽象类中的规范去去一一实现对应的方法
多态(通过继承实现):
在一个类之下发展出来的多个类的对象都可以作为参数传入一个函数或者方法,在python中不需要可以实现多态,因为它本身自带多态效果
Pyhon不支持Java和C#这一类强类型语言中多态的写法,但支持原生多态,其Python崇尚“鸭子类型。所谓的鸭子类型,说白了就是两个不同的类拥有相同的方法名,利用一个统一的接口来调用,不同的对象在接收时会产生不同的行为(即方法)
鸭子类型:不是通过具体的继承关系来约束某些类必须必须要有哪些方法名,是通过一种约定俗成的概念来保证多个类中相似的功能叫相同的名字
python的鸭子类型:
class A:
pass
class B(A):
def show(self):
print(111)
class C(A):
def show(self):
print(222)
def func(obj): # 在Java或C#中定义函数参数时,必须指定参数的类型,即def Func(F1
return obj.show() # 定义一个统一的接口来使用
s1 = B()
func(s1) # 111
s2 = C()
func(s2) # 222
# 抽象类和接口类做的事情就是创建规范,制定一个类的metaclass(元类)是ABCmeta,这个类就变成了一个接口类,这个类的功能主要就是建立一个规范,从而约束后面的子类
抽象接口:
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self):
pass
class Alipay(Payment):
def pay(self, money):
print(\'使用支付宝支付了%s元\' % money)
class QQpay(Payment):
def pay(self, money):
print(\'使用qq支付了%s元\'%money)
class Wechatpay(Payment):
def pay(self, money):
print(\'使用微信支付了%s元\'%money)
def recharge(self):
pass
def pay(a, money):
a.pay(money)
# 归一化设计:不管是哪一个类的对象,都调用了同一个函数去实现了相似的功能
a = Alipay()
a.pay(100) # 使用支付宝支付了100元
b = QQpay()
b.pay(50) # 使用qq支付了50元
c = Payment()
c.pay() # 接口类不能被实例化,会报错
# TypeError: Can\'t instantiate abstract class Payment with abstract methods pay
4.3 封装:顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容
封装原则:
1. 将不需要对外提供的内容都隐藏起来;
2. 把属性都隐藏,提供公共方法对其访问。
封装的好处:
1.提高代码的复用性
2.提高安全性
3.降低代码的冗余度
4.3.1 私有变量和私有方法:
定义一个私有的名字:在私有的名字前面加两条下划线 _ _N = \'name\'。这样就不能在类的外面调用它
但是,一个私有的名字在存储过程中仍然会出现在A.__dict__中,我们仍然可以调用到,只不过python对其名字进行了修改:_类名__名字
class A: __N = 666 # 静态变量 def func(self): print(A.__N) print(A._A__N) # 666 print(A.__dict__) # 通过这个可以查看它的内部机制
在类里面,只要代码遇到_ _名字,就会被python解释器自动转换成 _类_ _名字
# 在类中,静态属性,方法,对象属性都可以变成私有的,只需要在这些名字之前加上__
# 私有属性
class A:
def __init__(self,name):
self.__name = name
def b(self):
print(\'in b: %r\' % self.__name)
a = A(6)
print(a._A__name) # 6
a.b() # in b: 6
例二
class F:
def a(self):
self.__name = 666
f = F()
f.a()
print(f.__name) # 报错
print(f._F__name) # 666
# 私有方法
class A:
def __a(self):
print(111)
def b(self):
self.__a()
c = A()
c.b() # 私有方法的调用
# 继承中的私有方法
class D:
def __func(self):
print(111)
class E(D):
def __init__(self):
self.__func() # __func()是父类私有的方法
e = E() # 报错,因为私有的名字不能被子类继承
# 继承中的关于私有属性的易错点(一不小心就分析错误)
### 子类不能继承父类的私有方法和属性
class A:
def fa(self):
print(\'from A\')
def test(self):
self.fa()
class B(A):
def fa(self):
print(\'from B\')
b = B()
b.test() # from B(捋一捋这里为什么是B)
class A:
def __fa(self): # 此时这里变形为_A__fa
print(\'from A\')
def test(self):
self.__fa() # 这里止与自己所在的类为准,及调用_A__fa
class B(A):
def __fa(self): # 此时这里变形为_B__fa
print(\'from B\')
b = B()
b.test() # from A (捋一捋这里为什么又是A)
这个题也是一样
class C:
__name = "公有静态字段"
def func(self):
print(C.__name)
class D(C):
def show(self):
print(C.__name) # 公有静态字段
obj = C()
obj.func() #正常
obj_son = D() # 报错
obj_son.show()
4.3.2 封装与扩展
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。
字典对象做封装:
list_filter = [
{\'text\':\'董方方\',\'gender\':\'男\',\'color\':\'xx\'},
{\'text\':\'黄晓雪\',\'gender\':\'男\',\'color\':\'xx\'},
{\'text\':\'李贝贝\',\'gender\':\'男\',\'color\':\'xx\'},
]
for item in list_filter:
print(item[\'text\'] + item[\'gender\'])
这里我们不要狭隘的认识对象,python中一切皆对象,字典也是对象,这里也是一种封装的思想
接下来看看下面的例子:
class Option(object): def __init__(self,text,gender,color): self.text = text self.gender = gender self.color = color def get_t_g(self): return self.text +self.gender list_filter = [ Option(text=\'董方方\',gender=\'男\',color = \'xx\'), Option(text=\'黄晓雪\',gender=\'男\',color = \'xx\'), Option(text=\'李贝贝\',gender=\'男\',color = \'xx\'), ] for item in list_filter: print(item.get_t_g())
该类也是一种封装的思想,这里实例化了三次,开辟了三个内存空间,不过都是用同一个对象进行调用的
# 面试晋升题
class D: def __init__(self): self.__func() def __func(self): print(\'in D\') class E(D): def __func(self): print(\'in E\') e = E()
@property(实现了将方法伪装成属性)
能够将一个方法伪装成属性,从原来的obj.func()变成了obj.func
被property装饰的方法仍然是一个方法,虽然代码看上去逻辑上没有什么变化,但是从调用者角度来看换了一种方式,使之更加合理。
# 人体BMI指数 ,体质指数(BMI)=体重(kg)÷身高^2(m),写一个类 描述人体BMI指数
class Person: def __init__(self, tall, weight): self.__weight = weight self.tall = tall @property def BMI(self): return \'BMI指数:%s\' % (self.__weight/(self.tall**2)) me = Person(1.79, 69) print(me.BMI) # 将方法伪装成属性
@property的进阶之@funcname.setter(实现了对私有属性的修改)
被@property装饰的方法名必须和被@funcname.setter装饰的方法同名
class Person: def __init__(self,name): self.__name = name @property def name(self): return self.__name @name.setter # 这里的名字name必须与上面的方法名一致 def name(self,new_name): # 只能传入一个形参 print(\'one\', new_name) p = Person(\'luffy\') print(p.name) # luffy p.name = \'piece\' # one piece
@property的进阶之@funcname.deleter(实现了对私有属性的删除)
class Person: def __init__(self, name): self.__name = name @property def name(self): return self.__name @name.deleter # 这里的名字name必须与上面的方法名一致 def name(self): del self.__name p = Person(\'luffy\') print(p.name) # luffy del p.name print(p.name) # 报错,因为已经删除掉了
例:输入原价和折扣价,查看价格的时候可以查看折扣价,同时实现可以更换价格,也能实现输出折扣价
class Goods: def __init__(self, name, price, discount): self.name = name self.__price = price self.__discount = discount @property def now_price(self): return self.__price * self.__discount @now_price.setter def now_price(self, new_price): if type(new_price) is int or float: self.__price = new_price apple = Goods(\'apple\', 8, 0.8) print(apple.now_price) # 6.4 apple.now_price = 10 # 对价格进行修改 print(apple.now_price) # 8.0 # 折扣价
@classmethod类方法(可以被类直接调用,不需要默认传对象参数,需要传入类参数cls,执行类方法时,自动将调用该方法的类复制给cls)
对于静态属性的修改
class Goods: __discount = 0.8 def __init__(self, name, price): self.name = name self.__price = price @property def now_price(self): return self.__price*Goods.__discount @classmethod def change_discount(cls,new_discount): # 类方法,可以被类直接调用,需要传入一个类参数cls cls.__discount = new_discount apple = Goods(\'apples\', 6) print(apple.now_price) # 4.8 Goods.change_discount(1) # 修改折扣后
# 这里注意是在括号里传值,不是赋值,不是Goods.change_discount=1,不知道为啥自己很容易犯错
banana = Goods(\'bananas\', 6)
print(banana.now_price) # 6
@staticmethod静态方法(由类调用,无默认参数)
# 当一个方法要使用对象的属性时 就是用普通的方法(不带self)
# 当一个方法要使用类中的静态属性时 就是用类方法
# 当一个方法要既不使用对象的属性也不使用类中的静态属性时,就可以使用staticmethod静态方法
class A: def __init__(self,name): self.name = name @staticmethod def a(): # 这里不用再传入self print(666) A.a() # 666
类中的三种方法总结:
注意:
对于实例方法:由实例化对象调用
对于类方法:由于不适用于对象内存空间的属性,所以不会将对象和方法绑定到一起,而是将类和方法绑定在一起
对于静态方法:不是绑定方法,没有和对象或者类方发生任何绑定关系
class Foo:
def __init__(self, name):
self.name = name
def ord_func(self):
""" 定义实例方法,至少有一个self参数 """
# print self.name
print \'实例方法\'
@classmethod
def class_func(cls):
""" 定义类方法,至少有一个cls参数 """
print \'类方法\'
@staticmethod
def static_func():
""" 定义静态方法 ,无默认参数"""
print \'静态方法\'
# 调用实例方法
f = Foo()
f.ord_func()
# 调用类方法
Foo.class_func()
# 调用静态方法
Foo.static_func()
例题:(分析输出的是什么)
class A:
__role = \'CHINA\'
@classmethod
def show_role(cls):
print(cls.__role)
@staticmethod
def get_role():
return A.__role
@property
def role(self):
return self.__role
a = A()
print(a.role)
print(a.get_role())
a.show_role()
7.面向对象进阶
7.1.isinstance和issubclass
7.2.反射(高级知识点,重点)
7.3.python面向对象中的反射
class F:
a = \'luffy\'
def __init__(self,name):
self.name =name
def A(self):
print(\'%s666\' % self.name)
obj = F(\'Ronan\')
# 检查是否存在某属性,也能检查静态属性
print(hasattr(obj, \'name\'))
print(hasattr(obj, \'A\'))
# 获取属性,包含静态属性
print(getattr(obj, \'name\'))
getattr(obj,\'A\')()
# 设置属性,如果有就改,没有就增
setattr(obj, \'ACE\', \'999\')
print(obj.__dict__) # {\'name\': \'Ronan\', \'ACE\': \'999\'}
# 删除属性(如果不存在该属性则报错),无法删除静态属性
delattr(obj, \'name\')
print(obj.__dict__) # {\'ACE\': \'999\'}
再看看详细用法:
# 例一
class A:
role = \'luffy\'
def func(self):
print(\'*\'*self)
print(getattr(A,\'role\')) # 从A的命名空间找一个属性,直接就可以找见这个属性的值
f = getattr(A, \'func\');f(5) # 从A的命名空间找一个方法,找见的是这个方法的内存地址
# 在类中hasattr和gettar的组合使用
ret = input(\'>>>\')
if hasattr(A, ret):
print(getattr(A, ret)) # getattr从A的命名空间里找一个属性
if hasattr(A, ret):
func = getattr(A, ret) # getattr从A的命名空间里找一个方法
func(1) # 给self传参
# 例二
class A:
role = \'Person\'
def __init__(self):
self.money = 500
def func(self):
print(\'*\'*10)
a = A()
print(a.func)
getattr(a,\'func\')() # 对象使用对象能用的方法
print(getattr(a,\'money\')) # 对象使用对象能用的属性
# 对于模块,模块使用模块中的名字,从自己所在的模块中使用自己名字
import os
os.rename(\'userinfo\',\'user\')
getattr(os,\'rename\')(\'user\',\'user_info\')
# 总结
# 类使用类命名空间中的名字
# getattr(类名,\'名字\')
# 对象使用对象能用的方法和属性
# getattr(对象名,\'名字\')
# 模块使用模块中的名字
# import 模块
# getattr(模块名,\'名字\')
# 从自己所在的模块中使用自己名字
# import sys
# getattr(sys.modules[\'__main__\'],名字)
# 关于setattar和delattr
class A:
def __init__(self, name):
self.name = name
def wahaha(self):
print(\'wahahahahaha\')
a = A(\'alex\')
print(a.__dict__) # {\'name\': \'alex\'}
setattr(a, \'age\', 18) # 给a对象新增一个属性
print(a.__dict__) # {\'age\': 18, \'name\': \'alex\'}
setattr(a, \'name\', \'egon\')
print(a.__dict__) # {\'age\': 18, \'name\': \'egon\'}
delattr(a, \'age\')
print(a.__dict__) # {\'name\': \'egon\'}
类中带下划线的内置方法:详细介绍
__doc__ 表示类的描述信息
__dict__ 类或对象中的所有成员
__getitem__
__setitem__
__delitem__ 用于字典索引操作
__iter__ 用于迭代器,列表,字典,元祖进行for循环,是因为它们的内部都定义了__iter__
__module__ 表示当前操作的对象在哪个模块
__class__ 表示当前操作的对象的类是什么
__init__ 构造方法,通过类创建对象时,自动触发执行
__len__ obj对应的类中含有__len__方法,len(obj)才能正常执行
__hash__ obj类自带的,只有实现了__hash__方法,hash(obj)才能正常执行
__str__
__repr__ 在格式化字符串的时候,repr可以作为str的替补,单反之不行
__call__ __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
__del__ 析构方法:在删除一个对象时做一些收尾工作,对象在内存中被释放时自动触发执行,用来关闭在对象中打开的系统资源
python内的垃圾回收机制
__new__ 单例模式:一个类只能创建一个实例。它在__init__之前执行,负责创建一个对象,控制生成一个新实例的过程
__init__ 初始化方法:用于初始化一个新实例,控制这个初始化过程,比如添加某些属性
# __repr__
class Book:
s = \'dsa\'
def __init__(self):
self.name = \'alex\'
def __repr__(self):
return \'zero\'
def __str__(self):
return \'luffy\'
f = Book()
print(f) # luffy # 优先执行__str__里的内容
print(str(f)) # luffy
print(repr(f)) # zero
# 不管是在字符串格式化还是打印对象的时候,__repr__方法都可以作为__str__方法的替补
# 如果__str__和__repr__方法你只能实现一个:先实现__repr__
# __call__
class A:
s = \'ro\'
def __call__(self, *args, **kwargs):
print(\'one\')
def call(self):
print(\'two\')
a = A()
a.call() # two
a() # one # 通过对象名()直接调用类内置的__call__
A()() # one
print(callable(a)) # True
print(callable(A)) # True
print(callable(A.s)) # False, 只能查看方法
# 一个对象是否可调用 完全取决于这个对象对应的类是否实现了__call__
# __eq__
class A:
def __eq__(self, other):
return True
a = A()
b = A()
print(a == b) # True
print(a is b) # False
# == 是由__eq__的返回值来决定的
# __new__ 设计模式:单例模式,即一个类只能有一个实例
class B:
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = object.__new__(cls,*args,**kwargs)
return cls.__instance
def __init__(self,name,age):
self.name = name
self.age = age
a = B(\'alex\',80)
b = B(\'egon\',20)
print(a)
print(b)
print(a.name)
print(b.name)
# <__main__.B object at 0x04F1D330> # 结果相同
# <__main__.B object at 0x04F1D330>
# egon
# egon
使用了__instance=None来存放实例,如果 _instance 为 None,则新建实例,否则直接返回 _instance 存放的实例
再看一个单例模式的例子:
class A: def __new__(cls, *args, **kwargs): if not hasattr(cls,\'instance\'): # 每次实例化,都返回这同一个对象 cls.instance = super().__new__(cls) return cls.instance obj1 = A() obj2 = A() obj1.attr = \'value\' print(obj1.attr) print(obj2.attr) # value # value
其他实现单例模式方式:猛戳此处
__del__
class Foo:
def __del__(self):
print(\'run __del__\')
f1=Foo()
# del f1
print(\'------->\')
>>------->
run __del__
#程序中没有调用del方法,在整个程序执行结束之后调用__del__方法
class Foo:
def __del__(self):
print(\'run __del__\')
f1=Foo()
del f1
print(\'------->\')
>>run __del__
------->
# 调用del方法则先执行析构方法,然后执行
__call__:对象后面加括号,触发执行。构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于__call__方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print(\'__call__\')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
__next__,__iter__用来实现迭代器
# 斐波那契数列
class Fib:
def __init__(self):
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
self.a, self.b = self.b, self.a + self.b
return self.a
f = Fib()
for i in f:
if i > 1000:
break
print(i,end=\'\t\')
例题:
有一个类的init方法如下:
class Person:
def __init__(self,name,age,sex,weight):
self.name = name
self.sex = sex
self.age = age
self.weight = weight
假设有100个person的对象,
若两个对象的obj1,obj2的name和sex属性相同
即obj1.name==obj2.name and obj1.sex==obj2.sex
我们认为两个对象为同一个对象,已知一个列表中的100个对象,对这100个对象进行去重。
### 首先看到这个题,会考虑到使用内置函数__eq__方法 ,只要name和sex相同,返回True,用集合去重时会报错,说对象时不可哈希类型
class Person:
def __init__(self,name,age,sex,weight):
self.name = name
self.sex = sex
self.age = age
self.weight = weight
def __eq__(self, other):
if other.name == self.name and other.sex==self.sex:
return True
def __hash__(self):
return hash(self.name + self.sex)
lis = [Person(\'luffy\',19,\'male\',88),Person(\'luffy\',19,\'male\',120),Person(\'ronan\',19,\'male\',120)]
print(set(lis)) # {<__main__.Person object at 0x05306930>, <__main__.Person object at 0x05306910>}