Python学习日记5
——类和对象(2)
昨天404了,一天啥事儿没干成,学的还没头没脑的,今天解答一些疑惑。
到底何为对象?
我们说,类是一批对象的抽象,可以把类抽象成某种概念;对象才是一个具体存在的实体;例如“汽车模型”是一个类,那么“吉普车”、“摩托车”就是对象,我们所说的某个人是人的对象,而不是人类。
大多数时候,定义一个类是为了重复创建该类的对象,同一个类的多个对象具有相同的特征,而类则定义了多个对象的共同特征。从某个角度来看,类定义的是多个对象的特征,因此类不是一个具体存在的实体,对象才是一个具体存在的实体。
然后我就开始了懵了。懵起始于我执着于为啥不能直接用类的名字来调用方法?今天又仔细读了一遍,昨天真的是在梦游? 404 No Found
实际上,Python程序中处理的每样东西都是一种对象,通过抽象和具体的区别只是为了好理解二者的关系而已,并不是对其下的定义。像函数、模块和类这样的编程单元在Python中也是对象,他们由def、class、import和lambda这样的语句和表达式创建,并且可以在脚本之间自由传递、存储在其他对象中。
(鹅是真滴蠢,好歹也是学过一点JAVA的人,上课时候在做梦?。。太笨了)
4.2 方法
方法是类或对象的行为特征的抽象,但Python的方法其实也是函数,定义方法和函数也很相像。
4.2.1类也能调用实例方法
Python的类中定义的方法默认都是实例方法,创建对象后即可通过对象来调用实例方法。
Python的类就像命名空间。Python程序默认处于全局命名空间内,类体则处于类命名空间内。Python允许在全局范围内放置可执行代码,类体允许在类范围内放置可执行代码。
# 定义全局空间的fun函数
def fun():
print("全局空间内的fun方法")
# 定义全局空间内的bar变量
bar = 20
#-------------------
class ssalc:
# 定义类体内的fun函数
def fun(self):
print("类体空间内的fun方法")
# 定义类体内的bar变量
bar=2000
#----------------------
# 调用全局空间的函数和变量
fun()
print(bar)
# 调用类空间内的函数和变量
s=ssalc()
ssalc.fun(s)
print(s.bar)
Python的类可以调用实例方法,但使用类调用实例方法是,Python不会自动为方法的第一个参数self绑定参数值,程序必须显式地为第一个参数传入方法调用者。上面代码的最后如是。像下面的代码就会报错:
ssalc.fun()
TypeError: fun() missing 1 required positional argument: 'self'
这里也就是我之前懵圈的地方,为啥不可以直接使用类名来调用,实际是参数传递导致的问题。
4.2.2 类方法与静态方法
Python支持定义类方法,甚至也支持定义静态方法。类方法和静态方法很相似,他们的区别在于:Python会自动绑定类方法的第一个参数,类方法的第一个参数(通常命名为cls)会自动绑定到类本身;但对于静态方法则不会自动绑定。
@classmethod修饰的方法即为类方法 ;
使用@staticmethod修饰的方法就是静态方法。
class player:
@classmethod
def classM(cls):
print('类方法:',cls)
@staticmethod
def staticM(p):
print('静态方法:',p)
#调用类方法,player类会自动绑定到第一个参数
player.classM()
#调用静态方法,不会自动绑定,需要手动绑定
a=player()
a.classM() #类方法不需要手动绑定
#用对象调用staticM静态方法,实际上依然还是使用类来调用的
#必须为第一个参数执行手动绑定
a.staticM('hello')
4.2.3 @ 函数装饰器
上面所述的@classmethod 和@staticmethod 本质就是函数装饰器,其中classmethod 和 staticmethod都是python内置的函数。
使用@符号来引用已有函数后,可用于修饰其他函数,装饰被修饰函数。
当程序使用“@A函数” 装饰另一个“B函数”,实际上完成了下面两步:
- 将被修饰的函数B作为参数传给@符号引用的A函数;
- 将B函数装饰成第1步的返回值。
def funA(fn):
print('A')
return 'fkit'
@funA #相当于把funB作为funA的参数,funA(funB)
def funB():
print('B')
print(funB)
A
fkit #因为funA的返回值是fkit,所以此时funB=fkit,
# funB相当于被替换成了字符串
通过@符号来修饰函数是一个非常实用的功能,它既可以在被修饰的函数的前面添加一些额外的处理逻辑如权限检查,也可以在被修饰函数的后面添加一些额外的处理逻辑如记录日志,还可以在目标方法出现异常时进行一些修复操作。
面向对象语言的三大特征:多态、封装和继承:
4.3 多态
多态是Python编程方式的核心,一个有趣的叫法把它称为:鸭子类型。这来源于一个说法:“如果走起来像鸭子,叫起来像鸭子,那么它就是鸭子”。
当你无需知道对象时什么类型的、什么样的就可以对其执行操作,都是多态在发挥作用。如Python并没有要求对new的变量声明变量类型,就可以直接使用变量;再如加法运算符“+”既可以用于数也可以用于字符串;又如一个可称为多态的集大成者的函数 repr(),它能把一个对象用字符串的形式表达出来以便辨认,那么就可以构造一个函数来指出对象的长度:
def length_fun(x):
print('The length of',repr(x),is,len(x)
这个函数便可以用于任何输出任何对象的长度。
如果想要破坏多态,唯一的办法是使用如type、issubclass、isinstance等函数显式地执行类型检查,但是其实issubclass本身也是多态的了。
4.4 封装
封装(Encapsulation)指的是向外部隐藏不必要的细节。
这好像多态一样,无须知道对象的内部细节就可以执行它,这两个概念很像,因为他们都是抽象的原则。
封装是面向对象语言对客观世界的模拟,在客观世界里,对象的状态信息都被隐藏在对象内部,外界无法直接操作和修改。对一个类或对象实现封装可达到这些目的:
- 隐藏类的实现细节
- 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对属性的不合理访问。
- 可以进行数据检查,有利于保证对象信息的完整性
- 便于修改,方便代码的维护
为实现良好的封装需要从两个方面来考虑:
- 将对象的属性和实现细节隐藏起来,不允许外部直接访问
- 把方法暴露出来,让方法来控制对这些属性进行安全的访问和操作。
把该藏的藏起来,把该露的露出来,就像穿衣服一样hhhh
Python没有提供和JAVA中priviate一样的修饰符,所以Python不能真正支持隐藏。Python隐藏的方法是:将类的成员命名为以双下划线开头的,Python就会将它们隐藏起来。
class User :
def __hide(self):
print('示范隐藏的hide方法')
def getname(self):
return self.__name
def setname(self, name):
if len(name) < 3 or len(name) > 8:
raise ValueError('用户名长度必须在3~8之间')
self.__name = name
name = property(getname, setname)
def setage(self, age):
if age < 18 or age > 70:
raise ValueError('用户名年龄必须在18在70之间')
self.__age = age
def getage(self):
return self.__age
age = property(getage, setage)
# 创建User对象
u = User()
u.name = 'fkit'
# 引发 ValueError: 用户名长度必须在3~8之间
User类的两个实例变量 __name和 __age被隐藏了起来,这样程序就无法直接访问这两个变量,只能通过setname()、getname()、setage()、getage()这些访问器方法进行访问,而setage()、setname()会对用户设置的name、age进行控制,只有符合条件的才允许设置。
上面的代码里还定义了一个__hide()方法,这个方法默认是隐藏的,如果程序尝试调用如下代码就会报错:
#尝试调用隐藏的__hide()方法
u.hide()
AttributeError: 'User' object has no attribute '__hide'
但是Python其实没有真正的隐藏机制,双下划线只是一个小技巧:Python会偷偷改变以双下划线开头的方法名,会在这些方法名前添加单下划线和类名,因此可以根据下面的方式调用:(但是通常不这么干)
# 调用隐藏的__hide()方法
u._User__hide()
4.5 类的继承
继承是偷懒的重要手段。Python的继承是多继承机制,即一个子类可以同时有多个直接父类。
4.5.1 继承的语法
Python子类继承父类的语法是在定义子类是,将多个父类放在子类之后的圆括号里,即可表明该子类继承了这些父类:
class SubClass(SuperClass1,SuperClass2,...):
#定义部分
如果在定义一个类时并未显式指定这个累的父类,则这个类继承于Object类,那么也就是说,Object类是所有类的父类,要么是直接父类要么是间接父类。
通常父类包含的范围要比子类大,子类是父类的拓展,父类派生出子类,子类也是一种特殊的父类。
4.5.2 重写父类的方法
大多数时候,子类总以父类为基础,再对父类加以拓展,但子类也有可能觉得父类不那么棒或是父类的方法并不适合子类,长江后浪推前浪,子类就需要重写父类的方法。
class Driver:
# Driver类的fly()方法
def driver(self):
print("我会开拖拉机...")
class Ostrich(Driver):
# 重写Driver类的driver()方法
def driver(self):
print("我会开大卡车,挖掘机,拖拉机,直升机...")
# 创建Ostrich对象
os = Ostrich()
# 执行Ostrich对象的driver()方法,将输出"我会开大卡车,挖掘机,拖拉机,直升机..."
os.driver()
这种子类包含与父类同名的方法的现象称为方法重写(Override),也被称为方法覆盖。
4.5.3 使用未绑定方法调用被重写的方法
如果在子类中调用重写之后的方法,Python总是会执行子类重写的方法,不执行父类中被重写的方法,如果需要在子类中也能调用父类中被重写的方法时,就可以使用类名调用的方法来调用。
class BaseClass:
def foo (self):
print('父类中定义的foo方法')
class SubClass(BaseClass):
# 重写父类的foo方法
def foo (self):
print('子类重写父类中的foo方法')
def bar (self):
print('执行bar方法')
# 直接执行foo方法,将会调用子类重写之后的foo()方法
self.foo()
# 使用类名调用实例方法(未绑定方法)调用父类被重写的方法
BaseClass.foo(self)
sc = SubClass()
sc.bar()
4.5.4 使用super函数调用父类的构造方法
Python的子类也会继承得到父类的构造方法,如果子类有多个父类,那么排在前面的父类的构造方法会被先使用,例如:
class Employee :
def __init__ (self, salary):
self.salary = salary
def work (self):
print('普通员工正在写代码,工资是:', self.salary)
class Customer:
def __init__ (self, favorite, address):
self.favorite = favorite
self.address = address
def info (self):
print('我是一个顾客,我的爱好是: %s,地址是%s' % (self.favorite, self.address))
# Manager继承了Employee、Customer
class Manager (Employee, Customer):
pass
#m = Manager(25000)
m = Manager('IT产品', '广州')
#m.work() #代码1
m.info() #代码2
代码中定义的Manager类,该类继承了Employee和Customer类,因为Employee排在前面所以会优先使用他的构造方法。所以用Manager(2500)来创建Manager对象是可以的,该方法会初始化salary实例变量,所以执行代码1可以,但是代码2就会报错,因为程序并未初始化favorite和address两个变量。
为了让Manager能同时初始化两个父类,它就应该定义自己的构造方法——就是重写父类的构造方法。Python要求:如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法;有以下两种方式:
- 使用未绑定方法
- 使用super()函数调用父类的构造方法
super其实是一个类,因此调用super的本质就是调用super类的构造方法来创建super对象。
使用super()构造方法最常用的就是不传入任何参数,然后通过super对象的方法既可以调用父类的实例方法,也可以调用父类的类方法。。在调用实例方法时会完成第一个参数self的绑定,在调用类方法时会完成第一个参数cls的绑定。
那么上面的程序就可以改为下面的形式:
class Manager(Employee, Customer):
# 重写父类的构造方法
def __init__(self, salary, favorite, address):
print('--Manager的构造方法--')
# 通过super()函数调用父类的构造方法
# super().__init__(salary)
# 与上一行代码的效果相同
super(Manager, self).__init__(salary)
# 使用未绑定方法调用父类的构造方法
Customer.__init__(self, favorite, address)
# 创建Manager对象
m = Manager(25000, 'IT产品', '广州')
m.work() #①
m.info() #②
5.6 Python的动态性
Python是动态语言,表现在:类、对象的属性、方法都可以动态的增加和修改。
5.6.1动态属性与__slots__
为对象动态添加方法只对当前对象有效,如果希望为所有实例都添加方法,则可以通过为类添加方法实现,但是也会带来一定的隐患。
如果要限制为某个动态增加属性和方法,则可使用__slots__属性来指定。
class Dog:
__slots__ = ('walk', 'age', 'name')
def __init__(self, name):
self.name = name
def test():
print('预先定义的test方法')
d = Dog('Snoopy')
from types import MethodType
# 只允许动态为实例添加walk、age、name这3个属性或方法
d.walk = MethodType(lambda self: print('%s正在慢慢地走' % self.name), d)
d.age = 5
d.walk()
#如果尝试添加其他属性,那么会报错
d.foo = 30 # AttributeError
有两点注意:
- __slots__属性并不限制通过类来动态添加属性或方法
- __slots__属性只对当前类的实例起作用,对派生出来的子类并无作用,需要在子类中重新定义__slots__属性。
5.6.2 使用 metaclass
如果希望#创建某一批类全部具有某种特征,则可通过mataclass来实现。使用metaclass可在创建类时动态修改类定义。
为了使metaclass动态修改类定义,程序需先定义metaclass,metaclass应继承type类,并重写__new__()方法。
5.6.3 使用type()动态创建类
type()函数可用来查看变量的类型:
class Role:
pass
r = Role()
# 查看变量r的类型
print(type(r)) # <class '__main__.Role'>
# 查看Role类本身的类型
print(type(Role)) # <class 'type'>
可见,类本身的类型是type。
当程序使用class创建类时,可以理解为定义了一个特殊的对象(type类的对象),并讲该对象复制给Role变量,所以用class定义的所有类都是type类的实例。
所以可以用type()函数来动态创建类:
def fn(self):
print('fn函数')
# 使用type()定义Dog类
Dog = type('Dog', (object,), dict(walk=fn, age=6))
# 创建Dog对象
d = Dog()
# 分别查看d、Dog的类型
print(type(d))
print(type(Dog))
d.walk()
print(Dog.age)
<class '__main__.Dog'>
<class 'type'>
fn函数
6
在使用type()创建类时可指定三个参数:
- 参数一:创建的类名
- 参数二:继承的父类的集合
- 参数三:该字典对象为该类绑定的类变量和方法。
几个代码直接用了疯狂python讲义里面的了,实在不想写了
明天一定要好好复习啊
自闭了。。。。。。。。。。。。。
自闭了。。。。。。。。。。。。。
自闭了。。。。。。。。。。。。。