is
与==
==
运算符是比较两个对象的内容是否相等,默认状况是调用对象的__eq__
方法进行比较;而is
是比较两个对象是否同样,它比较的两个对象的id
,即它们的内存地址是否相同。shell
>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True
# a和b是不是同一个对象
>>> a is b
False
# a和b的地址实际上是不同的
>>> id(a)
4498717128
>>> id(b)
4446861832
复制代码
在比较时但也有例外。Python
对一些经常使用的值进行缓存优化,例如在区间[-5,256]的整数,它们在建立时,不管建立多少个对象,它们的id
是同样的,即它们在底层中只保存一分内存。缓存
>>> a = -5
>>> b = -5
>>> a == b
True
>>> a is b
True
>>> a = -6
>>> b = -6
>>> a == b
True
>>> a is b
False
复制代码
对一些短的字符串也是如此,所以并非全部字符串都会建立新的实例bash
>>> a='123'
>>> b='123'
>>> a==b
True
>>> a is b
True
>>> id(a)
4446903800
>>> id(b)
4446903800
>>> x = 'long char'
>>> y = 'long char'
>>> x == y
True
>>> x is y
False
复制代码
__repr__
与__str__
每一个类都应该提供一个__repr__
方法。__repr__
方法和__str__
方法有什么不同呢?工具
简单的说,__repr__
能够反映一个对象的类型以及包含的内容,而__str__
主要是用于打印一个对象的内容。例如看一下Python
中的日期类datetime
post
import datetime
>>> today = datetime.date.today()
>>> today
datetime.date(2019, 7, 7)
>>> print(today)
2019-07-07
>>> str(today)
'2019-07-07'
>>> repr(today)
'datetime.date(2019, 7, 7)'
复制代码
__str__
在字符串链接,打印等操做会用到,而__repr__
主要是面向开发者,它能反馈的信息比较多,例如在交互环境下输入today
这个变量会打印出datetime.date(2019, 7, 7)
,不只能够看出today
表明的是今天的日期信息,还能够看出它的类型信息。更重要的是你能够直接复制这段打印出来的信息,直接构造一个“相同”的对象出来。 例如学习
>>> now = datetime.date(2019, 7, 7)
>>> now
datetime.date(2019, 7, 7)
复制代码
对象的复制或说对象拷贝能够分为浅拷贝和深拷贝。优化
咱们经过代码来讲明,就很好理解ui
若是要拷贝的对象是基本数据类型,那么深拷贝和浅拷贝的区别不是很大。编码
>>> a = [1,2,3]
>>> b = list(a)
>>> a[1]=200
>>> a
[1, 200, 3]
>>> b
[1, 2, 3]
复制代码
修改a
中的元素并不会影响到b
spa
但若是要拷贝的对象包含了另外一个对象,那么就要考虑深拷贝和浅拷贝的问题了。
>>> a = [[1,2,3],[4,5,6],[7,8,9]]
>>> b = list(a)
>>> a == b
True
>>> a is b
False
复制代码
这里有一个列表a
,里面有三个子列表,即列表里包含的是对象。
咱们使用list
工厂方法建立了一个a
的拷贝b
,这个b
就是a
的浅拷贝,为何呢?
>>> a[1][2]='x'
>>> a
[[1, 2, 3], [4, 5, 'x'], [7, 8, 9]]
>>> b
[[1, 2, 3], [4, 5, 'x'], [7, 8, 9]]
复制代码
把a[1][2]
的元素修改为了x
,这时候b
列表中也响应了相同的修改。因此这是浅拷贝,由于没有把子对象进行拷贝,只是拷贝了指向子对象的引用。
知道浅拷贝,那么深拷贝就很好理解了。执行拷贝以后,拷贝对象和原对象是彻底独立的,修改任何一个对象都不会影响到另外一个对象
这时候就须要copy
模块了,该模块有两个重要的方法deepcopy
和copy
。不错,就是分别表明深拷贝和浅拷贝。
>>> import copy
>>> a = [[1,2,3],[4,5,6],[7,8,9]]
>>> b = copy.deepcopy(a)
>>> a
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> b
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> a[1][2]='change'
>>> a
[[1, 2, 3], [4, 5, 'change'], [7, 8, 9]]
>>> b
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
复制代码
执行深拷贝以后,对a
的修改并不会影响到b
。
抽象基类的使用
为了说明为何要使用ABC
,咱们先看下不使用ABC
的状况
# 定义了基类Base
>>> class Base:
def foo(self):
raise NotImplemented
def bar(self):
raise NotImplemented
# 定义实现类
>>> class Concrete(Base):
def foo(self):
print('called foo')
# 实现类并无实现bar方法
>>> c = Concrete()
>>> c.foo()
called foo
>>> c.bar()
Traceback (most recent call last):
File "<pyshell#391>", line 1, in <module>
c.bar()
File "<pyshell#384>", line 5, in bar
raise NotImplemented
TypeError: exceptions must derive from BaseException
复制代码
能够看到没有实现基类bar
方法的Concrete
类,依然可使用,并无在第一时间抛出错误。
要解决这个问题,就要用到咱们abc
模块了。
from abc import ABC
# 定义基类,继承于ABC
>>> class Base(ABC):
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
>>> class Concrete(Base):
def foo(self):
print("called foo")
# 实现类并无实现bar方法
>>> c = Concrete()
Traceback (most recent call last):
File "<pyshell#357>", line 1, in <module>
c = Concrete()
TypeError: Can't instantiate abstract class Concrete with abstract methods bar
复制代码
能够看到,在使用Concrete
构造方法的时候,就当即抛出TypeError
了。
namedtuple
的好处关于namedtuple
的用法在前面的文章《如何在Python中表示一个对象》 也有提到。 简单的说namedtuple
是一个能够命名的tuple
,他是对tuple
的扩展,它有跟tuple
同样不可变的属性。
对于一些数据类的定义,namedtuple
使用起来很是方便
>>> from collections import namedtuple
>>> Point = namedtuple('Point','x y z')
>>> p = Point(1,3,5)
>>> p
Point(x=1, y=3, z=5)
>>> p.x
1
>>> p.y = 3.5
AttributeError: can't set attribute
# 能够看出经过namedtuple定义对象,就是一个class类型的
>>> type(p)
<class '__main__.Point'>
复制代码
还可使用它内部的一些工具方法,在实际的编码当中也是很是实用的。
# 转化为dict
>>> p._asdict()
OrderedDict([('x', 1), ('y', 3), ('z', 5)])
# 更新或替换某个属性值
>>> p._replace(x=111)
Point(x=111, y=3, z=5)
# 使用_make建立新对象
>>> Point._make([333,666,999])
Point(x=333, y=666, z=999)
复制代码
Python
中对象的属性类型有实例变量和类变量。
类变量是属于类的,它存储在“类的内存空间”里,并可以被它的各个实例对象共享。而实例变量是属于某个特定实例的,它不在“类的内存空间”中,它是独立于各个实例存在的。
>>> class Cat:
num_legs = 4
>>> class Cat:
num_legs = 4
def __init__(self,name):
self.name = name
>>> tom = Cat('tom')
>>> jack = Cat('jack')
>>> tom.name,jack.name
('tom', 'jack')
>>> tom.num_legs,jack.num_legs
(4, 4)
>>> Cat.num_legs
4
复制代码
这里定义了一个猫类,它有一个实例变量name
,还有一个类变量num_legs
,这个是各个实例共享的。
若是对类变量进行,那么其它实例也会同步修改。而对某个实例对修改,并不会影响都类变量。
>>> Cat.num_legs = 6
>>> tom.num_legs,jack.num_legs
(6, 6)
>>> tom.num_legs = 2
>>> tom.num_legs,jack.num_legs
(2, 6)
>>> Cat.num_legs
6
复制代码
tom.num_legs = 2
这个语句都做用其实对tom
这个实例增长了一个属性,只不过这个属性名称跟类属性的名称是一致的。
为了更好区分,咱们仍是来看代码
>>> class MyClass:
def method(self):
print(f"instance method at {self}" )
@classmethod
def classmethod(cls):
print(f'classmethod at {cls}')
@staticmethod
def staticmethod():
print('staticmethod')
>>> mc = MyClass()
>>> mc.method
<bound method MyClass.method of <__main__.MyClass object at 0x10c280b00>>
>>> mc.classmethod
<bound method MyClass.classmethod of <class '__main__.MyClass'>>
>>> mc.staticmethod
<function MyClass.staticmethod at 0x1090d4378>
复制代码
能够看到在MyClass
中分别定义实例方法(method
)、类方法(classmethod
)和静态方法(staticmethod
)
在Python
中一切都是对象,因此我打印来一下各个方法的__repr__
输出。
对于实例方法method
是绑定在MyClass
的具体实现对象中的,而类方法classmethod
是绑定在MyClass
中的,而静态方法staticmethod
既不绑定在实例里,也不绑定在类中,它就是一个function
对象
实例方法的调用,须要传递一个实例对象到实例方法中,如下两种方法的调用是等价的。
>>> mc.method()
instance method at <__main__.MyClass object at 0x10910ada0>
>>> MyClass.method(mc)
instance method at <__main__.MyClass object at 0x10910ada0>
复制代码
类方法的调用和静态方法的调用都是使用ClassName.methodName()
的方式。
>>> MyClass.classmethod()
classmethod at <class '__main__.MyClass'>
>>> MyClass.staticmethod()
staticmethod
复制代码
类方法和静态方法有什么区别呢?
bound method
,一个是function
。MyClass
,而静态方法不能。function
对象同样,只不过它是属于类命名空间的。本文主要对Python
中一些常见的面向对象的相关的一些特性进行了说明。包括对象的比较、输出、拷贝等操做,以及推荐使用namedtuple
定义数据类。最后对类变量和实例变量以及类方法、实例方法和静态方法的不一样做了分析。