一切皆对象
在python里,int
整形是对象,整数2
也是对象,你定义的函数啊,类啊都是对象,你定义的变量也是对象。总之,你在python里能用到的都可以称之为对象。
对象的特征:
- 对象标识(Identity):同一个(父)类实例化出来的对象,往往具有许多相同的特征,但是这些具有许多相同特征的对象肯定都有一个唯一的对象标识,当然,不同父类实例化出来的对象也是用对象标识作为判断对象的条件之一的;
- 值(A value):这意味着对象包含一堆属性。我们可以通过
objectname.attributename
的方式操作属性; - 类型(A type):每个对象都有一个确切地类型。例如,对象“2”的类型是
int
; - 一个或多个“Bases”(One or more bases):不是所有对象都有Bases,但一些特殊的对象会有,比如:类。Bases类似于面向对象语言中的“基类”,“超类”。
在面向对象体系里面,存在两种关系:
父子关系,即继承关系,表现为子类继承于父类
类型实例关系,表现为某个类型的实例化
type和object
要理解type和object的关系,需要弄懂下面两句话:
type is kind of object
在Python的世界中,object是父子关系的顶端,所有的数据类型的父类都是它:
class type(object):
"""
type(object_or_name, bases, dict)
type(object) -> the object's type
type(name, bases, dict) -> a new type
"""
包括type, object规定了一个对象的基本属性:
def __delattr__(self, *args, **kwargs): # real signature unknown
""" Implement delattr(self, name). """
pass
def __dir__(self): # real signature unknown; restored from __doc__
"""
__dir__() -> list
default dir() implementation
"""
return []
def __eq__(self, *args, **kwargs): # real signature unknown
""" Return self==value. """
pass
def __format__(self, *args, **kwargs): # real signature unknown
""" default object formatter """
pass
def __getattribute__(self, *args, **kwargs): # real signature unknown
""" Return getattr(self, name). """
pass
等等,
object is and instance of type
下面是type类的实例化函数:
def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
"""
type(object_or_name, bases, dict)
type(object) -> the object's type
type(name, bases, dict) -> a new type
# (copied from class doc)
"""
pass
直接赋值,浅拷贝,深拷贝
- **直接赋值:**其实就是对象的引用(别名)。
- **浅拷贝(copy):**拷贝父对象,不会拷贝对象的内部的子对象。
- 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。
a = {1: [1,2,3]}
b = a
赋值引用,a 和 b 都指向同一个对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VjPnWNo4-1615707894323)(/home/zhq/Documents/notes/static/1489720931-7116-4AQC6.png)]
import copy
a = {1: [1,2,3]}
b = a.copy()
浅拷贝, a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(是引用)。
import copy
a = {1: [1,2,3]}
b = copy.deepcopy(a)
深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。
可变类型和不可变类型
Python的每个对象都分为可变和不可变,主要的核心类型中,数字、字符串、元组是不可变的,列表、字典、集合是可变的
对不可变类型的变量重新赋值,实际上是重新创建一个不可变类型的对象,并将原来的变量重新指向新创建的对象。
例如:
a = 5
id(a)
94740728717184
a += 1
id(a)
94740728717216
对于不可变类型int,无论创建多少个不可变类型,只要值相同,都指向同个内存地址。
>>> a = 5
>>> id(a)
140001530538416
>>> a += 1
>>> id(a)
140001530538448
>>> b = 6
>>> id(b)
140001530538448
对于可变类型list为例:
>>> l1 = [1,2,3]
>>> id(l1)
140001529294208
>>> l1.append(4)
>>> l1
[1, 2, 3, 4]
>>> id(l1)
140001529294208
可以看出可变类型和不可变类型在底层存储上,可变类型是通过一个固定的地址访问子对象的地址,而不可变类型是直接通过对象地址访问对象
可hash和不可hash
如果一个对象是可哈希的,那么在它的生存期内必须不可变(而且该对象需要一个哈希函数),而且可以和其他对象比较(需要比较方法).比较值相同的对象一定有相同的哈希值,即一个对象必须要包含有以下几个魔术方法:
- eq():用于比较两个对象是否相等
- cmp():用于比较两个对象的大小关系,它与__eq__只要有一个就可以了
- hash():实际上就是哈希函数(散列函数),返回经过运算得到的哈希值
函数传参
函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题。基本的参数传递机制有两种:值传递和引用传递。
值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
def func1(li):
print(id(li),li,type(li))
li[1] = "haha"
print(id(li),li,type(li))
def func2(a):
print(id(a),a,type(a))
a += 1
print(id(a),a,type(a))
if __name__ == "__main__":
li = [1, 2, 3, 4]
func1(li)
a = 10
func2(a)
## 结果:
139834329196744 [1, 2, 3, 4] <class 'list'>
139834329196744 [1, 'haha', 3, 4] <class 'list'>
94879406080000 10 <class 'int'>
94879406080032 11 <class 'int'>
python不允许程序员选择采用传值还是传引用。Python参数传递采用的肯定是“传对象引用”的方式。这种方式相当于传值和传引用的一种综合。如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值--相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象--相当于通过“传值’来传递对象。