python面向对象

python面向对象

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 在类外动态为类增删属性和方法

类外创建类属性后,实例对象对应的类属性也会被拥有
可以为类动态添加 类方法、实例方法、静态方法
注意,对于类的实例对象,只允许添加实例方法,不能添加类方法和静态方法
为单个实例对象添加方法,不会影响该类的其他实例对象,而如果为类动态的添加方法,则所有的实例对象都可以使用

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 __

https://www.bilibili.com/video/BV1AN4y137uT?p=113&spm_id_from=pageDriver&vd_source=7155082256127a432d5ed516a6423e20 看看就可以了

实际上就是通过__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

gettersetter的区别:其实gettersetter这些方法的一个同一叫法(自我理解)
@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):中的attrstr类型,在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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值