多态
- 当子类和父类都存在相同的
run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running very fast...')
-
多态的好处
# 定义一个run_twice函数 def run_twice(animal): animal.run() animal.run() # 当我们传入Animal的实例时,run_twice()就打印出: >>> run_twice(Animal()) Animal is running... Animal is running... # 当我们传入Dog的实例时,run_twice()就打印出: >>> run_twice(Dog()) Dog is running... Dog is running... # 当我们传入Cat的实例时,run_twice()就打印出:注意直接调用的Cat的run的方法 >>> run_twice(Cat()) Cat is running very fast... Cat is running very fast... -
这就是多态的意思:
对于一个变量,我们只需要知道它是
Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、还是Cat对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:对扩展开放:允许新增
Animal子类;对修改封闭:不需要修改依赖
Animal类型的run_twice()等函数。
静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。
在程序设计中,**鸭子类型(duck typing)**是动态类型的一种风格。
在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。
James Whitcomb Riley 提出的鸭子测试,“鸭子测试”可以这样表述:
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。
从静态类型语言转向动态类型语言的用户通常试图添加一些静态的(在运行之前的)类型检查,从而影响了鸭子类型的益处和可伸缩性,并约束了语言的动态特性(Python 文档中有一句:鸭子类型应避免使用 type() 或 isinstance() 等测试类型是否合法)。
class Duck:
def quack(self):
print("呱呱呱!")
def feathers(self):
print("这个鸭子拥有灰白灰白的羽毛。")
class Person:
def quack(self):
print("你才是鸭子你们全家人是鸭子!")
def feathers(self):
print("这个人穿着一件鸭绒大衣。")
def in_the_forest(duck):
duck.quack()
duck.feathers()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
从哪里可以看出 Python 是鸭子类型的风格呢?
in_the_forest() 函数对参数 duck 只有一个要求:就是可以实现 quack() 和 feathers() 方法。然而 Duck 类和 Person 类都实现了 quack() 和 feathers() 方法,因此它们的实例对象 donald 和 john 都可以用作 in_the_forest() 的参数。这就是鸭子类型。
我们可以看出,鸭子类型给予 Python 这样的动态语言以多态。 但是这种多态的实现完全由程序员来约束强制实现(文档、清晰的代码和测试),并没有语言上的约束(如 C++ 继承和虚函数)。 因此这种方法即灵活,又提高了对程序员的要求。
类,类对象,实例对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ALkaHdSh-1651018619095)(…/resources/image51-9.png)]
- 实例对象属性赋值不会改变类对象的值,类一旦定义就会产生类对象
class A:
num = 10
######
a1 = A()
a2 = A()
a3 = A()
a3.num += 10 --- 实例对象的num属性变化,不影响其他实例对象,也不会影响类对象的,因为他们在不同的内存空间
a3.num
20
a1.num
10
a2.num
10
A.num
10
A.num += 100 --- 类对象的num属性变化,导致其他未改变的实例对象相应变化但不会影响已经变化的
a1.num
110
a2.num
110
a3.num
20
- 千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
- 如果属性和方法重名,则会覆盖方法
>> 不要试图在一个类里面定义所有能想到的特性和方法,应该利用继承和组合机制类进行扩展.
>> 用不同的词性命名,如属性用名词,方法用动词**
class C:
def x(self):
print("X-Ray!")
######
c1 = C()
c1.x()
X-Ray!
c1.x = 1 --- 对象c1产生一个新的x属性(python中变量不用声明)
c1.x
1
c1.x() --- 由于c1的x属性和类的x方法同名,调用的时候直接找c1对象去了,就会报错
Traceback (most recent call last):
File "<pyshell#75>", line 1, in <module>
c1.x()
TypeError: 'int' object is not callable
- 绑定:python 严格要求方法需要有实例才能被调用,这种限制其实就是python所谓的绑定概念
使用实例属性(self.x, dd.dict),不要用类属性, 类被删除,实例依然可以调用属性(因为类的属性和方法是静态的存放在内存中)
class A:
def __init__(self, x):
self.x = x
def getX(self):
return self.x
######
class B(A):
pass
######
A(0).x
0
b2 = B(1)
b2.x
1
b2.getX()
1
A.dict (查看该类的方法,字典格式,键值是属性或者方法,单引号加持)
mappingproxy({'module': 'main', 'init': <function A.init at 0x00000202A1FDF160>, 'getX': <function A.getX at 0x00000202A1FDF1F0>, 'dict': <attribute 'dict' of 'A' objects>, 'weakref': <attribute 'weakref' of 'A' objects>, 'doc': None})
b2.dict (b2实例化的属性/方法只有x,因为b2实例化的时候x和b2是绑定的)
{'x': 1}
del A (此时删除类A)
a3 = A()
Traceback (most recent call last):
File "<pyshell#63>", line 1, in <module>
a3 = A()
NameError: name 'A' is not defined
b2.getX() --(b2依然可以正常使用属性/方法)
1
b2.x
1
类和对象的一些相关的BIF
*issubclass(class, classinfo)— 如果第一个参数(class)是第二个参数(classinfo)的一个子类,则返回 True,否则返回 False。
另外以下这些常识你应该知道:
- 一个类被认为是其自身的子类
- classinfo 可以是类对象组成的元祖,只要 class 与其中任何一个候选类的子类,则返回 True
- 在其他情况下,会抛出一个 TypeError 异常
class A:
pass
class B(A):
pass
issubclass(B, A)
True
issubclass(B, B)
True
issubclass(B, C)
False
issubclass(B, (A, C))
True
*isinstance(object, classinfo)— 如果第一个参数(object)是第二个参数(classinfo)的实例对象,则返回 True,否则返回 False。
另外以下这些常识你应该知道:
- 如果 object是 classinfo 的子类的一个实例,也符合条件
- 如果第一个参数不是对象,则永远返回False
- classinfo 可以是类对象组成的元祖,只要class与其中任何一个候选类的子类,则返回 True
- 如果第二个参数不是类或者由类对象组成的元祖,会抛出一个 TypeError 异常.
a = A()
isinstance(a, A)
True
isinstance(a, B)
False
isinstance(a, 'a')
Traceback (most recent call last):
File "<pyshell#103>", line 1, in <module>
isinstance(a, 'a')
TypeError: isinstance() arg 2 must be a type or tuple of types
另外有type() 方法:
*type() — 看元素类型
-
如果我们要在
if语句中判断,就需要比较两个变量的type类型是否相同 -
>>> type('abc') == type('123') True -
判断基本数据类型可以直接写
int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:>>> import types >>> def fn(): ... pass ... >>> type(fn)==types.FunctionType True >>> type(abs)==types.BuiltinFunctionType True >>> type(lambda x: x)==types.LambdaType True >>> type((x for x in range(10)))==types.GeneratorType True -
能用
type()判断的基本类型也可以用isinstance()判断,总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。# 可以判断是是否是某些类型中的一种 >>> isinstance([1, 2, 3], (list, tuple)) True
type() 隐藏技能:
-
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
-
type()函数可以查看一个类型或变量的类型,B是一个class,它的类型就是type,而b是一个实例,它的类型就是class。>>> class B: ... pass ... >>> type(B) <class 'type'> >>> b = B() >>> type(b) <class '__main__.B'> -
我们说class的定义是运行时动态创建的,而创建class的方法就是使用
type()函数, 无需使用class xxx要创建一个class对象,
type()函数依次传入3个参数:- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数
fn绑定到方法名study上。
通过
type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。>>> def fn(self, name='day day up'): # 定义一个函数 ... print('good good study, %s' %name) ... >>> C = type('C', (object,), dict(study=fn)) # type()动态创建类 >>> c = C() >>> c.study() good good study, day day up >>> type(C) <class 'type'> >>> type(c) <class '__main__.C'>
*dir() – 获得一个对象的所有属性和方法,它返回一个包含字符串的list
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
操作对象状态:
*hasattr(object, name)-- 测试一个对象object是否有指定的属性(name),注意属性用字符串表示需要加引号
*getattr(object, name[, default])— 得到一个对象object的指定属性name的值,如果没有设置default参数则返回attributeERROR, 否则返回default内容
class C:
num = 3
c = C()
hasattr(c, num) #不加引号一定会报错
Traceback (most recent call last):
File "<pyshell#108>", line 1, in <module>
hasattr(c, num)
NameError: name 'num' is not defined
hasattr(c, 'num')
True
hasattr(c, 'number')
False
getattr(c, 'num')
3
getattr(c, 'number', 'Not valid') #当属性不存在时给出一个默认值
'Not valid'
# 假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None
也可以获得对象的方法:
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
*setattr(object, name, value)— 为对象object设置属性name的值,如果属性不存在则新建这个属性并赋值value
*delattr(object, name)— 删除对象的属性name,若属性不存在则抛出AttributeError异常
*property(fget=None, fset=None, fdel=None, doc=None)-- 通过属性设置属性,对用户方便,只需要提供x的接口,以后改方法的时候方便
class property(object)
property(fget=None, fset=None, fdel=None, doc=None)
Property attribute.
|
| fget
| function to be used for getting an attribute value
| fset
| function to be used for setting an attribute value
| fdel
| function to be used for del'ing an attribute
| doc
| docstring
Typical use is to define a managed attribute x:
class C(object):
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
# example:
class Size:
def __init__(self, size=10):
self.size = size
def getSize(self):
return self.size
def setSize(self, value):
self.size = value
def delSize(self):
del self.size
x = property(getSize, setSize, delSize)
######
small = Size()
small.x
10
small.x = 20
small.x
20
small.getSize()
20
del small.x
small.x
Traceback (most recent call last):
File "<pyshell#150>", line 1, in <module>
small.x
File "<pyshell#143>", line 5, in getSize
return self.size
AttributeError: 'Size' object has no attribute 'size'
函数修饰符(decorator)
- 函数修饰符
修饰符是解决这类问题(有插入日志、性能测试、事务处理等)的绝佳设计,有了修饰符,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。 - 概括的讲,修饰符的作用就是为已经存在的对象添加额外的功能。
- Python 的修饰符就是一种优雅的封装,但要注意的是只可以在模块或类定义内对函数进行修饰,不允许修饰一个类。一个修饰符就是一个函数,它将被修饰的函数做为参数,并返回修饰后的同名函数或其它可调用的东西。
Eg:
********************************************
import time
def timeslong(func):
start = time.time()
print("It's time starting ! ")
func()
print("It's time ending ! ")
end = time.time()
return "It's used : %s." % (end - start)
def myfunc():
print("Hello FishC.")
t = timeslong(myfunc)
print(t)
********************************************
等价于下面的修饰符形式:
********************************************
import time
def timeslong(func):
def call():
start = time.time()
print("It's time starting ! ")
func()
print("It's time ending ! ")
end = time.time()
return "It's used : %s." % (end - start)
return call
@timeslong
def myfuna():
print("Hello World.")
@timeslong
def myfunb():
print("Hello Python.")
@timeslong
def myfunc():
print("Hello FishC.")
print(myfuna())
print("========================================")
print(myfunb())
print("========================================")
print(myfunc())
********************************************
或者包装成类的形式
********************************************
import time
class timeslong(object):
def __init__(self, func):
self.func = func
def __call__(self):
start = time.time()
print("It's time starting ! ")
self.func()
print("It's time ending ! ")
end = time.time()
return "It's used : %s." % (end - start)
@timeslong
def myfuna():
print("Hello World.")
@timeslong
def myfunb():
print("Hello Python.")
@timeslong
def myfunc():
print("Hello FishC.")
print(myfuna())
print("========================================")
print(myfunb())
print("========================================")
print(myfunc())
- 这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
>>> def now():
... print('2015-3-25')
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__) # func.__name__ 表示函数的名称
return func(*args, **kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:
>>> now()
call now():
2015-3-25
把@log放到now()函数的定义处,相当于执行了语句:
now = log(now)
由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
- 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')
执行结果如下:
>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper':
>>> now.__name__
'wrapper'
因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
eg: 设计一个decorator打印一个函数的执行时间
# -*- coding: UTF-8 -*-
import time, functools
# 不加参数装饰器
def metric(fn):
@functools.wraps(fn)
def wrapper(*args, **kw):
t1 = time.time()
result = fn(*args, **kw)
t2 = time.time()
print('function %s run %f ms' % (fn.__name__, t2 - t1))
return result
return wrapper
@metric
def fast(x, y):
time.sleep(0.0012)
return x + y
@metric
def slow(x, y, z):
time.sleep(0.1234)
return x y z
f = fast(11, 22)
s = slow(11, 22, 33)
print(f, s)
function fast run 0.006482 ms
function slow run 0.138556 ms
33 7986
# 装饰器加不加参数都可运行
# -*- coding: UTF-8 -*-
import time, functools
def log(text):
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kw):
if isinstance(text, str):
print('%s %s' % (text, fn.__name__))
else:
print('calling %s' % fn.__name__)
return fn(*args, **kw)
return wrapper
if isinstance(text, str):
return decorator
else:
return decorator(text)
@log
def fast(x, y):
time.sleep(0.0012)
return x + y
@log('execute')
def slow(x, y, z):
time.sleep(0.1234)
return x y z
f = fast(11, 22)
s = slow(11, 22, 33)
print(f, s)
枚举类
Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较。- 当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份,好处是简单,缺点是类型是
int,并且仍然是变量。
JAN = 1
FEB = 2
- 更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了
Enum类来实现这个功能:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
value属性则是自动赋给成员的int常量,默认从1开始计数。
如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
@unique装饰器可以帮助我们检查保证没有重复值。
访问这些枚举类型可以有若干种方法:
>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
... print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
python内置的修饰符
- 分别是 staticmethod、classmethod 和 property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性.
# 类方法 @classmethod
# 只需要在类里面的函数名上方加上一个 @classmethod 修饰符,就可直接使用Hello.print_hello()
>>> class Hello(object):
... @classmethod
... def print_hello(cls):
... print("Hello FishC")
>>> Hello.print_hello()
# 类的静态方法 @staticmethod
# 静态方法是类的特殊方法,静态方法只需要在普通方法的前边加上 @staticmethod 修饰符即可。
# 静态方法最大的优点是:不会绑定到实例对象上,换而言之就是节省开销。使用的时候需要注意的地方:静态方法并不需要 self 参数,因此即使是使用对象去访问,self 参数也不会传进去。
>>> class B:
@staticmethod
def checksum(arg1, arg2, arg3):
return arg1 + arg2 + arg3
def nocheck(self):
print('I am X-man!')
>>> b1 = B()
>>> b1.checksum
<function B.checksum at 0x03AF1538>
>>> B.checksum
<function B.checksum at 0x03AF1538> # 注意这个地址和上面b1调用的地址是一样
>>> b1.nocheck
<bound method B.nocheck of <__main__.B object at 0x03AE02E0>>
>>> B.nocheck
<function B.nocheck at 0x03AF1580> # 注意这个地址和上面b1调用的地址不一样
@property(属性修饰符)
-
Python内置的
@property装饰器就是负责把一个方法变成属性调用,既能检查参数,又可以用类似属性这样简单的方式来访问类的变量.(类似于函数用的decorator) -
使用属性修饰符创建描述符:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
- 把一个getter方法变成属性,只需要加上
@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:(Note: 还可以定义只读属性,只定义getter方法,不定义setter方法, 就是一个只读属性了)
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
-
要特别注意:属性的方法名不要和实例变量重名。例如,以下的代码是错误的:
class Student(object): # 方法名称和实例变量均为birth: @property def birth(self): return self.birth- 这是因为调用
s.birth时,首先转换为方法调用,在执行return self.birth时,又视为访问self的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError。
- 这是因为调用
喜欢就记得三连哦 😃
4141




被折叠的 条评论
为什么被折叠?



