python类的成员可以分为三个方面:字段、方法和属性。注意python都是引用,不同变量的数据值假如是相同的,就会去引用同一数据,指向同一块内存(也因此实现了弱类型,因为创建变量只是创建引用,即创建指向某一块内存,修改变量,给变量赋值其他类型,也只是修改变量的引用,指向其他的内存。)
在定义一个类以后,方法、属性和静态字段都是属于类的,在内存中只保存一份,只有普通字段是每个对象独有的,该类的每个对象都会创建并保存自己的一份。
我们可以将存储区分为几种:程序存储区、全局存储区、静态存储区、栈存储区。
-
程序存储区: 我们所有的普通函数、类函数、类等程序所存储的位置。
-
全局存储区:所有全局变量的存储区。
-
静态存储区:类的静态变量存储区。
-
栈存储区:栈存储区就是局部变量存储,所有的局部变量、类对象等都存储在这里。
字段
字段包括:普通字段和静态字段,使用和定义都是不一样的,其最本质的区别就是内存中保存的位置不同。
-
普通字段:每个对象的专属
-
静态字段:属于类,该类的所有对象共享。通过类名.字段名去修改,所有对象的字段值都改变;通过对象名.字段名去修改,只有该对象的字段值被修改。
class Demo:
aa = "我是静态字段" # 静态字段
def __init__(self, name):
self.bb = name # 普通字段
def custom_func(self, name):
self.cc = name
>>> obj1 = Demo("obj1")
>>> print("obj1.aa = ", obj1.aa)
obj1.aa = 我是静态字段 # 调用得到静态字段的初始值
>>> Demo.aa = 111111 # 修改类的静态字段的值,注意调用方式是【类名.字段】
>>> print("obj1.aa = ", obj1.aa)
obj1.aa = 111111
>>> obj2 = Demo("obj2")
>>> print("obj1.aa = ", obj1.aa) # 创建新对象
obj1.aa = 111111
>>> print("obj2.aa = ", obj2.aa)
obj2.aa = 111111 # 所有该类对象的静态字段的值都被更改
>>> obj1.aa = 1 # 修改对象的静态字段的值,只是该对象的静态字段被修改
>>> print("obj1.aa = ", obj1.aa)
obj1.aa = 1
>>> print("obj2.aa = ", obj2.aa)
obj2.aa = 111111
>>> print("Demo.aa = ", Demo.aa)
Demo.aa = 111111
>>> id(obj1.aa) # 修改后obj1.aa变量的引用从111111变为1
140735426205456
>>> id(obj2.aa)
2039924347312
>>> id(Demo.aa)
2039924347312
>>> obj2.aa =1
>>> id(obj2.aa)
140735426205456 # python为变量引用数据,加上其GC原理,值相同的变量指向同一块内存
通常情况下我们都使用普通字段,当一个变量在类的所有对象中共同使用,而且数据共享的时候,我们就可以使用静态字段。
方法
方法包括:普通方法、静态方法和类方法。他们的区别在于调用方式不同。
-
普通方法:由对象调用;包含一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
-
类方法:由类调用; 包含一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
-
静态方法:由类调用;没有默认参数;
>>> class Foo:
FooName = "静态字段"
def custom_func(self, name):
# 普通方法,至少包含一个self参数
self.name = name # 调用后,为该对象创建一个普通字段,并赋值
print("普通方法")
@classmethod
def class_func(cls, name):
# 类方法,至少包含一个cls参数
cls.name = name # 调用后,为该创建一个静态字段,并赋值,该类的所有对象都有值相同的name
print("类方法")
@staticmethod
def static_func():
# 静态方法,没有默认参数
print("静态方法")
>>> f = Foo()
>>> f.custom_func("aaa") # 调用普通方法 对象名.方法名
普通方法
>>> Foo.class_func("bbb") # 调用类方法 类名.方法名
类方法
>>> Foo.static_func() # 调用静态方法 类名.方法名
静态方法
普通方法的实现中可以直接使用对象的普通字段self.name
类方法中可以直接使用静态字段cls.FooName
静态方法中不能直接使用普通字段,静态字段需要通过 类名.静态字段名 进行调用
注意:静态字段在哪儿调用都是类名.静态字段名
属性
Python中的属性其实是普通方法的变种。就是普通方法+装饰器,实现和字段同样的调用效果(即是调用时不加括号)本质:就是使得普通方法的调用换为一种简洁的调用方式,(主要原因是@property装饰器或者是property类可以创建只读属性)
对于属性,有以下两个知识点:基本使用,两种定义方式
- 属性的基本使用
class Foo:
def func(self):
pass
@property # 内置装饰器,用来创建只读属性,使得可以像访问字段一样来获取一个函数的返回值
def prop(self):
print("属性")
f = Foo()
# 调用函数
f.func()
# 调用属性
f.prop
访问属性时可以制造出和访问字段完全相同的假象,它拥有字段的简洁性,又拥有方法的多功能性
属性的定义有两种方式:
-
装饰器:在方法上应用装饰器
-
静态字段:在类中定义值为property对象的静态字段
我们知道Python中的类有经典类和新式类之分,如果类继承自object,那么该类是新式类,新式类的属性比经典类更丰富。但是现在都已经使用python3了,而python3中默认类都继承自object,所以python3中全是新式类。(不知所云)
-
装饰器方式:普通方法加上@property装饰器
class Goods:
@property
def price(self):
print("@property")
@price.setter
def price(self, val):
print("@price.setter: ", val)
@price.deleter
def price(self):
print("@price.deleter")
obj = Goods()
obj.price # 自动执行@property修饰的price方法,并获取方法的返回值
obj.price = 100 # 自动执行@price.setter修饰的price方法,并将100赋值给方法的参数
del obj.price # 自动执行@price.deleter修饰的price方法
# 属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法
# 本质是换了更加简洁的调用方式,制造假象
-
静态字段方式,使用property创建静态字段
property是一个类,在builtins.py文件中,初始化函数:def __init__(self, fget=None, fset=None, fdel=None, doc=None)
,有四个参数
-
第一个参数fget是方法名,调用 对象.属性 时自动触发执行方法
-
第二个参数fset是方法名,调用 对象.属性 = XXX 时自动触发执行方法
-
第三个参数fdel是方法名,调用 del 对象.属性 时自动触发执行方法
-
第四个参数doc是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息
class Foo:
def __init__(self):
self.price = 10
def get_price(self):
return self.price
# set函数必须有两个参数
def set_price(self, value):
self.price = value
def del_price(self):
del self.price
PRICE = property(get_price, set_price, del_price, "description Price")
f = Foo()
print(f.PRICE) # 自动调用get_price
f.PRICE = 20 # 自动调用set_price
del f.PRICE # 自动调用del_price
在python的类中,没有真正的私有化,不管是方法还是属性,为了编程的需要,约定加了下划线 _ 的属性和方法不属于API,不应该在类的外面访问,也不会被from M import * 导入。类中的方法和字段通过下划线来区分其作用域:
-
xx:公有变量,所有对象都可以访问;
-
_xx:单前置下划线,私有化属性和方法,for 包名
import *
禁止导入,类对象和子类可以访问,使用对象._变量名调用; -
__xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问,该类的私有变量;
-
__xx__:双前后下划线,用于定义类的特殊属性/模法方法,例如:__init__,__str__等,无法在类的外部直接被import,但是部分可以通过类名.__xx__实现调用;
-
xx_:单后置下划线,用于避免与python关键字的冲突。
Python 类中,凡是以双下划线 "__" 开头和结尾命名的成员(属性和方法),都被称为类的特殊成员(特殊属性和特殊方法)。Python 类中的特殊成员,其特殊性类似 C++ 类的 private 私有成员,即不能在类的外部直接调用,但允许借助类中的普通方法调用甚至修改它们。如果需要,还可以对类的特殊方法进行重写,从而实现一些特殊的功能。
重载运算符 | 含义 |
---|---|
__new__ | 创建类,在 __init__ 之前创建对象 |
__init__ | 类的构造函数,其功能是创建类对象时做初始化工作。 |
__del__ | 析构函数,其功能是销毁对象时进行回收资源的操作 |
__dict__ | 获取l类或者对象的所有成员,调用方法:类名/对象名.__dict__ |
__add__ | 加法运算符 +,当类对象 X 做例如 X+Y 或者 X+=Y 等操作,内部会调用此方法。但如果类中对 __iadd__ 方法进行了重载,则类对象 X 在做 X+=Y 类似操作时,会优先选择调用 __iadd__ 方法。 |
__radd__ | 当类对象 X 做类似 Y+X 的运算时,会调用此方法。 |
__iadd__ | 重载 += 运算符,也就是说,当类对象 X 做类似 X+=Y 的操作时,会调用此方法。 |
__or__ | “或”运算符 |,如果没有重载 __ior__,则在类似 X|Y、X|=Y 这样的语句中,“或”符号生效 |
__repr__,__str__ | 在打印print/打印或者str转换时,输出该方法的返回值。 |
__call__ | 函数调用,使用对象X(*args, **kwargs) ,就调用__call__() |
__getattr__ | 点号运算,用来获取类属性 |
__setattr__ | 属性赋值语句,类似于 X.any=value |
__delattr__ | 删除属性,类似于 del X.any |
__getattribute__ | 获取属性,类似于 X.any |
__getitem__ | 索引运算,类似于 X[key],X[i:j],在dataset类中该方法负责实现读取样本逻辑 |
__setitem__ | 索引赋值语句,类似于 X[key], X[i:j]=sequence |
__delitem__ | 索引和分片删除 |
__get__, __set__, __delete__ | 描述符属性,类似于 X.attr,X.attr=value,del X.attr |
__len__ | 计算长度,类似于 len(X) |
__lt__,__gt__,__le__,__ge__,__eq__,__ne__ | 比较,分别对应于 <、>、<=、>=、=、!= 运算符。 |
__iter__,__next__ | 迭代环境下,生成迭代器与取下一条,类似于 I=iter(X) 和 next() |
__contains__ | 成员关系测试,类似于 item in X |
__index__ | 整数值,类似于 hex(X),bin(X),oct(X) |
__enter__,__exit__ | 在对类对象执行类似 with obj as var 的操作之前,会先调用 __enter__ 方法,其结果会传给 var;在最终结束该操作之前,会调用 __exit__ 方法(常用于做一些清理、扫尾的工作) |
__module__,__class__ | 表示当前操作的对象在那个模块(即.py文件)/属于哪个类 |
注意__init__与__call__的区别:两个都是实现类时的关键方法,__init__的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print '__call__'
obj = Foo() # 执行 __init__
obj() # 执行 __call__
额外讲点:
super() 函数是用于调用父类(超类)的一个方法。使用super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的序表。
class A:
def add(self, x):
y = x+1
print(y)
class B(A):
def add(self, x):
super().add(x)
b = B()
b.add(2) # 3
Python3.x 和 Python2.x 的一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx
super(FooChild,self) 首先找到 FooChild 的父类(就是类 FooParent),然后把类 FooChild 的对象转换为类 FooParent 的对象
关于类方法中的cls
在python的类方法中,默认使用的第一个参数是cls,而在实例方法中,一般使用self作为第一个参数。
(1)比较一般类方法中的self和cls的区别:一般来说,使用某个类的方法,需要先将类实例化,赋予一个对象才可以调用类中的方法,但是如果使用了@staticmethod 或@classmethod,就可以不用实例化,直接类名.方法名()来调用。
(2)在classmethod中可以调用类中定义的其他方法、类的属性,但staticmethod只能通过类名.属性调用类的属性,但无法通过在该函数内部调用类名.常规方法。
(3)关于cls(),其实这就是类本身,比如这里的cls()=A,如果cls()里面有参数,那么这个参数就是构造函数init(self,parameter1,parameter2)中的参数1,2,同时还是表示类本身。