类
python的类变量和C++的静态变量不同,并不是由类的所有对象共享。
类本身拥有自己的类变量(保存在内存),当一个 TestClass
类的对象被构造时,会将当前类变量拷贝一份给这个对象,当前类变量的值是多少,这个对象拷贝得到的类变量的值就是多少;而且,通过对象来修改类变量,并不会影响其他对象的类变量的值,因为大家都有各自的副本,更不会影响类本身所拥有的那个类变量的值;只有类自己才能改变类本身拥有的类变量的值。
对于类数据属性和实例数据属性,可以总结为:
- 类数据属性属于类本身,可以通过类名进行访问/修改
- 类数据属性也可以被类的所有实例访问/修改
- 在类定义之后,可以通过类名动态添加类数据属性,新增的类属性也被类和所有实例共有
- 实例数据属性只能通过实例访问
- 在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例
class TestClass(object):
# 类变量
val1 = 100
def __init__(self):
# 成员变量
self.val2 = 200
def fcn(self, val=400):
val3 = 300
self.val4 = val
self.val5 = 500
inst = TestClass()
print(TestClass.val1) # 100
print(inst.val1) # 100
print(inst.val2) # 200
print(inst.val3) # 'TestClass' object has no attribute 'val3'
print(inst.val4) # 'TestClass' object has no attribute 'val4'
print(inst.val5) # 'TestClass' object has no attribute 'val5'
inst1 = TestClass()
inst2 = TestClass()
print(inst1.val1) # 100
inst1.val1 = 1000
print(inst1.val1) # 1000
print(TestClass.val1) # 100
print(inst2.val1) # 100
TestClass.val1 = 2000
print(TestClass.val1) # 2000
print(inst1.val1) # 1000 被重新赋值后,就跟类变量没关系了
print(inst2.val1) # 2000 没有重新赋值,跟类变量保存一致
inst3 = TestClass()
print(inst3.val1) # 2000
不可变对象与可变对象
python内置的一些类型中
可变对象:
list
dict
set
不可变对象:
tuple
string
int
float
bool
Python中万物皆对象,每个对象包含3个属性,id,type,value
id就是对象地址,可以通过内置函数
id()
查看对象引用的地址。type就是对象类型,可以通过内置函数
type()
查看对象的类型。value就是对象的值
is 和==
is比较的是id是不是一样
==比较的是值是不是一样。
a = 1 b = a c = 1 d = 1.0 print(id(a)) # 140729241962160 print(id(b)) # 140729241962160 print(id(c)) # 140729241962160 print(id(d)) # 2070028299024 print(a is d) # False print(a == d) # True
判断
a is d
的时候,实际上比较的是id(d)==id(d)
,结果为False。判断
a==d
的时候,实际上比较的是id(a)
这个地址指向的值是不是和id(d)
这个地址指向值一样。结果为True。python为了实现对内存的有效利用,对小整数[-5,256]内的整数会进行缓存,不在该范围内的则不会缓存。
注意在python交互式界面有内存池缓存机制,只适用于-5~256,在python脚本编程中则没有这个限制:
a = 255 b = 255 print(a is b) # True c = 257 d = 257 print(c is d) # True
Python console:
a = 255 b = 255 id(a) Out[10]: 2922684620976 id(b) Out[11]: 2922684620976 a is b Out[4]: True
c = 257 d = 257 id(c) Out[8]: 2920614082448 id(d) Out[9]: 2920614081904 c is d Out[7]: False
类属性
class Person(object):
tall = 180
hobbies = []
def __init__(self, name, age, weight):
self.name = name
self.age = age
self.weight = weight
def inform(self):
print('%s is %s weights %s' % (self.name, self.age, self.weight))
特殊的类属性:对于所有的类,都有一组特殊的属性
__name__
:类的名字(字符串)
__doc__
:类的文档字符串
__bases__
:类的所有父类组成的元组
__dict__
:类的属性组成的字典
__module__
:类所属的模块
__class__
:类对象的类型
print(Person.__name__) # Person
print(Person.__doc__) # None
print(Person.__bases__) # (<class 'object'>,)
print(Person.__dir__) # <method '__dir__' of 'object' objects>
print(Person.__module__) # __main__
print(Person.__class__) # <class 'type'>
__dict__
与dir()
区别
__dict__
属性:
- 类的
__dict__
存储所有实例共享的变量和函数(类属性,方法等),类的__dict__
并不包含其父类的属性。- 实例的
__dict__
属性仅仅是那个实例的实例属性的集合,并不包含该实例的所有有效属性,正是因为实例的__dict__
属性,每个实例的实例属性才会互不影响。- python一切皆对象,并不是所有对象都拥有
__dict__
属性。许多内建类型就没有__dict__
属性,如list
,此时就需要用dir()
来列出对象的所有属性。dir()
函数:
dir()
是Python提供的一个API函数,dir()
函数会自动寻找一个对象的所有属性(包括从父类中继承的属性)。
所以如果想获取一个对象所有有效属性,应使用dir()
。
# 类数据属性属于类本身,可以通过类名进行访问/修改,此处添加"football"、"woman"两个
Person.hobbies.extend(["football", "woman"])
print(Person.hobbies) # ['football', 'woman']
# 在类定义之后,可以通过类名动态添加类数据属性,新增的类属性也被类和所有实例共有
Person.hobbies2 = ["reading", "jogging", "swimming"]
print(Person.hobbies2) # ['reading', 'jogging', 'swimming']
# 实例数据属性只能通过实例访问
Bruce = Person("Bruce", 25, 60)
print(f"{Bruce.name} is {Bruce.age} years old") # Bruce is 25 years old
# 在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例
Bruce.gender = "male"
print("{Bruce.name} is {Bruce.gender}") # Bruce is male
# class instance can access class attribute
Bruce.hobbies.append("C#")
print(Bruce.hobbies) # ['football', 'woman', 'C#']
print(Bruce.hobbies2) # ['reading', 'jogging', 'swimming']
类数据属性属于类本身,被所有该类的实例共享并且,通过实例可以去访问/修改类属性。但是,在通过实例中访问类属性的时候一定要谨慎,因为可能出现**属性"隐藏"**的情况
对于不可变类型的类属性,隐藏属性可以总结为:
- 对于不可变类型的类属性
person.tall
,可以通过实例Bruce
进行访问,并且"person.tall is Bruce.tall
" - 当通过实例赋值/修改tall属性的时候,将为实例Bruce新建一个tall实例属性,这时,“
person.tall is not Bruce.tall
” - 当通过"
del Bruce.tall
"语句删除实例的tall属性后,再次成为"person.tall is Bruce.tall
"
# 对于不可变类型的类属性person.tall,可以通过实例Bruce进行访问
print(Person.tall is Bruce.tall) # True
# 重新赋值或者修改
Bruce.tall = 185
print(Bruce.tall) # 185
print(Person.tall is Bruce.tall) # False
# 再次删除实例的赋值
del Bruce.tall
print(Bruce.tall) # 180
print(Person.tall is Bruce.tall) # True
对于可变类型的类属性,隐藏属性可以总结为:
- 同样对于可变类型的类属性
person.hobbies
,可以通过实例Bruce
进行访问,并且"person.hobbies is Bruce hobbies
" - 当通过实例赋值
hobbies
属性的时候,都将为实例Bruce
新建一个hobbies
实例属性,这时,“person.hobbies is not Bruce hobbies
” - 当通过"
del Bruce. hobbies
"语句删除实例的hobbies
属性后,再次成为"person. hobbies is Bruce hobbies
" - 当通过实例修改
hobbies
属性的时候,将修改Bruce. hobbies
指向的内存地址(即person.hobbies
),此时,“person.hobbies is Bruce. hobbies
”
# 对于可变类型的类属性person.hobbies,可以通过实例Bruce进行访问
print(Person.hobbies is Bruce.hobbies) # True
Bruce.hobbies.append("CSS")
print(Person.hobbies is Bruce.hobbies) # True
print(Person.hobbies) # ['football', 'woman', 'C#', 'CSS']
Will = Person("Will", 27, 60)
print(f"{Will.name} is {Will.age} years old") # Will is 27 years old
# Will shares the same class attribute with wilber
# Will don't have the "gender" attribute that belongs to wilber
print(Will.hobbies) # ['football', 'woman', 'C#', 'CSS']
print(Will.gender) # AttributeError: 'Person' object has no attribute 'gender'
注意,虽然通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦
__del__
class Test:
# 当内存不需要的时候调用这个删除方法,python解释器自动调用
def __del__(self):
print("Over")
t1 = Test()
t2 = t1
del t1
del t2
print("==========")
# Over
# ==========
class Test:
# 当内存不需要的时候调用这个删除方法,python解释器自动调用
def __del__(self):
print("Over")
t1 = Test()
t2 = t1
del t1
print("==========")
# ==========
# Over
当删除了t1
,内存空间还没有结束,还不会调用__del__
方法,当调用完最后一条语句时,内存空间被释放,调用__del__
方法
类class的访问控制
"_
“和”__
"的使用 更多的是一种规范/约定,并没有真正达到限制的目的
单下划线"_
"
以单下划线开头的表示的是 protected 类型的变量,即只能允许其本身与子类进行访问;同时表示弱内部变量标示,如"from moduleName import *
" 将不会引入以单下划线 "_
"开头的函数。
双下划线"__
"
双下划线的表示的是private类型的变量。只能是允许这个类本身进行访问了,连子类也不可以,这类属性在运行时属性名会加上单下划线和类名。
-
对于Python中的类属性,可以通过双下划线**"
__
“来实现一定程度的私有化**,因为双下划线开头的属性在运行时会被"混淆”(mangling) -
双下划线的另一个重要的目地是,避免子类对父类同名属性的冲突
-
单下划线,可被重写,调用子类方法
-
双下划线,不能被重写,调用的还是父类方法
class A(object):
def __init__(self):
self.__private()
self.public()
def __private(self):
print('A.__private()')
def public(self):
print('A.public()')
class B(A):
def __private(self):
print('B.__private()')
def public(self):
print('B.public()')
b = B()
# A.__private()
# B.public()
类class的继承
在Python中,同时支持单继承与多继承
实现继承之后,子类将继承父类的属性(除了文档字符串),也可以使用内建函数insubclass()
来判断一个类是不是另一个类的子孙类
类型比较
type()
不会认为子类是一种父类类型isinstance()
会认为子类是一种父类类型。class Foo(object): pass class Bar(Foo): pass print(type(Foo()) == Foo) # True print(type(Bar()) == Foo) # False print(isinstance(Foo(), Foo)) # True print(isinstance(Bar(), Foo)) # True
**Example 1 **
class A:
def __init__(self):
self.__j = 1
self.number = 5
class B(A):
def __init__(self):
self.__j = 2
self.number = 7
def show(self):
print(self.__j, self.number)
b = B()
b.show() # 2 7
**Example 2 **
class A(object):
def __method(self):
print("I'm a method in A")
def method(self):
self.__method()
class B(A):
def __method(self):
print("I'm a method in B")
B().method() # I'm a method in A
A().method() # I'm a method in A
Example 3
class Parent(object):
"""
parent class
"""
pass
class Child(Parent):
pass
# doc属性不会被继承
print(Parent.__doc__) # parent class
print(Child.__doc__) # None
super
的使用
super
主要显式调用父类,在子类中,一般会定义与父类相同的属性(数据属性,方法),从而来实现子类特有的行为。也就是说,子类会继承父类的所有的属性和方法,子类也可以覆盖父类同名的属性和方法
调用父类的方法:
- 将“
self
”显式的传递进去:
class Parent(object):
Value = "Hi, Parent value"
def fun(self):
print("This is from Parent")
class Child(Parent):
Value = "Hi, Child value"
def fun(self):
print("This is from Child")
# 调用父类Parent的fun函数方法
Parent.fun(self)
c = Child()
c.fun()
# This is from Child
# This is from Parent
- 使用Python中的
super
关键字
class Parent(object):
Value = "Hi, Parent value"
def fun(self):
print("This is from Parent")
class Child(Parent):
Value = "Hi, Child value"
def fun(self):
print("This is from Child")
# 相当于用super的方法与上一调用父类的语句置换
super(Child, self).fun()
c = Child()
c.fun()
# This is from Child
# This is from Parent
super()
对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。 实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
虽然名义上来说super是用来调用父类中的方法,但是super实际上是在MRO表中找到下一个匹配的类。super原型如下:
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls) + 1]
两个参数 cls
和 inst
分别做了两件事:
inst
负责生成 MRO 的 list- 通过
cls
定位当前 MRO 中的 index, 并返回mro[index + 1]
但是根据我们上面说的super
本质知道 super
和父类其实没有实质关联,我们就不难理解为什么 enter B 下一句是 enter C 而不是 enter A了(如果认为 super
代表“调用父类的方法”,会想当然的认为下一句应该是enter A)。
可以用 self.__class__.__mro__
方法来查询当前MRO
一个多继承中的MRO是固定的(只要每个类之间都有继承关系)
Example:
class A(object):
def __init__(self):
print("Enter A")
class B(A):
def __init__(self):
print('Enter B')
super(B, self).__init__()
print('Leave B')
class C(A):
def __init__(self):
print('Enter C')
super(C, self).__init__()
print('Leave C')
class D(B, C):
def __init__(self):
print('Enter D')
super(D, self).__init__()
print("Leave D")
print(self.__class__.__mro__)
d = D()
# Enter D
# Enter B
# Enter C
# Enter A
# Leave C
# Leave B
# Leave D
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
上方例子的中流程:
-
class D
中,输出“Enter D" , 然后就会调用super
方法,super()
方法,第一个参数是D
,在MRO列表中的下标(index)为0,那么调用的下一个类就是下标为(index+1)的类,即class B
, -
进入
class B
,输出"Enter B" ,再次调用super()
,此时的index为1,那么调用的下一个类的index为2,即class C
,输出“Enter C” . -
在
class C
中,调用super()
,进入class A
,输出“Enter A”, -
回到
class C
,输出 “Leave C” , 再回到class B
,输出“Leave B”, 然后回到class D
,输出“Leave D”。结束
当你使用 super()
函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super()
并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。
__new__
__new__
方法:类级别的方法
- 是在类准备将自身实例化时调用,并且至少需要传递一个参数**
cls
**,此参数在实例化时由python解释器自动提供 - 始终是类的类方法,即使没有被加上类方法装饰器;
- 必须要有返回值,返回实例化出来的实例;在自己实现
__new__()
时需要注意:可以return
父类(通过super(当前类名,cls)).__new__
出来的实例,或者直接是object的__new__
出来的实例
class A(object):
pass
# 默认调用父类object的__new__()方法来构造该类的实例
a = A()
print(a) # <__main__.A object at 0x000001BE6F8CD520>
class A(object):
def __new__(cls):
"重写__new__方法"
return "abc"
a = A()
print(a) # 'abc'
print(type(a)) # <class 'str'>
通过__new__()
方法实现单例
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
a = Singleton()
b = Singleton()
c = Singleton()
print(a) # <__main__.Singleton object at 0x000001F0B57D86D0>
print(b) # <__main__.Singleton object at 0x000001F0B57D86D0>
print(c) # <__main__.Singleton object at 0x000001F0B57D86D0>
__init__
__init__
方法:实例级别的方法
-
有一个参数
self
, 该self
参数就是__new__()
返回的实例; -
__init__()
在__new()__
的基础上完成初始化动作,不需要返回值;
若__new__()
没有正确返回当前类cls
的实例,那__init__()
将不会被调用
创建的每个实例都有自己的属性,方便类中的实例方法调用;
class A:
def __new__(cls, *args, **kwargs):
print("A' __new__")
# return super(A,cls).__new__(cls)
return object.__new__(cls)
def __init__(self):
print("A' __init__")
class B(A):
def __new__(cls, *args, **kwargs):
print("B' __new__")
def __init__(self):
print("B' __init__")
a = A()
# B' __new__ 没有创建真正的类B,所以类B的构造函数没有调用
b = B()
# A' __new__
# A' __init__
# B' __new__
当在Python中出现继承的情况时,一定要注意初始化函数__init__
的行为:
-
如果子类没有定义自己的初始化函数,父类的初始化函数会被默认调用;但是如果要实例化子类的对象,则只能传入父类的初始化函数对应的参数,否则会出错
-
如果子类定义了自己的初始化函数,而在子类中没有显式调用父类的初始化函数,则父类的属性不会被初始化
-
如果子类定义了自己的初始化函数,在子类中显式调用父类,子类和父类的属性都会被初始化
元类
类是能够创建出类实例的对象。类本身也是实例,它们是元类的实例。
Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type
。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。
其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:
class A:
pass
print(type(1)) # <class 'int'>
print(type(type(1))) # <class 'type'>
print(type(int)) # <class 'type'>
print(type(A)) # <class 'type'>
元类是类的类,常可以用在类工厂中;
- Python中所有的类都是对象,可以通过
type()
来创建元类 - 在定义类时,可用过
metaclass
参数来指定此类的元类 - Python类语句执行时,会先查找其类本身的
metaclass
属性,如果没找到,会继续在父类中找,还没找到,则到模块中找,最后再用内置的type来创建此类对象 - 使用类、函数都可以当作元类,通常在**
__new__
**方法中通过type来自定义自己的元类 - 从设计的复杂度来讲,尽量少用元类,多用普通类或函数