面向对象编程
类和实例
类:关键字class,类就是创建一个模板;实例就是将模板实例化
构造方法:__init__负责绑定类的一些必须的属性,当实例化的时候,必须接受这些属性。在类里面也可以定义函数(在里面叫方法),第一个参数必须是self,其他就跟正常函数没有啥区别了。
访问限制
当我们使用__init__方法定义一些类的属性,在外面还是可以被直接更改,可以在定义的时候在前面加一个__,确定为私有变量,外面不能直接访问了,通过实例对象也不能看了。只能再写方法才能看。(跟大二上学Java的时候一样,就是再写set方法和get方法)
继承和多态
继承
就是跟Java一样,class类的时候,右边的括号写继承那些类,子类可以获得父类的所有方法。
多态
就是当子类和父类都有同一种方法后, 字类的这个方法就会覆盖父类的这个方法。
获取对象信息
type()方法,这个就很熟悉了。还有isinstance()这个方法返回True和false。
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个列表里面包含字符串,字符串都是这个对象的属性和方法。
三个方法getattr()、setattr()和hasattr(),我们可以直接操作一个对象的状态。
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
#还有一个小提醒,想给实例对象添加某个属性
obj.name = "li"
#这个就相当于在init里面加个self.name=name了。
hasattr(obj, 'x') #对象obj有属性'x'吗?返回True
setattr(obj, 'y', 19) # 设置一个属性'y'为19
getattr(obj, 'y') # 获取属性'y',返回的是19
getattr(obj, 'z', 404) # 获取属性'z',如果不存在
#,返回默认值404
#也可以用这个三个函数获取方法,同样的用法,就不写了。
实例属性和类属性
class Student():
name_1 = "我是类属性"
def __init__(self,name):
self.name = name #这个是实例属性
在类里面用init构造函数写的属性是实例属性,就是实例出来的对象有的属性,而直接在类里面定义的就是这个类本身有的属性,两个属性名字建议不要起一样的。
面向对象高级编程
__slots__方法
限制输入属性的
当我们创建一个类后,实例对象之后,需要给这个对象添加某个属性,可以直接添加(上面有代码),但是需要给对象添加某个方法的时候,可以这样
class Student():
pass
#这个是给实例对象添加属性
a = Student()
a.name = "环"
#添加方法
from types import MethodType
def add_way(self):#注意这里要添加参数self。
print("这个对象添加了这个方法")
a.add_way = MethodType(add_way,a)#主要是这一句
a.add_way()
但是这个给实例对象添加方法,其他这个类的实例对象不能用,所以为了给所有的实例对象都能用这个方法,可以给类绑定这个方法,可以这样做:
Student.add_way = add_way#主要是这句
b = Student()
b.add_way()
#还是上面的那个函数
跟给实例对象绑定方法不一样,给类绑定方法简洁一些。
如果我们想要限制实例对象的属性,只允许这个类的实例对象添加这几个属性。可以使用__slots__方法:
clasa Student():
__slots__ = ("name","age")
#只允许实例对象添加这两个属性,添加其他属性就会报错
a = Student()
a.name = "环"#这句没有报错
a.score = 99#这句就会报错
还有一条就是,定义__slots__的这个类,限制实例属性的这个方法只对这个类的实例对象有限制,而对这个类的子类没有影响。
使用@property
又是一个装饰器
前面学的,当在类里面定义私有实例属性的时候,需要再写set方法和get方法来更改和访问这个属性。
python中的@property装饰器,就是负责把一个方法变成属性调用的
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
#这段if就是对输入的参数筛选的,不用管
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
a = Student()
a.set_score(30)
print(a.get_score())
#这个就是学Java那时候的一样,但是没有init这个构造函数,没有开始就设置实例属性,而是set方法获取,get方法来访问,里面没有init方法但是那个get方法就相当于是个构造方法,所以实例对象的时候不需要传值。
使用装饰器之后
class Student(object):
#注意,这个跟上面的代码,在函数名这里变了
@property#@property装饰的是get_score函数
def score(self):
return self._score
@score.setter#这个看装饰器的名字也知道,装饰的是赋值函数
def score(self, value):
self._score = value
a = Student()
a.score = 50 #实际是a.get_score(50)
print(a.score)#里面的a.score实际就是a.get_score
一般@property用于只会返回参数的函数,这样实例对象调用的时候,只需要a.函数名就可以获得这个函数的返回值了。写法一般就写个返回值就行了。
而@函数名.setter这个装饰器一般跟@property连用,@property会返回值,这个就是获得值,写法跟构造方法一样。
注意,这俩装饰器连用的时候,需要函数名一样。
特别注意:再写带有装饰器的函数的时候,返回值跟函数名千万不能一样,上面的代码,返回的值前面都带有一共横杠,就是跟函数名区别的,不然会陷入死循环。
练习题:
多重继承
这个简单就记得一个类可以继承多个类就行了。
一个子类可以继承多个类:class Studnet(Person,Son):这个就是继承了两个类。这个就是多重继承。
这种多重继承的设计就叫做MixIn。而只支持单一继承的Java没有。
定制类
插入一个小知识,len()方法,平时直接用len()就会返回容器的长度,但在类的里面需要这个方法能直接返回实例属性还是类属性的长度,就必须在前面调用__len__(self):
class Student():
def __init__(self,name):
self.name = name
def __len__(self):
return self.name
其实还有很多这种实用的小方法可以帮助我们定制类。
-
str
正常如果我们打印一个实例对象,会出来地址对象这些比较乱的东西。
所以可以使用定义__str__这个方法,返回我们能自己看的这个实例对象
其实这里面涉及到内部的一个__repr__()函数,正常被调用的是这个函数,返回的是这个函数的值,当定义__str__函数之后,就优先str这个函数,其实也可以在后面加一个这句话:repr = str ,也是可行的。 -
iter
这个方法会返回一个可迭代对象,跟len写进类里面的方法一样,把这个方法写进类里return self,就是返回自身实例对象的意思,所以需要在里面写一个next()里面不断返回值。例如:
class Fib():
def __init__(self):
self.a,self.b = 0,1#初始化两个计数器
#返回自身的实例对象作为可迭代对象
def __iter__(self):
return self
def __next__(self):
self.a,self.b = self.b,self.a+self.b
if self.a > 100:#退出循环条件
raise StopIteration
return self.a
for i in Fib():#在for循环里就是不断调用next方法。
print(i)
- getitem
上面写的Fib()这个实例对象,虽然可以不断返回斐波那契数列的值,但是不能像list那样,所以要表现得像list那样按照下标取出元素,需要实现getitem()方法:
class Fib():
def __getitem__(self, item):
a,b = 1,1
for i in range(item):
a,b = b,a+b
return a
f = Fib()
print(f[0])
print(f[1])
print(f[2])
print(f[3])
print(f[4])
列表还能实现切片的功能,实例Fib()对象之后,传入的值可以像上面是个int也可以是个切片,但是上面这个函数无法识别,所以需要判断出是个切片然后再写出切片的功能,切片也是一种数据类型,也是个对象。
class Fib():
def __getitem__(self, item):
a,b = 1,1
if isinstance(item,int):
for i in range(item):
a,b = b,a+b
return a
if isinstance(item,slice):
start = item.start
end = item.stop
l = []
if start is None :
start = 0
for i in range(end):
a,b = b,a+b
if i == start:
l.append(a)
start += 1
return l
f = Fib()
print(f[0:9])#z注意传入切片的时候不加括号
除了通过定义__getitem__()方法来使对象能像列表一样,还可以做一些改动,改成字典,元组,具体实现,边看文档变自己练习。
-
getattr
这个getattr,后面的attr全写开就是attribute(属性,特征),当我们在类里面写这个方法之后,实例对象后,当访问一些类里面没有的属性或则和方法的时候,python解释器会去这个方法里面找相应的相应的属性和方法名,如果找到就按着返回的返回值,没有就会默认返回None。
这个只是访问一些没有的属性的时候可以这样。
还有可以返回一些方法。
注意__getattr__方法里面,return的时候只需要返回函数的名字,不加括号,调用的时候加括号,说明这是个函数。 -
call
python里面有对象和对象的方法(方法就是函数),当我们写好类之后,需要把这个类给实例化对象,然后可以通过这个对象来调用类里面的一些方法。有没有一种写在类的函数,可以使用对象本身来调用。
call函数就是可以通过自己本身来调用的,但也可以这样想,函数和对象之间的界限也可以模糊。能被调用的就是一个callable(可调用的)对象。
使用枚举类
枚举类型是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。
使用普通类来实现枚举:
@unique#装饰器需要写在这
#装饰器不仅仅是只能装饰函数,也能装饰类。
class color():
YELLOW = 1#RED是键,数字是值
RED = 2
GREEN = 3
PINK = 4
# 访问枚举项
print(color.YELLOW)#需要根据value的值获得枚举常量
但枚举类要求里面的键不能相同,而且不能在外部修改。而且这样定义比较麻烦,所以可以使用python内置的enume模块(不是前面那个enumerate返回元素跟索引的那个函数)。
- 枚举类不能被实例化为对象
- 枚举类里面定义的Key = Value,在类外部不能修改Value值。
- 导入Enum之后,一个枚举类中的Key和Value,Key不能相同,Value可以相,但是Value相同的各项Key都会当做别名。
- @unique装饰器可以帮助我们检查保证没有重复值。
但是像上面那样创建枚举类比较麻烦,可以直接使用Enum()函数来创建枚举类。
from enum import Enum
Month = Enum('month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
Enum("第一个参数,用来写枚举类的类名",("第二个参数一般用来放枚举变量的元组")),上面代码又在前面写个Month,但是枚举类是不能创建实例对象的,我的理解就是第一个参数,其实就是个可以填充的参数,前面的填充过来。最后还是以Month为准。
每一个元组变量都有name和value这两个属性,name就是元组里面的变量名,value代表该枚举元素的序号(序号通常从1开始)。如果你再赋值,肯定以你赋值的为准。
python为枚举类提供了一个__members__方法,当枚举类调用这个方法,会返回字典,这个字典的键是变量名,值是一个枚举元素,有name和value这里属性。
元类
#使用type()来创建类
def f(self):#注意在外面写函数的时候还是需要写self的
print("这个方法被调用了")
#前面还是得写个类名=,我的理解还是跟上面创建Enum枚举类一样,填充类名。
Student = type("Student",(),dict(add = f))#这样这个类就创建好了
a = Student()
a.add()
type()一般用来返回类或对象的属性,其实也可以被用来创建类
使用type()创建一个类,需要传入三个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数f绑定到方法名add上。(不知道为什么要用dict的写法,反正就这样写,而且需要绑定的函数在外面提前写,里面还是得加self的参数的)
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。所以在python内部,最后还是调用的type()来创建类。
metaclass这个叫元类
现在估计也不咋用,而且也看不懂,以后需要再来学。反正就是可以通过metaclass来修改类定义的,以后遇到再来看。
元类是面向对象高级最难理解的一块了,也是最后一块。
错误,调试和测试
错误
就是try-except,其实还有个finally就是写在后面一定会执行的。
还有一个抛出错误raise,这个就是主动抛出错误,自己知道哪错了,自己主动抛出错误。
调试
断言 assert
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
assert就是断言,断言后面是个表达式,如果表达式是Flase错误,就会返回后面那句话。可以通过-0(具体在哪写我也不知道),把这句断言给省略掉,就是pass的意思了。
logging
logging允许我们指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。
这个就是指定信息info。
pdb调试器
就是pycharm的debug设置断点啥的。还是得多练这个
单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。(需要看再补)
文档测试
Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。同样,用到再来学。
IO进程
文件读写
读取文件f = open("路径","r"),然后调用f.read()方法来打印文件的所有内容,输出成str。最后f.close(),不然会占用内存。但是可以用with语句可以帮我们自动close。
with open('/path/to/file', 'r') as f:
print(f.read())
还有要是文件很大,读进内存里放不下,可以在read()传入参数,来说明读入多少。还可以调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。
要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件即可,就在读取的时候把后面的"r"改成“rb”就可以了。
字符编码
要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
f.read()
写文件
跟读文件一样,不过读取的时候传入的是“w”或是“wb”,而且调用的是write方法。因为写文件是把写的操作放进内存,只有最后调用close才会写进磁盘,所以还是用with语句最好。
StringIO和BytesIO(读和写的操作不能同时用)
StringIO和BytesIO也是一个对象,可以直接在写在内存里,然后直接在内存中读取。但是得提前声明这是个StringIO和BytesIO对象。
StringIO
读入:
这个就是单独一个读入的操作,我写一个StringIO对象放进内存里,但是不能调用read()方法查看,但是有一个内置的getvalue()方法可以用来查看。
读取:
这个里面就是写操作,可以调用read()方法。
BytesIO
字节流跟字符流操作都是一样,不过字节流可以操作二进制的数据:
f.write('中文'.encode('utf-8'))
其他都跟字符流的操作一样。
操作文件和目录
就是os模块的一些操作
查看当前目录的绝对路径:
os.path.abspath('.') >'/Users/michael'
创建一个目录
os.mkdir('/Users/michael/testdir')
删除一个目录
os.rmdir('/Users/michael/testdir')
把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数
os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
序列化
把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
Python提供了pickle模块来实现序列化。
这个就是把这个字典d给写进磁盘了。这里用的是pickle.dumps(),这个加了s就是只把这个数据给序列化成一个字节类型数据,能够写入文件里,(但是还没写,因为没有指定哪个文件)。所以还要另一个方法,pickle.dump(),比上面多一个s的多一个功能,就是还可以传入一个文件对象,然后把字节型数据给写入传入的文件里。:
f = open('dump.txt', 'wb')#随便一个文件,以二进制可写的形式读入
pickle.dump(d, f)
f.close()
反序列化
当我们要把对象从磁盘读到内存时(就是上面的操作反过来),可以直接用pickle.load()方法从一个file-like Object(这个obj就是可以用open()和read()方法的,StringIO和BytesIO也同样是这样)中直接反序列化出对象。我们打开另一个Python命令行来反序列化刚才保存的对象:
f = open('dump.txt', 'rb')
d = pickle.load(f)
f.close()
#这样d就是之前的那个字典了
JSON(重点)
要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如手机上都可以查看的XML。
但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。
所以来学习JSON
Python内置的有JSON转换的库
import json
d = dict(name="li",age=20,gender="男")
json.dumps(d)
把字典转换成JOSN字符串,其实就是这样的"{"age": 20, "score": 88, "name": "Bob"}"这就是转换的JSON字符串。
这样就是把这个字典d给转换成一个JSON类型(其实也是个字符串)的数据了,跟上面的dump()用法一样,没有加s,就是写进file-like Object里(就是写进一个文件里)。同样反序列化也是跟上面一样用的load和loads(),load()这个传入这个file-like Obj,反正就是load(),少一个s的重要些。
总之就记得dump()是转成JSON,load()是转回来。(dump(去),load(回))
也同样,最好是搭配with语句,省的忘了写close:
# 写成 JSON 数据 “去”
with open('data.json', 'w') as f:
json.dump(data, f)
# 读取数据 “回”
with open('data.json', 'r') as f:
data = json.load(f)
JOSN进阶
如果我们要传输自己定义的类的对象,首先得序列化,但是下面这样会明显报错,这是因为默认情况下,dumps()方法不知道如何将Student实例变为一个JSON字符串对象。就像默认知道怎么把字典对象转化成JSON字符串,但是对于一个新的Student是不知道的。
所以在dumps()里面可以有很多参数,这些参数以后会接触到的。
有个参数defalut就是把任意一个对象变成一个可序列为JSON的对象,需要写一个函数,这个函数把实例化的对象传入这函数,defalut参数就会把这个函数的返回值给序列里化。
所以上面的代码可以加一个参数,就能使用了。其中函数studentTodict就是把传入的类对象a给返回字典类型的对象,然后就能转成JSON字符串了。
上面是把对象给转换成JSON字符串,还有把JSON给转回对象,里面也需要写个函数,也需要load()里面的参数,具体在文档里。
剩下还有很多参数,需要慢慢再去学。
标签:__,进阶,Python,self,对象,实例,方法,函数 From: https://www.cnblogs.com/huanc/p/17441378.html