Type、Object与Class关系

type创建所有对象,包括它自己本身,这便是一切皆对象的缘由,而所有类的基类都是object

抽象基类与鸭子类型

from abc import ABCMeta,abstractmethod #(抽象方法)

class Payment(metaclass=ABCMeta): # metaclass 元类 metaclass = ABCMeta表示Payment类是一个规范类
def __init__(self,name,money):
self.money=money
self.name=name

@abstractmethod # @abstractmethod表示下面一行中的pay方法是一个必须在子类中实现的方法
def pay(self,*args,**kwargs):
pass

class AliPay(Payment):

def pay(self):
# 支付宝提供了一个网络上的联系渠道
print('%s通过支付宝消费了%s元'%(self.name,self.money))

class WeChatPay(Payment):

def pay(self):
# 微信提供了一个网络上的联系渠道
print('%s通过微信消费了%s元'%(self.name,self.money))


class Order(object):

def account(self,pay_obj):
pay_obj.pay()

pay_obj = WeChatPay("wang",100)
pay_obj2 = AliPay("zhang",200)

order = Order()
order.account(pay_obj)
order.account(pay_obj2)

这是抽象基类的用法,特点是在构造一个Payment抽象基类后,它不能直接实例化,只能被其他类去继承,如Alipay和WeChatPay两个类,继承后的类必须要复现抽象基类里面的方法,否则报错!

此代码完美解释了什么是鸭子类型,Order类中的方法account要求传入一个对象,并且只要求其含有pay方法,因而不管它是什么对象,只要有pay方法,就认为它与Payment基类一致

类变量与实例变量

class A:
aa = 10
def __init__(self,x,y):
self.x = x
self.y = y

这里aa变量为A类的类变量,其创造的所有实例共享,可以通过A.aa = 20来修改该类变量,但是不能通过实例对象来修改类变量,如:

test = A(3,4)
print(test.aa=20)

test.aa=20这个会给test实例添加一个新的属性aa

类和实例属性的查找顺序

class D:
pass
class B(D):
pass
class C(D):
pass
class A(B,C):
pass
print(A.__mro__)

输出结果:(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)

表明这种菱形结构的继承关系查找顺序是:A–>B–>C–>D–>object


class D:
pass
class E:
pass
class B(D):
pass
class C(E):
pass
class A(B,C):
pass
print(A.__mro__)

输出结果是:(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)

表明继承顺序是:A–>B–>D–>C–>E–>object

实例方法、类方法、静态方法

参考链接

class Date:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day

def __str__(self):
return "日期为:{year}年{month}月{day}日".format(year=self.year,month=self.month,day=self.day)

@staticmethod
def parse_from_string(time_string):
year,month,day = tuple(time_string.split('-'))
return Date(int(year),int(month),int(day))

@classmethod
def from_str(cls,time_string):
year,month,day = tuple(time_string.split('-'))
return cls(int(year),int(month),int(day))

d = Date(2019,9,11)
d1 = Date.parse_from_string('2022-12-36')
d2 = Date.from_str('2023-12-36')
print(d,d1,d2)

输出结果:日期为:2019年9月11日 日期为:2022年12月36日 日期为:2023年12月36日

静态方法、类方法使用区别或者说使用场景:

  1. 模拟构造函数_init_()多次初始化类
  2. 类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法
  3. 静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。

super()函数

  1. 为什么要在子类中调用父类的构造函数?
  2. super的执行顺序

一、有些子类需要利用父类init中的一些属性,列如多线程中使用Thread模块中的name属性,就不用自己再写一次了

from threading import Thread
class MyThread(Thread):
def __init__(self,name,user):
self.user = user
super().__init__(name = name)

super的作用:

  • 如果子类(Puple)继承父类(Person)不做初始化(_init_),那么会自动继承父类(Person)属性name。

    注:此时子类实例化时必须传入参数name,否则报错!

  • 如果子类(Puple_Init)继承父类(Person)做了初始化,且不调用super初始化父类构造函数,那么子类(Puple_Init)不会自动继承父类的属性(name)。

  • 如果子类(Puple_super)继承父类(Person)做了初始化,且调用了super初始化了父类的构造函数,那么子类(Puple_Super)也会继承父类的(name)属性。

  • 在子类中一个和父类重名的方法中调用父类的方法super(Child,self).fun(),等效于直接写父类名.fun(self),但是这种硬编码的方式不太好。super(A,self).__init__()为子类调用父类初始化的写法。

二、super()._init_()在多继承中是根据mro方式来执行的,如:

class A:
def __init__(self):
print('A')

class B(A):
def __init__(self):
print('B')
super().__init__()

class C(B):
def __init__(self):
print('C')
super().__init__()

class D(B,C):
def __init__(self):
print('D')
super(D,self).__init__()

d = D()

执行顺序:D–>B–>A–>C

传递参数(list,dict)时引发的问题!

引入:

def add(a,b):
a+=b
return a
a = [1,2]
b=[3 4]
c= add(a,b)
print(a)
print(c)

结果是a为[1,2,3,4],c也为[1,2,3,4],但是如果传递的a是元组(1,2),则a的值不会改变,其原因是对于list这种类型的结构,它可以被修改

进一步:

class Company:
def __init__(self,name,staffs=[]):
self.name = name
self.staffs = staffs

def add(self,staff_name):
self.staffs.append(staff_name)

def remove(self,staff_name):
self.staffs.remove(staff_name)

com1 = Company("com1",["bobby1","bobby2"])
com1.add("bobby3")
com1.remove("bobby2")
print(com1.staffs)

输出结果:['bobby1', 'bobby3']

但是,如果是这样:

com2 = Company("com2")
com2.add("bobby4")
print(com2.staffs)

com3 = Company("com3")
com3.add("bobby5")
print(com2.staffs)
print(com3.staffs)
print(Company.__init__.__defaults__) #查看默认值

打印结果:

其原因在于Company对象默认的值为一个空列表,因而如果创建对象时未传递列表将会默认使用这个空列表,也就是后续对象都将指向这个默认值容器,因此容易出错,改用元组将会避免此bug

__new__()与_init_()

参考资料

元类编程

引入:若想动态地创建类,则常用的方式是在函数中创建,如下:

def create_class(name):
if name == 'human':
class Human:
def __str__(self):
return "this is class Human"

return Human
elif name == 'animal':
class Animal:
def __str__(self):
return "this is class Animal"

if __name__ == '__main__':
cls = create_class('human')
human = cls()
print(human)

由前述可知,type能够创建一切对象,故可考虑用type来动态的创建类,type的源代码为:

#要给type创建类添加的方法
def say_name(self, sname):
print(self.name + sname)

cls = type('human', (), {'name': 'human', 'say_name': say_name})
human = cls()
print(human.say_name(',this is what i need to say'))

其中cls = type('human', (), {'name': 'human', 'say_name': say_name}),第二个参数代表这个类要继承的基类,如写了某个类叫Baseclass,则该参数为(Baseclass, )逗号必须加,否则抛出异常!,若没有需要继承的类必须加(),第三个参数是类的属性和方法

这里便能更好的理解元类,所谓元类就是类的类。type本身就是类,由它还可以创建类并能实例化对象。这里就能理解前面所说的对象是由类创建的,而类本身又是对象,它是由type创建的。

python中*args和**kwargs的理解

  1. *args和**kwargs主要用于定义函数的可变参数*
  2. *args:发送一个非键值对的可变数量的参数列表给函数
  3. **kwargs:发送一个键值对的可变数量的参数列表给函数
  4. 如果想要在函数内使用带有名称的变量(像字典那样),那么使用**kwargs。
def foo(a, b, *number, c):
print('a:', a)
print('b:', b)
print('c:', c)
print('number:', number)
for i in number:
print(i)
print(type(number))


foo(1, 2, 3, 4, 5)
#结果会报错

注:和序列解包不同,这里c并不会分配参数,而是剩下的参数都被number获取

def bar(**number):
print(number)


bar(a=1, b=2, c=3)

结果是:{'a': 1, 'b': 2, 'c': 3}

参考博客