python面向对象
- 0. 概念
- 1. 对象
- 2. 函数与方法
- 3. 类
- 4. 对象(实例)
- 5. 实例方法、静态方法、类方法
- 6. 类命名空间
- 7. 类的特殊方法
- 7.1 描述符类
- 7.2 特殊方法
- instance.instanceitem调用机制
- __ class __
- __ bases __ 不是特殊描述符
- __ getattr __() / getattr()
- __ setattr __() / setattr()
- @property 与getter/setter/deleter
- __ hasattr __
- __ getattribute __ 与 __ getattr __
- __ getitem __()
- __ setitem __()
- __ new __() ??? 与super.__new__()???
- __ repr __()/__ str__ ()
- __ bytes __ / __format __
- __ del __()
- __ dir __()/dir()
- __ dict __
- __ call __() / callable()
- __ contains __()
- __ bool __() / __ len __ ()
- __ iter __()/ __ next __()
- __ eq __ ()
- __ int __ / __ float __ / __ complex __
- 8. 类属性和方法属性设置
- 9. 继承(派生)
- 10. 多态
- 11. 其他
0. 概念
面向对象变成的3个特征
封装
继承
多态
1. 对象
所有对象都存储在堆中
id
用于标识对象,id()
返回对象标识(内存地址)的整数表示
变量不是对象
2. 函数与方法
函数不是方法
方法属于类和对象
3. 类
3.1 定义类
class Dog():
def __init__(self,name,age):
self.name=name
self.age=age
sedf.user='456'
def sit(self):
print(self.name.title()+"is now sitting!")
def roll_over(self):
print(self.age)
mydog=Dog('123',6)
mydog.sit()
mydog.roll_over()
类中定义的变量,每个都有一个self前缀,以self为前缀的变量,可供类中的所有方法调用,同时还可以通过类的任何实例来访问这些变量,
self.name=name 表示获取存储在形参中的name的值,并将其存储到self变量name中,然后该变量被关联到当前创建的实例中,
上述这种可以通过实例访问的变量称为属性
类中定义了两个方法,此时这些方法不需要额外的信息,因此只有一个形参self,后面创建的实例都会调用这些方法,
sedf.user=‘456’ 将属性设置为默认值,创建实例的时候可以修改
3.1.1 object基类
object是所有自定义类的基类(注意是小写)
python3的在定义类时,默认遵循object的继承关系
class CLS(object) #python2 或python3
pass
class CLS: # Python2 无继承object; python3继承object
pass
class CLS(): # 不建议
pass
3.1.2 type()
type() 方法创建类 http://c.biancheng.net/view/2292.html
# type(类名,由父类名称组成的元组(无继承可以为空),属性名)
CLS=type('cls_name',(),{})
CLS=type('cls_name',(object,),{'property':True})
# 创建实例方法
def func(self):
pass
CLS=type('cls_name',(object,),{'func':func})
cls=CLS()
cls.func() # 这里不需要绑定
3.2 类属性
类属性和实例属性不同
所有类的实例化对象都同时共享类属性
3.2.1 属性调用/修改
- 直接通过类名.属性调用,此修改影响所有实例化对象
- 通过类的实例化对象.属性调用(不推荐(因为如果重名实例对象调用会覆盖类对象)),此修改不影响类的属性和其他实例对象中对应的类属性(就相当于定义了一个新的实例变量)
3.2.2 在类外动态为类增删属性和方法
- 还可以动态的创建类属性http://c.biancheng.net/view/2283.html
类外创建类属性后,实例对象对应的类属性也会被拥有
可以为类动态添加 类方法、实例方法、静态方法
注意,对于类的实例对象,只允许添加实例方法,不能添加类方法和静态方法
为单个实例对象添加方法,不会影响该类的其他实例对象,而如果为类动态的添加方法,则所有的实例对象都可以使用
clas CLS():
pro1
pro2
# 类外动态为类创建属性
CLS.pro3=num
class CLS():
pass
def fun1(self):
pass
@classmethod
def fun2(cls):
pass
@staticmethod
def fun3():
pass
CLS.fun1=fun1 #
CLS.fun2=fun2
CLS.fun3=fun3
# 通过类,在类外添加的实例方法,调用时不需要进行手动绑定
instance=CLS()
instance.func1() # 不需要绑定,注意与下面 对 类的实例对象 添加实例方法 的不同!!!
进阶示例 – 重要
c=C([1,2,3])
def func(self,key,value):
print(self)
print(type(self))
self.x[key]=value
C.__setitem__=func # 因为是通过类 动态添加的 实例方法,所以instance.instanceMethod并不需要手动绑定
c=C([1,2,3])
c[0]=20
c.x # [20,2,3]
# 且由于是通过类添加的方法,所以影响所有的实例对象
b=C([4,5,6])
b[0]=100
b.x # [100,5,6]
进阶!!! – 太重要了,太容易忘记了
class A:
def __init__(self,x):
self.x=x
class C:
a=A(1)
def __init__(self,y):
self.y=y
c1=C(2)
c2=C(2)
# id(c1) != id(c2)
# id(c1.a) == id(c2.a) !!! 这个需要注意,本质上是类属性,所以所有的实例对象都是共享的,不管该类属性指向的是什么
# 所以通过c1.a.x进行的修改,也会反应在c2.a.x上 !!!
4. 对象(实例)
4.1 实例属性
所有类都创建对象,所有对象都包含称为属性的特征(在开头段落中称为属性)。使用__init __()方法通过为对象的初始属性提供其默认值(或状态)来初始化(例如,指定)对象的初始属性。此方法必须至少有一个参数以及自变量,它引用对象本身(例如,Dog)
class Dog:
# Initializer / Instance Attributes
def __init__(self, name, age):
self.name = name
self.age = age
4.1.1 实例属性/方法的调用/修改
实例属性只能通过实例化对象.属性名进行调用,不能通过类名.实例属性名进行访问
http://c.biancheng.net/view/2283.html
注意细节:在类方法中定义的实例属性,只有在调用了类的实例方法后才能进行调用
4.1.2 构造方法__init__()
即使不手动创建构造函数,创建类的时候也会自动创建1个__init__(self)的构造方法,即默认构造方法
class A:
# 在不手动创建__init__()构造方法时的默认构造方法
def __init__(self):
pass # 源码就是pass
4.1.3 self参数
自动将对象本身的引用作为参数,传递到实例方法的第一个参数self里,就是自动绑定实例对象
instance.method()
等价于method(instance)
4.1.4 在类外动态为实例对象增删属性和方法
instance=Cls()
instance2=Cls()
instance.item=value # 只是给当前对象动态增加了属性,instance2没有item属性
del instance.item # 删除属性
可以利用直接复制的方式进行动态的添加对象方法,但是不会自动的将方法的调用者绑定到方法的第一个参数(即使第一个参数定义的是self),因此在程序中必须手动为第一个参数传入参数值,传入的是实例对象名
动态添加方法时,不限制形参个数
class F:
pass
def fun(self):
pass
instance=F()
instance.fun=fun()
instance.fun(instance) # 手动绑定 ,注意与上面,用过类 添加实例对象方法 的不同(不需要手动绑定)
区别如下
class C:
def __init__(self,x):
self.x=x
def __getitem__(self,index):
return self.x[index]
def func(self):
print('instance method')
# 通过类对实例方法进行绑定
C.func=func
c=C(1)
print(c.func())
# 通过实例对象对实例方法进行绑定
c.func2=func
c.func2(c)
另一种无需手动绑定的方法 – 这个才工程中需要的方法
利用
types模块的MethodType方法
import types
instance.instanceMethod=types.MethodTypes(func,instance)
instance.instanceMethod() # 自动绑定self
__ slots __ 方法
只限制为实例对象动态增加方法,而无法限制动态地为类添加属性和方法
__slots__
属性对由该类派生出来的子类,也不起作用
http://c.biancheng.net/view/2291.html
在类中定义__slots__
,表明这个类的所有实例属性这有这么多了,实例不能再有除__slots__
中所列名称之外的其他(实例)属性和方法
注意:建议不要使用__slots__
属性来禁止类的用户新增实例属性,__slots
的实际作用,应该是用于优化的 节省内存,(不是为了约束程序员)
经过实验发现,在定义了__slots__
后,调用instance.__dict__
会报错???(__slots__
与__dict__
的关系见《流畅的python》 )(进一步说明,python会在各个实例中使用类似元组的结构存储实例变量,从而避免使用消耗内存的__dict__
属性,因为散列表虽然能够提升访问速度,但是会消耗大量内存)
具体应用场景还要再看???(好像是在应用处理外万个实例独享时,仅当权衡当下的需求并仔细搜集资料证明确实有必要时,在使用__slots__
)
class C:
__slots=('name','add','info')
def info(self):
pass
instance=C()
# 可以动态指定的添加方法和属性
instance.name='name' # 可以添加
instance.value=20 # 不可以添加,error: 'C' object has no attribute 'value'
def info(self,*args):
pass
def func(self,*args):
print('func')
instance.info=info
instance.info(instance) # 必须这样调用
instance.func=func # 不能动态添加,error: 'C' object has no attribute 'func'
instance.func()
补充
类外创建类属性后,实例对象对应的类属性也会被拥有
可以为类动态添加 类方法、实例方法、静态方法
注意,对于类的实例对象,只允许添加实例方法,不能添加类方法和静态方法
为单个实例对象添加方法,不会影响该类的其他实例对象,而如果为类动态的添加方法,则所有的实例对象都可以使用
修改时的注意事项
这是个容易理解错的点
下面能修改,其实是因为在+=1后,相当于重新在类外创建了一个self.num对象,修改前后的id()并不一样
class C:
def __init__(self):
self.num=1
def get(self):
pass
c=C()
c.num # 1
c.num+=1
c.num # 2
4.2 实例化对象例子
#1
class Dog:
# Class Attribute
species = 'mammal'
# Initializer / Instance Attributes
def __init__(self, name, age):
self.name = name
self.age = age
# Instantiate the Dog object
philo = Dog("Philo", 5)
mikey = Dog("Mikey", 6)
# Access the instance attributes
print("{} is {} and {} is {}.".format(
philo.name, philo.age, mikey.name, mikey.age))
# Is Philo a mammal?
if philo.species == "mammal":
print("{0} is a {1}!".format(philo.name, philo.species))
#2
class Dog:
# Class Attribute
species = 'mammal'
# Initializer / Instance Attributes
def __init__(self, name, age):
self.name = name
self.age = age
# instance method
def description(self):
return "{} is {} years old".format(self.name, self.age)
# instance method
def speak(self, sound):
return "{} says {}".format(self.name, sound)
# Instantiate the Dog object
mikey = Dog("Mikey", 6)
# call our instance methods
print(mikey.description())
print(mikey.speak("Gruff Gruff"))
#3
class Email:
def __init__(self):
self.is_sent = False
def send_email(self):
self.is_sent = True
my_email = Email()
print(my_email.is_sent)
my_email.send_email()
print(my_email.is_sent)
5. 实例方法、静态方法、类方法
- 类方法:@classmethod
- 静态方法:@staticmethod
- 实例方法:不加任何修饰
类中定义的默认是实例方法
类的构造方法也属于实例方法
5.1 实例方法
def fun(self):
pass
调用
# 对象.对象方法()
instance.instancemethod()
# 类名.对象方法(对象)
# 通过这种方法必须进行手动传参进行绑定
#(非绑定法,一般不推荐)
instance=Cls()
Cls.instancemethod(instance)
进阶
class C:
def __init__(self,*args):
pass
def create(self,*args):
C(*args)
# 等价于
# c=type(self)
# c(*args) # version1
# c.__class__(*args) # version2
进阶2 – 重要:与类名.实例方法有关/与继承相关
核心思想还是:类名.实例方法,要讲实例对象作为参数进行传递绑定
# 例1
class A(object):
def __init__(self):
self.a=5
def function_a(self):
print('base class A :self.a',self.a)
class B(A):
def __init__(self):
# A.__init__() # 这种写法是不对的
# A.__init__(self) # 通过类名.实例方法进行调用,需要手动绑定实例对象,进行传参
super(B,self).__init() # Python2 的写法,反而更容易理解
super().__init__() # python3的写法,super在解决多继承问题上有明显的优势
self.b=10
def function_b(self):
print('son class B: self.b',self.b)
self.function_a()
b=B()
b.function_b()
# 例2
# !!! 显然会后一个会覆盖前一个,所以这是不正确的,特别是多继承中,经常存在这种冲突问题
class A(object):
def __init__(self,name):
self.name=name
def function_a(self):
pass
class B(A):
def __init__(self,name1,name2):
# A.__init__() # 这种写法是不对的
A.__init__(self,name1) # 通过类名.实例方法进行调用,需要手动绑定实例对象,进行传参
# super(B,self).__init() # Python2 的写法,反而更容易理解
# super().__init__() # python3的写法,super在解决多继承问题上有明显的优势
# self.b=10
A.__init__(self,name2)
def function_b(self):
pass
self.function_a()
b=B('name1','name2')
print(b.name)
# b.function_b()
5.2 类方法
@classmethod
def fun(cls):
pass
python会自动将类绑定给cls参数(绑定的不是类对象),因此在调用类方法时,无需显示的给cls传参
调用
#类名.类方法名
Cls.CLsmethod()
#实例化对象; 实例化对象.类方法名(不推荐) ???
instance=Cls()
instace.CLsmethod()
# 这种方法其实就是利用了@classmethod的装饰器机制,如下的伪代码执行机制一样
def classmethod(func):
def warpper(*args): # args 指向instance
func(type(args).__class-_) # type(args).__class__执行了原函数的cls
return warpper
@classmethod
def m(cls):
pass
instance.m()
类方法的进阶使用
利用实例方法创建类对象
class C:
def __init__(self):
pass
@classmethod
def create(cls,*args):
cls(*args) # 调用的还是C的__init__方法,相当于创建了一个对象
# 等价于 C(*args)!!!
# 其实通过实例方法或静态方法应该也能创建对象
5.3 静态方法
就是定义在类命名空间中的函数
而基本函数是定义在全局/局部命名空间内的函数
@staticmethod
def fun():
pass
静态方法没有self、cls等特殊参数
调用
#类名.静态方法名
CLs.staticmethod()
#实例化对象.静态方法名
instance=Cls()
instance.staticmethod()
5.4 类/实例/静态-属性/方法调用机制
很重要!!!
class A:
# clsitem
aa=10
__bb=100 # 在类外不能被类名和实例对象名进行访问
_c=1 # 单下划线,约定保护属性,但是解释器不做特殊处理
# 实例方法
def instancefun(self):
# instanceitem
self.instance_aa=20
print('self.clsitem',self.aa)
print('self.clsitem 私有',self.__bb) # 私有属性只能在类内 被 类方法和实例方法访问,在类外无法访问
self.aa=20
self.__bb=200
print('self修改类属性',self.aa)
print('self修改类私有属性',self.__bb)
print('self.instanceitem',self.instance_aa)
self.instancefun2() #实例方法能调用实例方法
self.fun2() #实力方法能调用类方法
def instancefun2(self):
print('instance fun')
# 类方法
@classmethod
def fun(cls):
print('cls.clsitem',cls.aa)
print('cls.clsitem 私有',cls.__bb)
# print('self.instanceitem',self.aa) # 不能进行访问,不能使用self方法
cls.fun2() # 类方法能调用类方法,不能调用实例方法
@classmethod
def fun2(cls):
print('class fun')
@staticmethod
def staticfun():
# print('static clsitem',aa) # 静态方法不能访问类属性以及实例属性
print(A.aa) # 静态方法可以通过类名访问
print(A.__bb) # 静态方法可以通过类名访问私有属性
pass
# 类方法
A.fun()
instA=A()
instA.fun()
print('*'*20)
# 实例方法
# 一旦创建了 和类属性同名的 实例属性,通过实例访问的就是实例属性了
instA.instancefun()
A.fun() # self修改类属性不修改clsitem,也不改变类的私有属性
print('*'*20)
# 静态方法
# 当作类命名空间下的简单函数使用
A.staticfun()
instA.staticfun()
更重要!!!
class A(object):
aa=10
__bb=100
_c=1
@classmethod
def classfunc(cls,x,y):
cls.x=x # 自动绑定到类属性当中,且可以被实例对象访问
cls.__y=y
print('cfunc')
print(x**2,y**2)
print('cls 私有属性b',cls.__bb) # 必须加上cls.
print('cls 私有属性y',cls.__y)
print('cls 约定保护属性',cls._c)
# print('cls 调用 instancemethod',cls.instancefunc(self,10)) #不能使用这种方法,也就是上述不推荐使用类名.实例方法(实例对象)进行调用
print('cls 调用 staticmethod',cls.staticfunc(10))
print('cls 调用 classmethod',cls.classfunc2(1,2))
@classmethod
def classfunc2(cls,x1,y2):
print('cls method 2')
print('检测实例属性对类的私有属性的修改',cls.__bb) # 不会影响
def instancefunc(self,m):
self.m=m # 自动绑定到实例属性当中
print('ifunc')
print(m**2)
print('instancefunc 访问 cls私有属性',self.__bb) # 私有属性只能在类内被 实例方法和类方法访问,在类外不能访问
print('instancefunc 访问 cls属性',self._c)
print('instancefunc 调用 类方法',self.classfunc2(1,2)) # 可以调用类方法,但是不推荐
print('instancefunc 调用 instancefunc2',self.instancefunc2(5))
print('instancefunc 调用 staticfunc',self.staticfunc(5)) # 可以访问静态方法
# 修改
self.aa=20
self.__bb=200
print('self修改类属性',self.aa) # 相当于变成了实例属性了
print('self修改类私有属性',self.__bb) # 相当于变成了实例属性了
def instancefunc2(slef,m1):
print('ifunc 2')
@staticmethod
def staticfunc(p):
print('sfunc')
print(p**2)
print('staticfunc 通过类名 访问 cls私有属性',A.__bb) # 可以通过类名访问
# print('staticfunc 访问 cls私有属性',__bb) # 不能直接访问
# print('staticfunc 访问 cls私有属性',aa)
# print('staticfunc 调用 staticfunc2',staticfunc(10)) # 不能直接访问其他的静态方法
print('staticfunc 调用 staticfunc2',A.staticfunc2(10)) # 可以通过类名.静态方法访问
print('staticfunc 调用 staticfunc2',A.classfunc2(10,20)) # 可以通过类名.类方法访问
# print('staticfunc 调用 staticfunc2',A.instancefunc2(10))
@staticmethod
def staticfunc2(p):
print('sfunc2')
6. 类命名空间
http://c.biancheng.net/view/2274.html
7. 类的特殊方法
https://blog.csdn.net/redrose2100/article/details/118092188
https://blog.csdn.net/pysense/article/details/103095238 – 还没看
https://blog.csdn.net/dashoumeixi/article/details/80710521 – 还没看
https://blog.csdn.net/lx_ros/article/details/121216462 – 重要!!
7.1 描述符类
https://www.bilibili.com/video/BV1AN4y137uT?p=101&spm_id_from=pageDriver&vd_source=7155082256127a432d5ed516a6423e20
https://doc.itprojects.cn/0001.zhishi/python.0003.python3hexinbiancheng/index.html#/06.02.miaoshufu – 进阶,很重要!!!
https://www.bilibili.com/video/BV1AN4y137uT?p=107&spm_id_from=pageDriver&vd_source=7155082256127a432d5ed516a6423e20 – 无敌重要!!!,会这个基本把类的参数传递看的差不多了
https://www.bilibili.com/video/BV1AN4y137uT?p=116&spm_id_from=pageDriver&vd_source=7155082256127a432d5ed516a6423e20 – 无敌重要,!!! 会这个基本类属性和实例属性差不多了
哔站码农天高 – 有一个很重要,还没看
有时也叫描述符
在实例化对象后,对实例属性的修改与访问,本质上是通过描述符类完成的(python自动调用)
但是可以自己重写方法,实现自定义的功能
不能在类外进行直接调用
允许使用类中的普通方法进行调用、修改、重写
如果一个类中有,
__get__
,__set__
,__delete__
中的任何一个方法,那么这个类创建的对象,可以叫做"描述符对象"
如果有另外一个类,这个类中的一个 ‘类属性’,这个类属性对应的是 上面类创建的实例对象,则称这个 类属性 叫做 ‘描述符’
注意:只有描述符对象作为类属性时才会自动调用,如果是作为实例属性则调用机制不同https://www.bilibili.com/video/BV1AN4y137uT?p=115&spm_id_from=pageDriver&vd_source=7155082256127a432d5ed516a6423e20
描述符的作用:当访问一个属性时,可以不直接给一个值,而是接一个表述器,让访问和修改设置时自动调用
__get__
方法和__set__
方法,并在__get__
和__set__
方法做某些处理,就可以实现更改操作属性行为的目的,说白了就是访问属性,变成了调用方法,与@property类似
注意
__ set 、 get __ 、__ delete __ 定义了描述符协议
实现了__ set __ () 和__ get __ () 方法的描述符类被称为 数据描述符类
只实现了__ get __ () 方法的描述符类被称为非数据描述符类
https://www.bilibili.com/video/BV1AN4y137uT?p=111&spm_id_from=pageDriver&vd_source=7155082256127a432d5ed516a6423e20区别 – 很重要
(个人建议,如果要是使用描述符,建议使用数据描述符)
__ set __ / __ get __ / __ delete __
当自定义一个类属性,且类属性是一个具有
__gett__
,__set__
,__delete__
3个方法中任意实现的一个的 类 创建的实例对象,那么
访问这个类属性时,自动调用__gett__
设置这个类属性时,自动调用__set__
删除这个类属性时,自动调用__delete__
class Name:
def __get__(self,obj,objtype):
return 'perter'
class A:
name=Name()
a=A()
a.name # perter
A.name # perter
class A:
def __init__(self):
self.name=Name()
a=A()
a.name # perter
进阶!!!
class Name(object):
def __init__(self):
self.__name=None
def __get__(self,instance,owner):
print('self',self) # 指向的是当前的 描述符对象
print('instance',instace) # 类的类属性
print('owner') # (类属性)所属的类
return self.__name
def __set__(self,instance,value):
print('instance',instance) # instance <__main__.Person object at 0x0000018DB53A2E80>
print('value',value)
if isinstance(value,str):
self.__name=value
else:
raise TypeError('必须是字符串')
def __delete__(self,instance):
del self.__name
class Person(object):
# name 是一个描述符
name=Name()
# def __init__(self):
# print(self.name) # self.name 也是描述符,此时调用__get__方法
# self.name=xxx # 此时调用__set__方法
# 通过实例对象 调用类属性(描述符)
p=Person()
p.name='li' # def __set__(self,instance,value): name()创建的对象指向了self,p传给了instance,'li'传给了vlaue
p.name # 'li'
# 通过类 调用类属性(描述符)
Person.name='li' # def __set__(self,instance,value): name()创建的对象指向了self,instance为None,'li'传给了vlaue
描述符下的调用机制
先看下面的
__dict__
调用机制,再理解
instance.property
先找instance.__dict__['property']
是否存在,不存在则到type(instance).__dict__['property']
中查找,然后找type(instance).__bases__.__dict__
查找(注意.__mro__
的继承顺序)
上述值得注意的是,如果到普通值就输出,如果找到的是一个 描述符,则调用__get__
方法
与@property之间的关系
@property
就是利用上述的三个方法实现的
@property
方法太过于臃肿,需要为每个属性建立对应的方法
描述符与__ getattribute __
实际上就是通过
__getattribute__
判断是否该property
是否是描述符(该属性是否有__get__
方法),如果不是则是普通属性,如果没找到则调用__getattr__
注意:如果是自己重写了__getattribute__
,则会使内部的描述符失效
7.2 特殊方法
instance.instanceitem调用机制
instance.instanceitem
调用时,python会自动隐式调用特殊方法__getattribute__()
,并按照顺序进行查找属性instanceitem
验证该属性是否为
instance
的数据描述符
从instanace.__dict__
中查找该属性
验证该属性是否为instance
的非数据描述符
__ class __
__class__
不是特殊描述符,属于实例对象的属性
查看对象所属的类
作用在实例对象上
class C:
def __init__(self,x):
pass
c=C(1)
type(c) # <class '__main__.C'>
print(c.__class__) # <class '__main__.C'>
type(c) == c.__class__ # True
type(c) is c.__class__ # True
# 显示类名
c.__class__.__name__ # C
# 进阶
# 创建一个新的对象 ???
b=c.__class__(10)
b.x # 10
__ bases __ 不是特殊描述符
__bases__
不是特殊描述符,是对象的属性之一,如__class__
相似
查看类的继承关系,即查看类的父类
作用在类上
class C:
pass
class B(C):
pass
B.__bases__ # 是一个tuple (<class '__main__.C'>,)
# B().__bases__ # 利用实例对象调用,会报错
__ getattr __() / getattr()
运行机制:
myobj.x
(也可以是myobj.func
)形式的查找顺序为,从实例myobj
查找 --> 从;myobj
所属的类myobj.__class__
中查找 --> 顺着继承树进行查找 --> 全都找不到,则调用myobj
所属类中定义的__getattr__
方法
即,如果要访问查找的属性没有找到,则调用__getattr__
方法
应用场景,相当于利用@property
方法太过于麻烦,需要为每个属性都建立一个只读函数,而__getattr__
是另一种实现方法,且效率更高
注意:只有在找不到属性的时候才会调用__getattr__
方法!!!
见《流畅的python》10-10示例
最好在自己实现的__getattr__
方法中也要抛出异常
# 自定义__getatrr__方法时
# 也要定义抛出异常机制
raise AttributeError('string...')
getattr('instance','attribution')
直接调用__getattr__
方法,可以查找属性也可以查找方法
与 __ get __的不同
http://c.biancheng.net/view/5468.html
http://c.biancheng.net/view/2378.html
见上面的描述符与
__getattribute__
__ setattr __() / setattr()
是
@property.setter
的另一种实现方法
好像并不能创建方法
# 通常情况下
def __setattr__(self,key,value):
super().__setattr__(key,value) # 直接调用从超类继承的方法
setattr(instance,‘attribution’,value)
自动调用__setattr__()
方法,如果存在就赋值,如果不存在就创建一个新的属性
与 __ set __的不同???
@property 与getter/setter/deleter
与
getter
、setter
的区别:其实getter
、setter
这些方法的一个同一叫法(自我理解)
@property
是将(实例)方法变成一个只读同名属性,见下面的例子
实际上是转换成了同名的getter方法,或者就是返回return 的值
https://blog.csdn.net/z_feng12489/article/details/89387141
https://blog.csdn.net/qq_41359051/article/details/82939655 --重要!!!
更一般的说法是 把一个方法变成只读的,并以调用属性的方式调用该方法
class C(object):
def __init__(self,x):
self.__x=x
def getx(self):
return self.__x
def setx(self,value):
self.__x=value
def delx(self):
del self.__x
# property(fget,fset,fdel,doc)
# property()方法返回一个property属性
# 当实例化后,如果c是类C的实例,c.x调用
num=property(getx,setx,delx,doc)
c=C() # 实例化
c.num # 调用getter方法
c.num=value # 调用setter方法
def c.num # 调用deleter方法
# 利用property实现简化写法,等价于上述的例子
class C(object):
def __init__(self,x):
self.__x=x
@property # 只读方法
# 当然也可以写成@property def x(): pass 的形式,但是需要注意的是
# self.__x 不能写成self.x 否则会出错
def f(self):
return self.__x
@f.setter # 可读可写方法
def f(self,num):
self.__x=num
@f.deleter # 可读可写可删除方法
def f(self,num):
del self.__x
# 建议写成同名方法
class C:
def __init__(self,x,y):
self.__x=x
self.__y=y
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
@property同样可以作用于函数
class C:
def __init__(self):
pass
@property
def start(self):
pass # some code...
return None
c=C()
c.start
@property的进阶
class G(object):
def __init__(self):
self.origin_price=100
self.discount=0.8
@property
def price(self):
new_price=self.origin_price*self.discount
return new_price
@property.setter
def price(self,value):
self.origin_price=value
@property.getter
def price(self):
del self.origin_price
g=G()
g.price
g.price=100
del g.price
__ hasattr __
用于判断某个类(对象)或实例对象是否包含指定名称的属性或方法
hasattr(cls,property/method)
hasattr(obj,method_name/property_name)
hasattr(obj,property_name)
__ getattribute __ 与 __ getattr __
__getattribute__
叫属性拦截器
调用instance.property
的时候,其实是先调用__ getattribute __
方法
注意:在调用instance.method()
的时候,都是先调用__getattribute__
方法
注意:实现__getattr__
,没实现__getattribute__
时,在没有该属性的时候instance.property
也会自动调用__getattr__
;
class C(object):
def __init__(self):
self.name='name'
self.age=1
self.gender='male'
def __getattribute__(self,attr): # attr 是对属性或方法的引用,但是type(attr) : str类型
if attr=='age':
return '拦截'
else:
return object.__getattribute__(self,attr) # 通过父类取属性,但是传入的是当前的self对象
进阶
def __getattribute__(self,attr):
中的attr
是str
类型,在instance.__dict__
的key
中进行查找
注意事项
下面的例子会出现无限递归
因此要实现属性查找必须调用父类的方法,即object.__getattribute__(self,attr)
class C:
def __init__(self,x):
self.x=x
def __getattribute__(self,obj):
return self.x # 会导致进入无限递归
与__ getattr __之间的区别
当访问/调用 属性或方法时,如果
__getattribute__
方法中没有找到,就会调用__getattr__
方法,
(目前我的理解,因为__getattribute__
其实就是属性拦截器,主要是拦截的作用,因此实际调用instance.porperty
的时候,会先调用__getattribute__
方法,看看有没有对某些属性做了拦截操作,如果没有就按照object.__getattribute__(self,obj)
方法执行,剩下的就是上述说的__getarttr__
的方法,根据继承关系查找,最终如果要访问查找的属性没有找到,则调用__getattr__
方法,然后抛出异常
总结:__getattribute__
比__getattr__
的优先级高;不管属性(或方法)存不存在都是先不条件调用;重要前提:只有在__getattribute__
和__getattr__
在自定义方法中同时被定义的时候,只有在__getattribute__
方法中找不到对应的属性时,且抛出AttributeError
异常时,才会调用__getattr__
(如果两个方法同时被实现,但是不抛出异常,那么属性不存在的时候不会调用__getattr__
方法);尽量把抛出异常(对不存在属性的处理)写在__getattr__
方法中
注意:只有在找不到属性的时候才会调用__getattr__
方法
class C(object):
def __init__(self,x):
self.x=x
class B(C):
def __init__(self,x,y):
super(B,self).__init__(x)
self.y=y
def __getattr__(self,obj):
raise AttributeError(f"call built-in funcstion __getattr__: {obj} is not self.__name__'s property: ")
def __getattribute__(self,obj):
if obj=='z':
# return "z is not self.__name__'s property"
raise AttributeError
else:
return object.__getattribute__(self,obj)
b=B(1,2)
print(b.x) # 1
print(b.m) # z is not self.__name__'s property
__ getattribute __/ __ getattr __/ get
见上面的描述符与
__getattribute__
__ getitem __()
提供类似list索引访问机制
序列协议:只需要定义
__len__
和__getitem__
方法就行
注意,并不是一定要实现iter方法,从而让其变成可迭代对象,因为序列是序列,可迭代对象是可迭代对象
注意:在自定义时,也要实现异常处理机制,虽然可能在不正常索引时也会自动抛出异常
https://blog.csdn.net/chituozha5528/article/details/78354833
__ getitem __ /__ iter __ /__ next __
https://blog.csdn.net/qq_24805141/article/details/81411775
https://blog.csdn.net/QLeelq/article/details/123841591
__ setitem __()
用于序列化协议下的赋值操作,提供赋值、修改元素的功能,如
instance[index]=value
setitem/setattr getitem/getattr
setitem、getitem
用于序列写一下的操作,如instance[index]=value
setattr、getattr
用于属性控制,如instance.key=value
__ new __() ??? 与super.new()???
__ new __ / __ init __
调用类(生成对象时),实际上会先利用
__new__
方法创建一个实例,然后运行__init__
方法,初始化实例,并将该对象返回
__new__
先于__init__
执行
__new__
是由object基类提供的内置的静态方法,作用:1. 在内存中为对象分配空间;2. 返回对象的引用
在调用__new__
方法后,即获得对象的引用后,自动将引用作为__init__
的第一个参数进行传递,实现对象初始化
# https://blog.csdn.net/sj2050/article/details/81172022
# 对于自己重写__new__方法
# 固定写法
class MyClass(object):
def __new__(cls,*args,**kwargs):
return super().__new__(cls) # 为对象分配空间,返回对象的引用
def __init__(self):
pass # 初始化方法
c=MyClass() #
__ repr ()/ str__ ()
显示属性,输出某个实例化对象时,调用的就是该对象的 __ repr __() 方法,输出的是该方法的返回值 (这里是输出而不是初始化,所以不同与__ init__())
通过重写方法得到输出的实例化对象的信息
repr
应该注重现实类对象的继承关系和内存地址等信息,(以便于开发者理解的方式返回对象的字符串表示形式)
str
应该注重简单现实类对象的形式,简单print,(以便于用户理解的方式返回对象的字符串表示)
print(instance)
时,对首先尝试调用__str__()
函数,如果没有再尝试调用__repr__
函数,返回结果见下面的注意情况
见python进阶中的 !s / !r
repr
与!r
类似,str
与!s
类似
print(obj)
# print实例化对象得到的是:类名+object at+内存地址
print(obj.__repr__())
# 例子
class Vector(object):
def __init__(self,x,y):
self.x=x
self.y=y
def __repr__(self):
return 'repr' # 不能写成print('repr')
def __str__(self):
return 'str'
v=Vector(1,2)
print(v.__repr__()) # 等价于repr(instance)
print(repr(v))
print(v.__str__()) # 等价于str(instance)
print(str(v))
# 注意事项
# 1. __repr__不存在(自定义类中没有重载该方法),__str__存在
print(repr(v)) # 返回内存地址
print(str(v)) # 返回__str__() return的内容
# 2. __repr__存在,__str__不存在
print(repr(v)) # 返回__repr__() return的内容
print(str(v)) # 返回__repr__() return的内容!!!
__ bytes __ / __format __
__bytes__
方法与__str__
方法类似,bytes()
函数调用它获取对象的字节序列形式
__format__
方法会被内置的format
函数和str.format()
方法调用,使用特殊格式代码显示对象的字符串表示形式
__repr__、__str__、__format__
必须返回unicode字符串(str类型),__bytes__
返回字节序列
__format__
当自定义类中,没有定义
__format__
方法,则调用__str__
方法作为返回,前提是没有定义格式说明符
如果定义了格式说明符,且自定义类中没有定义__format__
方法,则会抛出异常
__ del __()
销毁实例化对象(一般情况下,python能自动销毁不需要的实例对象)
变量自动销毁机制(没细看)http://c.biancheng.net/view/2371.html
与垃圾回收机制有关,见python进阶https://blog.csdn.net/L_fengzifei/article/details/123559473
del
只是删除对象的引用,并不是直接调用__del__
方法,之后当(回收机制中,该对象的引用计数为0的时候,才会自动调用__del__
方法)
可以用sys.getrefcount()
测量引用对象的个数
注意:如果自定义实现__del__
方法,对应的实例则无法被Python的循环引用收集器收集,尽量不要自定义__del__
class C(object):
def __init__(self,name):
self.__name=name
def get(self):
return self.__name
def __del__(self):
print('__del__被调用')
a=[1,2,3]
c=C(a)
b=c
del c # 不调用__del__
print('*'*10)
del b # 调用__del__,引用计数为0,所以
sys.getrefcount()
测量引用对象的个数
注意显示的是比实际引用的多一个
import sys
sys.getrefcount(instance_name) # return a int value
与 __ delete __()的不同???
__ delitem __
__delitem__
是与序列协议有关的方法
__delitem__(p) # 删除索引p位置的值
__ dir __()/dir()
https://blog.csdn.net/lis_12/article/details/53521554
dir()
或__dir__()
是方法
列出对象拥有的所有属性名和方法名
dir(obj) # 利用内置函数显示信息,本质上调用的还是__dir__() 方法
# 等价于
obj.__dir__()
包括所有父类的属性
__ dict __
https://www.zhihu.com/question/302703968
https://blog.csdn.net/lis_12/article/details/53521554
__dict__
是属性,不是方法!!!
不同于__ dir __() /dir(),
__ dict __
方法列出的是字典形式,
__dict__
是dir()
的子集
部分内置类型没有__dict__
属性
继承关系下,子类的__ dict __
不包含父类的__ dict __
cls.__dict__ # 类属性和方法(所有方法,也包括实例方法)!!!
instance.__dict__ # 实例属性,不包括方法!!!
class C:
x=1
def __init__(self):
self.y=1
c=C()
c.__dict__ # {'y': 1}
C.__dict__ # mappingproxy({'__module__': '__main__', 'x': 1, '__init__': <function C.__init__ at 0x0000023712220D30>,
# '__dict__': <attribute '__dict__' of 'C' objects>, '__weakref__': <attribute '__weakref__' of 'C' objects>,
# '__doc__': None})
__ dict __调用机制
instance.property/method
先从instance.__dict__
中寻找对应的属性或方法,(但由于instance.__dict__
中没有存储实例方法,只存储了实例属性)所以需要到cls.__dict__
中寻找,(而cls.__dict__
中存储了实例方法),这相当于type(instance).__dict__
中找属性
vars
class C:
pass
c=C()
c.__dict__ 等价于 vars(c)
__ call __() / callable()
判断对象是否是可调用
callable(certain_obj) # True or False
自定义可调用对象
将类的实例化对象自身变成可调用的形式
https://zhuanlan.zhihu.com/p/184979212
https://blog.csdn.net/IAlexanderI/article/details/68946731
class A:
def __init__():
pass
def __call__(self,xxx): # 将实例对象变成可调用的形式
pass
obj=A()
obj(xxx) # 自动调用__call__ 方法,而对于其他的方法,仍然是用instance.method进行调用的
http://c.biancheng.net/view/2380.html
__ call __ 与hasatter方法???
__ contains __()
对应于
x in xxx
的实现(判断)
如果没有__contains__
,则尝试调用__getitem__
方法,迭代查找
def __contains__(self,items):
if xxx:
return True
else:
return False
__ bool __() / __ len __ ()
需要利用bool值进行判断是上下文,如
if, while, and, or, not
等语句或运算符,当需要判断一个instance对象是否是True
还是False
时,自动调用bool()
函数,其本质上是调用内置的__bool__()
函数
对于自定义的对象,在没有重载的情况下,默认是True
,无论class __ init __ 初始化什么类型的对象
bool 与 len 的关系
如果在没有重载
__bool__()
函数,则尝试调用__len__()
函数,当__len__() return 0
的时候bool(instance)
返回False
class Vector(object):
def __init__(self,x,y):
self.x=x
self.y=y
self.l=[x,y]
def __repr__(self):
return 'repr'
# def __str__(self):
# return 'str'
# def __bool__(self):
# return bool(abs(self.x+self.y))
def __len__(self):
return len(self.l)
__ iter __()/ __ next __()
迭代器:见https://blog.csdn.net/L_fengzifei/article/details/123559473
__ eq __ ()
对于数学运算符
==
来说,自动调用__eq__()
方法
注意:==
比较的是值,不是对象;is
比较的是内存地址
__ int __ / __ float __ / __ complex __
被
int() float()
方法调用,实现强制类型转换
8. 类属性和方法属性设置
公有属性、保护属性、私有属性
重要https://blog.csdn.net/PanYHHH/article/details/107813644
公有(public) 属性和方法
在类的外部、内部、以及子类都都能被正常方法
默认情况下 定义的属性和方法都是公有的
protect方法
http://c.biancheng.net/view/2286.html
单下划线,约定俗称为 ‘受保护’的属性
私有(private) 属性和方法
只有在本类内部使用,在类外和子类都无法使用https://blog.csdn.net/weixin_51647957/article/details/120657616(私有属性和方法无法继承,但是可以强制调用)
属性和方法前加__(双下划线) 是私有的,实际上解释器内部变成了_clsName__method/propertyname
(单下划线+类名+双下划线+私有属性名)
# 例子1
class C:
def __init__(self,x):
self.__x=x
c=C(1)
c.__dict__ # {'_C__x': 1}
# 例子2
class C:
__a=20
def __init__(self,x):
self.__x=x
# 也可以访问类的私有属性,甚至是修改
C._C__a=20
私有方法的类外访问与修改
注意,当在类外使用
instance._clsname__property
的方式就可以进行访问甚至是修改(实例的)私有方法
当在类外使用clsname._clsname__property
的方式就可以进行访问甚至是修改(类的)私有方法
注意!!!
class C():
__a=10
def __init__(self,x):
self.__x=x
def func(self):
pass
@property
def x(self):
return self.__x
@classmethod
def a(cls):
return cls.__a
# c=C([1,2,3])
c=C(1)
print(C.__dict__)
print(c.__dict__)
# print(C.__a) # 私有属性,不能调用
print('*'*20)
C.__m=10
print(C.__dict__)
print(c.__dict__)
print(C.__m) # 并不是类的私有属性
print('*'*20)
C._C__n=20
print(C.__dict__)
print(c.__dict__)
# print(C.__n) # 私有属性,不能调用,可以在外进行赋值
#########
c=C(1)
print(C.__dict__)
print(c.__dict__)
# print(c.__a) # 私有方法,不能调用
print('*'*20)
c.__m=10
print(C.__dict__)
print(c.__dict__)
print(c.__m) # 不是实例的私有方法,不能调用
print('*'*20)
c._C__n=20
print(C.__dict__)
print(c.__dict__)
# print(c.__n) # 私有方法,不能调用,可以在外进行赋值
各种下划线
__CertrainName__
(特殊方法) 开头结尾都有双下划线的自己定义的时候不要用,这是python自己调用时使用的
注:
全局命名空间下,_name 单下划线,为模块内的保护性属性,不会被import http://c.biancheng.net/view/2401.html
_name
单下划线能够在类外进行访问与修改,但是约定不在类外进行访问
__name
双下划线不能在类外进行访问与修改,实际上解释器内部变成了__clsName_method/propertyname
所以不能访问
9. 继承(派生)
一个子类可以继承自多个父类(不建议使用多继承)
对于多继承时的父类构造方法,要注意顺序问题http://c.biancheng.net/view/2290.html
子类继承了父类所有的属性和方法
创建子类实例时,首先需要完成的是给父类所有属性赋值,
在定义子类的时候,必须在括号内指定父类的名称
在子类的def __int__()
中接受创建父类实例时所需要的信息
__slots__
属性对由该类派生出来的子类,不起作用
子类的__ dict __
不包含父类的 __ dict __
type
不将子类作为父类的继承,isinstance
将子类作为父类的继承
- 子类不重写
__init__()
,则自动调用父类__init__()
方法 - 子类重写,则不调用
- 子类需要调用父类
__init__()
,Father.__init__(self)
或super(Son,self).__init__()
def __int__(self,make,model,year):
super.__int__(make,model,year)
#此处可以定义子类的属性了
def fcn_name(self):#可以定义专属于子类的方法
pass
进阶理解 – 很容易错的一点
class F:
def f1(self):
print('F.f1')
def f2(self):
print('F.f2')
self.f1()
class Bar(F):
def f1(self):
print('bar.f1')
b=Bar()
b.f2()
# F.f2
# bar.f1 # 因为这里的对象是Bar的对象,所以调用的还是Bar里的f1 !!!太容易错了
type / instance
type
不认为子类是父类的一种类型
instance
可以判断继承关系
class A:
pass
class B(A):
pass
type(B()) is A # False
type(B()) == A # False
isinstance(B(),A) # True
9.1 super
super将父类和子类关联起来,从而可以调用父类的__int__()
,从而让子类的实例包含父类所有属性,
super
不仅可以调用父类的初始化方法,还可以调用父类的其他方法
对于多继承的用法http://c.biancheng.net/view/2290.html
还有一种是非绑定的方法
https://www.runoob.com/python/python-func-super.html
重要!!!https://blog.csdn.net/qq_44804542/article/details/116173195
9.1.1 super例子
class A():
def add(self, x, y):
return x + y
class B(A):
def addInB(self, x, y):
return self.add(x, y) # 使用这种方式不能进行父类方法重写
def add(self,x,y):
return super(B, self).add(x,y) # python2
# return super().add(x,y) # python3
b = B()
print(b.addInB(1, 2))
print(b.addInB(1, 2))
9.2 例子
class Father:
def __init__():
pass
# vesion1
class Son(Father):
def __init__():
Father.__init__(self,*agrs,**kwargs)
pass
# version2 python3.x
class Son(Father):
def __init__():
super().__init__(*agrs,**kwargs)
pass
# version3 python2.x
# 这种感觉更舒服
class Son(Father):
def __init__():
super(Son,self)._init__(*agrs,**kwargs)
pass
9.3 重写父类方法
重写父类方法,利用实例化对象调用父类被重写的方法
http://c.biancheng.net/view/2289.html
# 利用Super方法实现
son=Son()
# 当子类重写父类方法时,想要再次调用父类的重名方法时
super(Son,son).FatherCertrainMethod()
# 一般方法
son=Son()
FatherCertainMethod(son)
9.4 多继承与super
https://blog.csdn.net/wanzew/article/details/106993425很重要!!!
先看5.1中的例子,再看下面的例子有助于理解
super与在多继承中的缺点:就是传参的不确定
多继承中 采用的是 广度优先方法(新式类)
# 比5.1稍微复杂一点的例子
class Parent(object):
def __init__(self,name):
print('parent class init')
self.name=name
print('parent class init end')
class Son1(Parent):
def __init__(self,name,age):
print('son1 class init')
self.age=age
Parent.__init__(self,age)
print('son2 class init end')
class Son2(Parent):
def __init__(self,name,gender):
print('son2 class init')
self.gender=gender
Parent.__init__(self,name)
print('son2 class init end')
class GrandSon(Son1,Son2):
def __init__(self,name1,name2,age,gender):
print('grandson class init')
Son1.__init__(self,name1,age)
Son2.__init__(self,name2,gender)
print('grandson class init end')
gs=GrandSon('grandson1','grandson2',12,'male')
print(gs.name) # 会覆盖,结果为gradnson2
print(gs.age)
print(gs.gender)
改进 – super与__mro__
class Parent(object):
def __init__(self,name,*args,**kwargs):
print('parent class init')
self.name=name
print('parent class init end')
class Son1(Parent):
def __init__(self,name,age,*args,**kwargs):
print('son1 class init')
self.age=age
super().__init__(name,*args,**kwargs)
print('son1 class init end')
class Son2(Parent):
def __init__(self,name,gender,*args,**kwargs):
print('son2 class init')
self.gender=gender
super().__init__(name,*args,**kwargs)
print('son2 class init end')
class GrandSon(Son1,Son2):
def __init__(self,name1,name2,age,gender):
print('grandson class init')
super().__init__(name1,age,gender)
print('grandson class init end')
gs=GrandSon('grandson1','grandson2',12,'male')
print(GrandSon.__mro__) # (<class '__main__.GrandSon'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
print(gs.name)
print(gs.age)
print(gs.gender)
9.4.1 MRO
cls.__ mro __
属性查看方法的搜索顺序,看上面的例子
super
的继承关系是有__mro__
决定的
__mro__
返回的是一个tuple,顺序是根据tuple中的顺序进行继承的,后面是前面的父类,且不能跨索引
例子
https://blog.csdn.net/qq_44804542/article/details/116173195
9.5 类中的匿名函数
匿名函数同样可以继承
class C:
func=lambda self,x:x+1
c=C()
print(c.func(1)) # 2
# 例子
class B(C):
pass
c=C()
print(c.func(1))
b=B()
print(b.func(2))
10. 多态
多态满足的条件:
继承:多态发生在子类与父类之间
重写:子类重写了父类的方法
多态:调用对象方法时,要看这个对象是父类创建的对象还是子类创建的对象,而不一定非得调用父类或子类
其实就是看懂继承关系就行了
11. 其他
- issubclass
12. 例子进阶
索引后保持类型不变
class C:
def __init__(self,x):
self.x=x
def __len__(self):
pass
# 第一种写法
def __getitem__(self,index):
cls=type(self)
if isinstance(index,slice): # 表明是切片
return cls(self.x[index]) # ??? 不懂这样写为什么可以
else: # 否则表明是取出某个元素
return self.x[index]
# 第二种写法,个人更习惯这种写法
def __getitem__(self,index):
if isinstance(index,slice):
return C(self.x[index])
函数里边定义类
def choose_cls(name):
if name=='name':
class=C1(object):
pass
return C1
else:
class C2(object):
pass
return c2