class Panthera(object):
"""
豹属动物类
"""
def __init__(self,addr):
self.addr = addr
print(f"这是正在{addr}的一头猎豹")
if __name__ == '__main__':
# 正在撒哈拉沙漠觅食的一头猎豹
Panthera1 = Panthera("撒哈拉沙漠觅食")
# 在东非大草原上追逐斑马的一头猎豹
Panthera2 = Panthera("东非大草原上追逐斑马")
这是正在撒哈拉沙漠觅食的一头猎豹
这是正在东非大草原上追逐斑马的一头猎豹
从上述代码可以看出,类的定义是通过关键字“class 类名A(A继承的类):”
类名首字母大写是约定俗成的。A的父类默认是object,即所有类公共的父类,在Python3中不强制要求写出object,默认为object。 详情参考Python2与Python3关与object的区别
通过“类名(参数列表)”完成的,在这个过程又分为两个阶段。
下面主要介绍一下我们应该怎样依靠自己的能力去查询未知的方法呢?
首先前后两个下划线的方法是内建方法,所以我们在IDLE交互式界面中输入
>>> help(__builtins__.__new__)
Help on built-in function __new__:
__new__(*args, **kwargs) method of builtins.type instance
Create and return a new object. See help(type) for accurate signature
根据提示,我们再去type中更详细的查询,道理也很显然,Python3的每个类都属于
print(type(Panthera))
print(type(Panthera1))
终于在type模块中找到了init和new的详细解释
help(type)
init(self, /, *args, **kwargs)
| Initialize self. See help(type(self)) for accurate signature.Static methods defined here:
|
| new(*args, **kwargs)
| Create and return a new object. See help(type) for accurate signature.
上面的案例中每个豹子实例有两个参数,第一个是self,第二个是addr(豹子所在地域)。self不需要人为传递Python解释器自己会把实例变量传进去(通过new),其余参数在实例化时必须一一对应传入。
在实属性上python默认没有严格限制,除了在____init____里初始化参数,还可以动态创建属性
Panthera1.name = 'A'
print(Panthera1.name)
动态修改属性
del Panthera1.name
print(Panthera1.name)
类的属性又是什么呢?每头猎豹所在的地域不同,但它们都有一些相同的属性,不论在哪里,所有的猎豹的此属性都相同,这就是类的属性。在类中直接定义
class Panthera(Cat):
"""
豹属动物类
"""
father = 'cat'
def __init__(self, addr):
self.addr = addr
print(f"这是正在{addr}的一头猎豹")
def __new__(cls) -> Any:
return super().__new__(cls)
与实例属性相同,类属性也可以做类似的增删改查,方法与实例属性类似。唯一要注意的是,实例属性最好不要与类属性同名,否则会覆盖掉类属性。
我们可以看到,属性的使用随意性极大,这不利于程序的安全性和可靠性。对于某些属性应该有严格的访问和修改限制。
私有属性:属性前加两个下划线
私有属性不能直接通过实例对象调用,为了引用私有属性,可以在类中定义访问和修改其的函数,还可以在函数中对参数做限制
class Panthera(object):
"""
豹属动物类
"""
father = 'cat'
def __init__(self, addr):
self.__addr = addr
print(f"这是正在{addr}的一头猎豹")
def get_Addr(self):
return self.__addr
def set_Addr(self, addr):
if isinstance(addr, int):
raise Exception("地域必须为str")
self.__addr = addr
if __name__ == '__main__':
# 正在撒哈拉沙漠觅食的一头猎豹
Panthera1 = Panthera("撒哈拉沙漠觅食")
# 在东非大草原上追逐斑马的一头猎豹
Panthera2 = Panthera("东非大草原上追逐斑马")
print(Panthera1.get_Addr())
小结:
对于一种抽象事物,除了其本身的特有属性,还有一些特有行为,这些行为就是方法,方法分为类方法和实例方法。这些方法均在类中定义,引用这些方法的人并不知道方法的内在实现细节,这种思想就是封装。
上述代码中的get_addr()和set_addr()就属于封装的一种。顺便引出方法
实例方法:在类中定义的方法都属于实例方法。实例方法的第一个形参必须是self,其余形参根据情况定义。
类方法:通过注解@clasmethod表明一个形参为cls的方法为类方法。cls由python解释器自动传入类自身,但类和实例对象都可调用
@classmethod
def clear(cls):
print("这是豹属动物类")
继续前面的背景;进一步,属于豹属生物(类)的生物必然也属于猫科(类)。所以我们定义猫科类,豹属类的父类就是猫科类,从生物学上看,猫科动物大多喜独居。肉食,常以伏击方式捕杀其他温血动物。豹子和其他猫科动物同样具有这些特点,那是否要在猫科动物的子类都写一遍同样的方法呢?
class Cat(object):
"""
猫科动物类
"""
def __init__(self, family):
self.family = family
# pycharm一般会将没有引用属性的类看做静态方法
def habit1(self):
print(f"我是{self.family}动物,我喜欢独居,是肉食性动物")
def clear(self):
print("这是猫科动物")
class Panthera(Cat):
"""
豹属动物类
"""
father = 'cat'
@classmethod
def clear(cls):
print("这是豹属动物类")
def __init__(self, addr, family):
super().__init__(family)
self.__addr = addr
print(f"这是正在{addr}的一头猎豹")
def get_Addr(self):
return self.__addr
def set_Addr(self, addr):
if isinstance(addr, int):
raise Exception("地域必须为str")
self.__addr = addr
if __name__ == '__main__':
# 正在撒哈拉沙漠觅食的一头猎豹
Panthera1 = Panthera("撒哈拉沙漠觅食", "猫科")
# 在东非大草原上追逐斑马的一头猎豹
Panthera2 = Panthera("东非大草原上追逐斑马", "猫科")
# print(Panthera1.get_Addr())
Panthera1.clear()
print(Panthera2.family)
子类后的括号内要写出父类的名称,在子类的____init____方法中要通过super().____init____引用父类方法,从而使子类实例拥有父类的属性。父类也称为超类(superclass),名称super因此而得名。子类(Subclass)
子类重写父类的方法,当父类的方法不适合子类时,即可在子类中定义与父类方法名相同的方法,修改方法内部实现。因此调用子类方法时,将会调用子类自身的方法。
1.顾名思义,一个对象有多种状态,拿子类来说。子类既属于本身又属于父类,但父类不属于子类
print(isinstance(Panthera1, Cat))
print(isinstance(cat,Panthera))
2.传参时,参数是父类的实例,但子类对象仍可以做实参
def fight(cat):
cat.habit1()
if __name__ == '__main__':
# 正在撒哈拉沙漠觅食的一头猎豹
Panthera1 = Panthera("撒哈拉沙漠觅食", "猫科豹属")
cat = Cat("猫科")
fight(cat)
fight(Panthera1)
再创建一个老虎类,重写habit1()方法
class Tiger(Cat):
"""
虎属
"""
def __init__(self, family):
super(Tiger, self).__init__(family)
def habit1(self):
print(f"我是{self.family}动物,我是万兽之王,是肉食性动物")
if __name__ == '__main__':
tiger = Tiger("虎属")
fight(tiger)
发现,fight函数依然可以使用,这样无论创建多少个子类,都可以作为fight()的参数。这就是著名的“开闭”原则:
对扩展开放:允许新增Cat子类;
对修改封闭:不需要修改依赖Cat类型的fight()
等函数。
3.动态语言的“鸭子类型”
对于fight()函数来说,传入参数只有有habit1()函数即可;对于Java等静态语言,传入参数有严格的限制,必须是其本身或子类。
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()
方法,返回其内容。但是,许多对象,只要有read()
方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()
方法的对象。