Day 3 面向对象进阶

3/100

slots

Python是动态语言,我们可以随时为对象绑定新的属性和方法。但是如果我们想限制类型的对象所能绑定的属性,则需要用到__slots__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Person(object):

__slots__ = ('_name', '_age', '_gender')

def __init__(self, name, age):
self._name = name
self._age = age

@property
def name(self):
return self._name

@property
def age(self):
return self._age

@age.setter
def age(self, age):
self._age = age

def play(self):
if self._age <= 16:
print('play game')
else:
print('work')


def main():
person = Person('Alice', 20)
person.play()
person._gender = 'male'
# Error
# person._family = True

if __name__ == '__main__':
main()

特殊方法

  1. 静态方法使用 @staticmethod 修饰
  2. 类方法使用 @classmethod 来修饰, 类方法的第一个参数必须是cls,表示当前类的对象

Day2 面向对象相关

2/100

定义类

python使用关键字class来定义类,在类名后的括号里写父类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student(object):

# 相当于Java的构造体,类的field可以直接赋值,不用事先定义
def __init__(self, name, age):
self.name = name
self.age = age

# 参数self相当于Java中的this,在调用方法的时候不需要指定,单如果没有这个参数就无法使用类的field
def study(self, course):
print(f'studing: {course}')

def watch_movie(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
# 这是原作者 [jackfrued] 的爱好
print('%s正在观看岛国爱情大电影.' % self.name)

可见性

python并没有复杂而严格的可见性设置,虽然可以通过在方法和变量前加两个下划线来设置私有性,但这其实只是改变了访问规则而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Test:

def __init__(self, foo):
self.__foo = foo

def __bar(self):
print(self.__foo)
print('__bar')


def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)

# 在前面添加 _Test 就可以访问了
test._Test__bar()
print(test._Test__foo)


if __name__ == "__main__":
main()

一般使用单下划线来表明方法或变量是受保护的,但这也只是一种隐含的意思而已。

@property装饰器
可以使用@property来包装属性,这样属性将会被封装起来,使用@property装饰的方法可以作为getter来使用,想要使用setter则需要@xxx.setter来修饰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Person(object):

def __init__(self, name, age):
self._name = name¬
self._age = age¬

# 如果没有这个修饰,下面的person.name = 'Bob' 就不会报错
@property
def name(self):
return self._name


@property
def age(self):
return self._age


@age.setter
def age(self, age):
self._age = age


def play(self):
if self.age <= 16:
print('plage game')
else
print('work')


def main():
person = Person('Alcie', 12)
person.play()
person.age = 22
person.play()
# AttributeError: can't set attribute
person.name = 'Bob'


if __name__ == '__main__':
main()

Day1 Python 基础

1/100

【1】基本元素

常见数据类型

  1. 整形 python3开始整数不区分int和long,支持二,八,十六进制。
  2. 浮点型 可以理解成小数
  3. 字符串 单引号 双引号都可以,三个引号输入多行内容
  4. 布尔型 True False 开头大写

变量名使用_连接 my_var_name
_name_ 单下划线开头为受保护的实例属性
__name__ 双下划线开头的为私有属性

使用 type() 来确认类型,使用下面的方法来转换类型:

1
2
3
4
5
int() #转换成整数 
float() #转换成浮点数
str() #转换成字符串,可以是字符编码
chr() #字符串编码(整数)转换成字符串
ord() #活的字符串对应的编码(整数)

【2】逻辑语句

运算符

除了常见的运算符外,python常用的还有:

1
2
3
4
5
is 
is not

in
not in

if分支

python的if分支跟函数,类一样都是使用缩进来表示代码的层次结构。

1
2
3
4
5
6
if name == 'a':
print('a')
elif name == 'b':
print('b')
else:
print('c')

循环结构

for-in
python一般使用for name in collection的方式来写循环语句

1
2
3
4
sum = 0
for x in range(100):
sum += x
print(sum)

【3】函数

函数定义和参数

定义函数的关键字是 def

1
2
def foo(a):
print(a)

python中函数的参数可以设置默认值,所以使用的时候可以忽略部分参数或者直接指定某些参数,所以python的函数不需要像Java一样重载

1
2
def add(a=0, b=0, c=0):
return a + b + c

还可以定义可变参数来接收无法确定个数的参数:

1
2
3
4
5
6
def add(*args):
total = 0
for val in args:
total += val
return total

模块

python一个文件就算一个模块,通过import来导入指定模块的内容。

1
2
3
# module1.py
def foo():
print('hello, world')
1
2
3
# module2.py
def foo():
print('hello, world')
1
2
3
4
5
6
7
# test
import module1 as m1
import module2 as m2

m1.foo()
m2.foo()

还可以直接从模块导入具体的函数

1
2
3
4
5
6
from module1 import foo
from module2 import foo

# 后导入的foo会覆盖之前导入的foo,所以这里使用的是module2的foo
foo()

name == ‘main

模块中如果需要写入可执行语句,一般需要包装在 __name__ == '__main__' 中,这样除非直接运行该模块,这些可执行语句是不会被执行的

1
2
3
4
5
6
def foo():
print('hello, world')


if __name__ == '__main__':
foo()

__name__ 是python的一个隐藏属性:模块名。 被python解释器直接执行的模块名字叫 __main__

PlantUML(一) 类图

ER图笔记(一)

ER图 Entity Relationship Diagram

Cardinality

在线的端点通过下列符号来指定数量关系

1
2
3
--  1
--|| 1 && only 1
--o| 0 or 1
1
2
3
A-l-a
B-||b
C-o|c
1
2
3
--{  multi
--|{ 1 or more
--o{ 0 or more
1
2
3
D-{d
E-|{e
F-o{f

Direction

在线的中央插入下列关键字来指定方向

1
2
3
4
up, u
down, d, do
left, l, le
right, r, ri
1
2
A-u->Up
B-d->Down
1
2
C-l->Left
D-r->Right

package style

可以使用自带的模版来定义每个包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package foo1 <<Node>> {
class Class1
}
package foo2 <<Rectangle>> {
class Class2
}
package foo3 <<Folder>> {
class Class3
}
package foo4 <<Frame>> {
class Class4
}
package foo5 <<Cloud>> {
class Class5
}
package foo6 <<Database>> {
class Class6
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package foo1 <<Node>> {
class Class1
}
package foo2 <<Rectangle>> {
class Class2
}
package foo3 <<Folder>> {
class Class3
}
package foo4 <<Frame>> {
class Class4
}
package foo5 <<Cloud>> {
class Class5
}
package foo6 <<Database>> {
class Class6
}

实用Optional时需要注意的二三事

使用了Optional为什么还发生NullPointerException

对于空值应该怎么设置

如何高效的实用Optional返回值

如何避免Optional相关的反模式

怎么把Optional用的更专业

Reference

The Java Optianl class (12 recipes)

The Java Optianl class (11 recipes)

关于Enums的小问题

原文链接

首先给出以下Enum:

1
2
3
4
5
enum Size{
SMALL,
MEDIUM,
LARGE
}

问下面的代码执行的结果是:

1
2
3
4
5
6
public static void main(String[] args) {
final var size = Size.SMALL; //line n1
switch (size) { //line n2
case SMALL: {System.out.println(size);} //line n3
}
}
  1. n1处编译失败,初始化的写法错误
  2. n2处编译失败,没对应把所有Size包含在case内
  3. n3处编译失败,case后面应该是完整的Size.SMALL
  4. 输出 SMALL
  5. 输出 Size@3cb5cdba

分析

选项1是错的,局部变量可以使用final来修饰,从Java 10开始,可以使用关键字var来声明局部变量,声明的变量类型由初始化的类型决定。
选项2也是错的,switch-case从来都不需要面对所有情况写case。
选项3也是错的,当switch的判断条件是enum类型时,case里不需要写类名。
选项4是对的,enum类型默认重写了toString方法,输出name的值。
选项5是错的,上面说了,enum的toString输出的是name,而不是HashCode.

使用Java读写CSV文件

最近针对一个Issue,需要给excel文件的读写功能增加CSV的支持,本来以为使用类似的方法可以直接写到DTO里面,结果一查,Java好像对CSV的支持还是最原始最基本的写法(并非贬义,可能是容易了反而不需要什么工具来读写CSV)。

总结成一句话就是,读写CSV就相当于读写一个纯字符串文件。

读取CSV

读取CSV文件大致分为以下几步:

  1. 使用FileInputStream读取csv文件
  2. 使用BufferedReader来对输入流进行更高效的处理
  3. 将每一行的数据作为字符串读取,使用split(",")分割
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 字符编码 设置下没坏处
Charset charset = StandardCharsets.UTF_8;
// 缓存大小 提高读取效率
int bufferSize = 5 * 1024 * 1024;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("input/sample.csv"), charset), bufferSize)) {
String line;
List<String[]> data = new ArrayList<>();
// 这里假设文件不是很大,不然这么些内存是不够用的
while (Objects.nonNull(line = reader.readLine())) {
data.add(line.split(","));
}

// 然后 想怎么用就怎么用了
} catch (IOException e) {
// IOException 就囊括了读取文件可能发生的全部意外
e.printStackTrace();
}

写CSV

跟上面读取CSV的操作正好反过来,写CSV文件分为以下几步:

  1. 使用FileOutputStream输出文件
  2. 将要输出的一行数据使用指定的符号(csv一般是逗号)连接成字符串
  3. 输出到文件,使用BufferedWriter.write()的话记得换行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 文件路径和文件名
String file = "output/output.csv";
// 指定字符编码
Charset charset = StandardCharsets.UTF_8;
// 指定缓存
int bufferSize = 5 * 1024 * 1024;
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(file), charset), bufferSize
)) {
for (String[] datum : data) {
writer.write(String.join(",", datum));
// 换行
writer.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}

二进制大对象和字符大对象

这都是啥啥啥

CLOB 和 BLOB

CLOB 是可变长度的字符(char)大(long)对象(Object),用于基于字符的数据,以字节Byte或者标准八字节OCTET为单位,最大长度是2G(2,147,483,647)

BLOB 是可变长度的二进制(Binary)大(long)对象(Object),用于保存语音,混合媒体等非传统数据,以字节Byte为单位,最大长度也是2G(2,147,483,647)

OCTET 和 Byte

我们都知道计算机中一个0/1表示一个比特bit,八个比特表示一个字节byte。但是在实际应用中,byte可以表示4-10比特的不同长度,为了统一标准,将严格意义上的8bits也称为OCTET

Python中类的使用

类就是Class

类的基本

  1. 类和实体 我们可以简单的将实体(instance)理解成具体的实物对象,把类看作是生产实体的蓝图。比如我们定义一个类Car,之后就可以在任何地方反复调用这个蓝图来生产Car的实体
  2. 方法 在Python中我们可以像写脚本一样随便写个方法就执行,但是这可能带来诸多麻烦。比如我们为汽车的启动和停止写了两个方法run()stop(),为了确定让哪辆车启动哪辆车停下,我们需要把对象的车多为参数传递进方法,是不是有一种拿遥控器开车还要每次都设置开的哪辆车的感觉? 当我们需要在不同的地方使用这两个方法时,就要重新写一遍。如果我们把run()stop()写到类Car里面,就可以通过每个Car的实体,直接调用启动和停止的方法,方法属于车的实体,不同实体间的启动停止互不影响,这就有开车那味儿了。
  3. 成员变量 类的成员变量分为两种(此处为Java思维):静态变量和实例变量。 所谓静态变量,我们可以理解成刻在蓝图里的,比如我们给Car定义一个静态变量轮子数 = 4,这样我们生成的每个车都可以使用这个变量,甚至不生成实例的时候,也可以使用Car.轮子数, 只要看一下蓝图,就知道这个变量值。相比之下,实例变量就是指从属于实例的变量,比如排气量 长度 宽度,如果要调用这些变量,直接通过Car.排气量是不行的,因为图纸上可没具体写这变量是多少,需要先生成一个实体my_car = Car(),然后给my_car设置并使用

Python中类的使用

类的定义

Python中定义类使用的是关键字class,类的范围通过统一的缩进来管理

1
2
3
4
5
6
7
8
9
10
class Car:
id = 12345


def __init__(self, id):
self.id = id


def run(self):
print(f'run {self.id}!')

类的内部使用关键字def来定义方法,方法必须至少有一个参数selfself的含义相当于Java的this,指的是类的实例,可以通过self来访问实例变量和方法。

__init__是构造体,当我们使用类名来生成实例的时候,会根据参数来调用对应的构造体生成实例,最基本的写法就是直接通过构造体给实例变量初始化,比如使用上面代码里得构造体:Car(54321)

Python中使用__双下划线来定义私有变量和私有方法,私有的元素无法在外部通过实例名或者类名访问到

就像Java中所有的类都是Object的字类,都继承了一些基本的方法一样,Python中也有一些类的专有方法, 且可以重写:

  • init : 构造函数,在生成对象时调用
  • del : 析构函数,释放对象时使用
  • repr : 打印,转换
  • setitem : 按照索引赋值
  • getitem: 按照索引获取值
  • len: 获得长度
  • cmp: 比较运算
  • call: 函数调用
  • add: 加运算
  • sub: 减运算
  • mul: 乘运算
  • truediv: 除运算
  • mod: 求余运算
  • pow: 乘方

后记

跟同事聊了聊为什么要使用类,想起了一个很重要的点:内存管理。
如果我们不适用类来管理方法和变量的话,程序运行过程中,定义的变量和方法就会一直存在,占用内存空间;但是当我们使用了类之后,则可以在需要的时候声明类的实例来调用内存,当一个实例不再被使用的时候,又可以通过垃圾回收自动收回内存(垃圾回收机制不同语言有不同的做法)。这样我们的程序才可以根据需求灵活地使用资源。
当然,方法和变量的管理更方便什么的,也算使用类的好处

Git Prune命令

git prune 简单地说就是一个清扫(housekeeping)的命令。

使用git查看分支,可以看到分支有三个重要分类:本地分支,本地-远程分支的参照,远程分支
比如我们在github上merge了一个PR,顺手删除了一个分支,这时在本地的电脑上本删除的分支对应的本地分支,远程分支,远程本地的参照关系都还存在,如果我们使用git branch -D name删除该分支, 会发现只删除了本地的分支,所参照的远程分支origin/name依然存在,清理这种残留垃圾的命令的时候,就是用到了prune。

git remove prunegit fetch --prune

git remove prune 清理所有已经删掉的远程分支参照,但是不会删除本地分支

git fetch --prune 则是清理完远程的无用分支之后,再把最近状态fetch到本地