Scala 自定义实体

Class

  • Scala是纯面向对象语言,所有变量都是对象,所有操作都是 方法调用
  • class定义类,包括类名、主构造参数,其中成员可能包括

    • 普通类可用new创建类实例

成员说明

  • 常/变量:声明时必须初始化,否则需定义为抽象类, 相应成员也被称为抽象成员常/变量
    • 可以使用占位符_初始化
      • AnyVal子类:0
      • AnyRef子类:null
  • 抽象类型:type声明
  • 方法、函数
  • 内部类、单例对象:内部类、单例对象绑定至外部对象
    • 内部类是路径依赖类型
    • 不同类实例中的单例对象不同
  • Java:仍然会为匿名类生成相应字节码文件
  • Java:public类成员常/变量在编译后自动生成getter、 setter(仅变量)方法
    • 即使是公有成员也会被编译为private
    • 对其访问实际上是通过setter、getter方法(可显式调用)

内部类型投影

类型投影:Outter#Inner类型InnerOutter类型作为前缀, Outter不同实例中Inner均为此类型子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Outter{
class Inner
def test(i: Outter#Inner) = i
}

val o1 = new Outter
val o2 = new Outter

// 二者为不同类型
val i1 = new o1.Inner
val i2 = new o2.Inner

// 类型投影父类作为参数类型
o1.test(i2)
o2.test(i1)

单例类型

单例类型:所有对象都对应的类型,任意对象对应单例类型不同

1
2
3
4
5
6
import scla.reflect.runtime.universe.typeOf
class Dog
val d1 = new Dog
val d2 = new Dog
typeOf[d1.type] == typeOf[d2.type] // `false`
val d3: d1.type = d1 // 两者单例类型也不同
链式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Pet{
var name: String = _
var age: Float = _
// 返回类型单例类型,保证类型不改变
def setName(name: String): this.type = {
this.name = name
this
}
def setAge(age: Float): this.age = {
this.age = age
this
}
}
class Dog extends Pet{
var weight: Float = _
def setWeight(weight: Float): this.type = {
this.weight = weight
this
}
}

// 若没有设置返回类型为单例类型`this.type`,`setAge`之后
// 将返回`Pet`类型,没有`setWeight`方法,报错
val d = new Dog().setAge(2).setWeight(2.3).setName("Tom")

Constructor

1
2
3
4
5
6
7
8
// 带默认参数的、私有主构造方法
class Person private(var name: String, var age: Int=10){
// 辅助构造方法
def this(age: Int) = {
// 必须调用已定义构造方法
this("", age)
}
}
  • 主构造方法:类签名中参数列表、类体

    • 其参数(类签名中)被自动注册为类成员变、常量

      • 其可变性同样由varval声明
      • 其访问控制类似类似类体中成员访问控制
      • 但缺省继承父类可见性,否则为private[this] val
      • 参数中私有成员变、常量没有被使用,则不会被生成
      • case class主构造方法中缺省为public val
    • 其函数体为整个类体,创建对象时被执行

    • 可类似普通方法

      • 提供默认值来拥有可选参数
      • 将主构造方法声明为私有
  • 辅助构造方法:this方法

    • 辅助构造方法必须以先前已定义的其他辅助构造方法、或 主构造方法的调用开始
  • 主构造方法体现于其中参数被自动注册为类成员

类继承

1
2
3
4
// `extends`指定类继承
class Student(name: String, age: Int, var studentNo: Int)
extends Person(name: String, age: Int){
}
  • 必须给出父类的构造方法(可以是辅助构造方法)

    • 类似强制性C++中初始化列表
  • 构造函数按以下规则、顺序执行

    • 父类、特质构造函数
    • 混入特质:多个特质按从左至右
    • 当前类构造函数
  • 可用override重写从父类继承的成员方法、常/变量

    • 重写父类抽象成员(未初始化)可省略override
    • java中方法都可以视为C++中虚函数,具有多态特性

提前定义、懒加载

  • 由于构造方法的执行顺序问题,定义于trait中的抽象成员 常/变量可能会在初始化化之前被使用
  • 可以考虑使用提前定义、懒加载避免
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.PrintWriter
trait FileLogger{
// 抽象变量
val fileName: String
// 抽象变量被使用
val fileOutput = new PrintWriter(fileName: String)
// `lazy`懒加载,延迟变量创建
lazy val fileOutput = new PrintWriter(fileName: String)
}
class Person
class Student extends Person with FileLogger{
val fileName = "file.log"
}
val s = new {
// 提前定义
override val fileName = "file.log"
} with Student

访问控制

  • 缺省:Scala没有public关键字,缺省即为public
  • protected[<X>]:在X范围内的类、子类中可见
  • private[<X>]:在X范围内类可见
  • X可为包、类、单例对象等作用域

    • 缺省为包含当前实体的上层包、类、单例对象,即类似Java 中对应关键字
  • 特别的private[this]表示对象私有

    • 只有对象本身能够访问该成员
    • 类、伴生对象中也不能访问

      1
      2
      3
      4
      5
      6
      7
      8
      class Person{
      private[this] age = 12
      def visit() = {
      val p = new Person
      // 报错,`age`不是`Person`成员
      println(p.age)
      }
      }

特殊类

abstract class

抽象类:不能被实例化的类

  • 抽象类中可存在抽象成员常/变量、方法,需在子类中被具体化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义抽象类
abstract class Person(var name: String, var age: Int){
// 抽象成员常量
val gender: String
// 抽象方法
def print: Unit
}

// 抽象类、泛型、抽象类型常用于匿名类
val p = new Person("Tom", 8){
override gender = "male"
// 覆盖抽象成员可以省略`override`
def print: Unit = println("hello")
}

case class

样例类

  • 和普通类相比具有以下默认实现

    • apply:工厂方法负责对象创建,无需new实例化样例类
    • unapply:解构方法负责对象解构,方便模式匹配
    • equals:案例类按值比较(而不是按引用比较)
    • toString
    • hashCode
    • copy:创建案例类实例的浅拷贝,可以指定部分构造参数 修改部分值
  • 类主构造函数参数缺省为val,即参数缺省不可变公开

  • 用途

    • 方便模式匹配
  • 样例类一般实例化为不可变对象,不推荐实例化为可变
1
2
3
4
case class Message(sender: String, recipient: String, body: String)
val message4 = Message("A", "B", "message")
// 指定部分构造参数的拷贝
val message5 = message.copy(sender=message4.recipient, recipient="C")

Value Class

Value Class:通过继承AnyVal以避免运行时对象分配机制

1
class Wrapper(val underlying: Int) extends AnyVal
  • 特点

    • 参数:仅有被用作运行时底层表示的公有val参数
    • 成员:可包含def自定义方法,但不能定义额外valvar、内嵌traitclassobject
    • 继承:只能继承universal trait,自身不能再被继承
    • 编译期类型为自定义类型,运行时类型为val参数类型
    • 调用universal trait中方法会带来对象分配开销
  • 用途

    • 和隐式类联合获得免分配扩展方法

      1
      2
      3
      4
      // 标准库中`RichInt`定义
      implicit class RichInt(val self Int) extends AnyVal{
      def toHexString: String = java.lang.Integer.toHexString(self)
      }
    • 不增加运行时开销同时保证数据类型安全

      1
      2
      3
      class Meter(val value: Double) extends AnyVal{
      def +(m: Meter): Meter = new Meter(value + m.value)
      }
  • JVM不支持value class,Scala有时需实例化value class
    • value class作为其他类型使用
    • value class被赋值给数组
    • 执行运行时类型测试,如:模式匹配

Object

单例对象:有且只有一个实例的特殊类,其中方法可以直接访问, 无需实例化

  • 单例对象由自身定义,可以视为其自身类的单例

    • 对象定义在顶层(没有包含在其他类中时),单例对象只有 一个实例
      • 全局唯一
      • 具有稳定路径,可以被import语句导入
    • 对象定义在类、方法中时,单例对象表现类似惰性变量
      • 不同实例中单例对象不同,依赖于包装其的实例
      • 单例对象和普通类成员同样是路径相关的
  • 单例对象是延迟创建的,在第一次使用时被创建

    • object定义
    • 可以通过引用其名称访问对象
  • 单例对象被编译为单例模式、静态类成员实现

Companion Object/Class

  • 伴生对象:和某个类共享名称的单例对象
  • 伴生类:和某个单例对象共享名称的类
  • 伴生类和伴生对象之间可以互相访问私有成员
    • 伴生类、伴生对象必须定义在同一个源文件中
  • 用途:在伴生对象中定义在伴生类中不依赖于实例化对象而存在 的成员变量、方法
    • 工厂方法
    • 公共变量
  • Java中static成员对应于伴生对象的普通成员
  • 静态转发:在Java中调用伴生对象时,其中成员被定义为伴生类 中static成员(当没有定义伴生类时)

apply

apply方法:像构造器接受参数、创建实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import scala.util.Random

object CustomerID{
def apply(name: String) = s"$name--${Random.nextLong}"
def unapply(customerID: String): Option[String] = {
val stringArray:[String] = customer.ID.split("--")
if (stringArray.tail.nonEmpty) Some(StringArray.head)
else None
}
}

val customer1ID = CustomerId("Tom")
customer1ID match {
case CustomerID(name) => println(name)
case _ => println("could not extract a CustomerID")
}
val CustomerID(name) = customer1ID
// 变量定义中可以使用模式引入变量名
// 此处即使用提取器初始化变量,使用`unapply`方法生成值
val name = CustomerID.unapply(customer2ID).get
  • unapplyunapplySeq参见cs_java/scala/entity_components

应用程序对象

应用程序对象:实现有main(args: Array[String])方法的对象

  • main(args: Array[String])方法必须定义在单例对象中

  • 实际中可以通过extends App方式更简洁定义应用程序对象 ,trait App中同样是使用main函数作为程序入口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    trait App{
    def main(args: Array[String]) = {
    this._args = args
    for(proc <- initCode) proc()
    if (util.Propertie.propIsSet("scala.time")){
    val total = currentTime - executionStart
    Console.println("[total " + total + "ms")
    }
    }
    }
  • 包中可以包含多个应用程序对象,但可指定主类以确定包程序 入口

case object

样例对象:基本同case class

  • 对于不需要定义成员的域的case class子类,可以定义为 case object以提升程序速度
    • case object可以直接使用,case class需先创建对象
    • case object只生成一个字节码文件,case class生成 两个
    • case object中不会自动生成applyunapply方法

用途示例

创建功能性方法

1
2
3
4
5
6
7
8
9
10
11
package logging
object Logger{
def info(message: String): Unit = println(s"IFNO: $message")
}

// `import`语句要求被导入标识具有“稳定路径”
// 顶级单例对象全局唯一,具有稳定路径
import logging.Logger.info
class Test{
info("created projects")
}

Trait

特质:包含某些字段、方法的类型

  • 特点

    • 特质的成员方法、常/变量可以是具体、或抽象
    • 特质不能被实例化,因而也没有参数
    • 特质可以extends自类
  • 用途:在类之间共享程序接口、字段,尤其是作为泛型类型和 抽象方法

    • extend继承特质
    • with混入可以组合多个特质
    • override覆盖/实现特质默认实现
  • 子类型:需要特质的地方都可以使用特质的子类型替换

  • Java:trait被编译为interface,包含具体方法、常/变量 还会生成相应抽象类

Mixin

混入:特质被用于组合类

  • 类只能有一个父类,但是可以有多个混入

    • 混入和父类可能有相同父类
    • 通过Mixin trait组合类实现多继承
  • 多继承导致歧义时,使用最优深度优先确定相应方法

复合类型

复合类型:多个类型的交集,指明对象类型是某几个类型的子类型

1
<traitA> with <traitB> with ... {refinement}

sealed trait/sealed class

密封特质/密封类:子类确定的特质、类

1
2
3
4
sealed trait ST

case class CC1(id: String) extends ST
case object CC2 extends ST
  • 密封类、特质只能在当前文件被继承
  • 适合模式匹配中使用,编译器知晓所有模式,可提示缺少模式

自类型

自类型:声明特质必须混入其他特质,尽管特质没有直接扩展 其他特质

  • 特质可以直接使用已声明自类型的特质中成员

细分大类特质

1
2
3
4
5
6
7
8
9
10
11
12
13
trait User{
def username: String
}
trait Tweeter{
// 声明自类型,表明需要混入`User`特质
this: User =>
def tweet(tweetText: String) =
println(s"$username: $tweetText")
}
class VerifiedTweeter(val username_: String)
extends Tweeter with User{
def username = s"real $username_"
}

定义this别名

1
2
3
4
5
6
7
8
9
10
class OuterClass{
// 定义`this`别名,下面二者等价
outer =>
// outer: OuterClass =>
val v1 = "here"
class Innerclass {
// 方便在内部类中使用
println(outer.v1)
}
}

Universal Trait

Universal Trait:继承自Any、只有def成员、不作任何 初始化的trait

  • 继承自universal trait的value class同时继承该trait方法, 但是调用其会带来对象分配开销

泛型

  • 泛型类、泛型trait

    • 泛型类使用方括号[]接受类型参数
  • 泛型方法:按类型、值进行参数化,语法和泛型类类似

    • 类型参数在方括号[]中、值参数在圆括号()
    • 调用方法时,不总是需要显式提供类型参数,编译器 通常可以根据上下文、值参数类型推断
  • 泛型类型的父子类型关系不可传导

    • 可通过类型参数注释机制控制
    • 或使用类型通配符(存在类型)“构建统一父类”

存在类型/类型通配符

存在类型:<type>[T, U,...] forSome {type T; type U;...} ,可以视为所有<type>[]类型的父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可以使用通配符`_`简化语法
// def printAll(x: Array[T] forSome {type T})
def printAll(x: Array[_]) = {
for (i <- x) {
print(i + " ")
}
println()
}

val a = Map(1 -> 2, 3 -> 3)
match a {
// 类型通配符语法可以用于模式匹配,但存在类型不可
case m: Map[_, _] => println(m)
}
  • 省略给方法添加泛型参数

类型边界约束

  • T <: A/T >: B:类型T应该是A的子类/B的父类

    • 描述is a关系
  • T <% STS的子类型、或能经过隐式转换为S子类型

    • 描述can be seen as关系
  • T : E:作用域中存在类型E[T]的隐式值

型变

型变:复杂类型的子类型关系与其组件类型的子类型关系的相关性

  • 型变允许在复杂类型中建立直观连接,利用重用类抽象
1
2
3
4
5
abstract class Animal{
def name: String
}
case class Cat(name: String) extends Animal
case class Cat(name: String) extends Animal

Covariant

协变:+A使得泛型类型参数A成为协变

  • 类型List[+A]A协变意味着:若AB的子类型,则 List[A]List[B]的子类型

  • 使得可以使用泛型创建有效、直观的子类型关系

1
2
3
4
5
6
7
8
9
10
11
12
def ConvarienceTest extends App{
def printAnimalNames(animals: List[Animal]): Unit = {
animals.foreach{ animal =>
println(animal.name)
}
}

val cats: List[Cat] = List(Cat("Winskers"), Cat("Tom"))
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
printAnimalNames(cats)
printAnimalNames(dogs)
}

Contravariant

逆变:-A使得泛型类型参数A成为逆变

  • 同协变相反:若AB的子类型,则Writer[B]Writer[A]的子类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Printer[-A] {
def print(value: A): Unit
}
class AnimalPrinter extends Printer[Animal] {
def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name)
}
class CatPrinter extends Printer[Cat]{
def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}

val myCat: Cat = Cat("Boots")
def printMyCat(printer: Printer[Cat]): Unit = {
printer.print(myCat)
}

val catPrinter: Printer[Cat] = new CatPrinter
val animalPrinter: Printer[Animal] = new AnimalPrinter

printMyCat(catPrinter)
printMyCat(animalPrinter)
  • 协变泛型

    • 子类是父类的特化、父类是子类的抽象,子类实例总可以 替代父类实例
    • 协变泛型作为成员
      • 适合位于表示实体的类型中,方便泛化、组织成员
      • 作为[部分]输出[成员]
    • 注意:可变的协变泛型变量不安全
  • 逆变泛型

    • 子类方法是父类方法特化、父类方法是子类方法抽象,子类 方法总可以替代父类方法
    • 逆变泛型提供行为、特征
      • 适合位于表示行为的类型中,方便统一行为
      • 仅作为输入
  • 协变泛型、逆变泛型互补

    • 对包含协变泛型的某类型的某方法,总可以将该方法扩展 为包含相应逆变泛型的类
1
2
trait Function[-T, +R]
// 具有一个参数的函数,`T`参数类型、`R`返回类型

Invariant

不变:默认情况下,Scala中泛型类是不变的

  • 可变的协变泛型变量不安全
1
2
3
4
5
6
7
8
9
10
11
12
13
class Container[A](value: A){
private var _value: A = value
def getValue: A = _value
def setValue(value: A): Unit = {
_value = value
}
}

// 若`A`协变
val catContainer: Container[cat] = new Container(Cat("Felix"))
val animalContainer: Container[Animal] = catCont
animalContainer.setValue(Dog("Spot"))
val cat: Cat = catContainer.getValue

Type Erasure

类型擦除:编译后删除所有泛型的类型信息

  • 导致运行时无法区分泛型的具体扩展,均视为相同类型
  • 类型擦除无法避免,但可以通过一些利用反射的类型标签解决

    • ClassTag
    • TypeTag
    • WeakTypeTag
    • 参见cs_java/scala/stdlib
  • Java:Java初始不支持泛型,JVM不接触泛型
    • 为保持向后兼容,泛型中类型参数被替换为Object、或 类型上限
    • JVM执行时,不知道泛型类参数化的实际类
  • Scala、Java编译器执行过程都执行类型擦除

抽象类型

抽象类型:由具体实现决定实际类型

  • 特质、抽象类均可包括抽象类型

    1
    2
    3
    4
    trait Buffer{
    type T
    val element: T
    }
  • 抽象类型可以添加类型边界

    1
    2
    3
    4
    5
    abstract class SeqBuffer extends Buffer{
    type U
    type T <: Seq[U]
    def length = element.length
    }
  • 含有抽象类型的特质、类经常和匿名类的初始化同时使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    abstract class IntSeqBuffer extends SeqBuffer{
    type U = Int
    }
    def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
    new IntSeqBuffer{
    type T = List[U]
    val element = List(elem1, elem2)
    }
    // 所有泛型参数给定后,得到可以实例化的匿名类
  • 抽象类型、类的泛型参数大部分可以相互转换,但以下情况无法 使用泛型参数替代抽象类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    abstract class Buffer[+T]{
    val element: T
    }
    abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T]{
    def length = element.length
    }
    def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
    new SeqBuffer[Int, List[Int]]{
    val element = List(e1, e2)
    }

包和导入

package

  • Scala使用包创建命名空间,允许创建模块化程序
  • 包命名方式

    • 惯例是将包命名为与包含Scala文件目录名相同,但Scala 未对文件布局作任何限制
    • 包名称应该全部为小写
  • 包声明方式:同一个包可以定义在多个文件中

    • 括号嵌套:使用大括号包括包内容
      • 允许包嵌套,可以定义多个包内容
      • 提供范围、封装的更好控制
    • 文件顶部标记:在Scala文件头部声明一个、多个包名称
      • 各包名按出现顺序逐层嵌套
      • 只能定义一个包的内容
  • 包作用域:相较于Java更加前后一致

    • 和其他作用域一样支持嵌套
    • 可以直接访问上层作用域中名称
      • 行为类似Python作用域,优先级高于全局空间中导入
      • 即使名称在不同文件中
  • 包冲突方案:Java中包名总是绝对的,Scala中包名是相对的, 而包代码可能分散在多个文件中,导致冲突

    • 绝对包名定位包__root__.<full_package_path>
      • 导入时:import __root__.<package>
      • 使用时:val v = new __root__.<class>
    • 串联式包语句隐藏包:包名为路径分段串,其中非结尾 包被隐藏
  • Scala文件顶部一定要package声明所属包,否则好像不会默认 导入scala.等包,出现非常奇怪的错误,如:没有该方法

import

  • import语句用于导入其他包中成员,相同包中成员不需要 import语句

    1
    2
    3
    4
    5
    6
    import users._
    import users.{User, UserPreferences}
    // 重命名
    import users.{UserPreferences => UPrefs}
    // 导入除`HashMap`以外其他类型
    import java.utils.{HashMap => _, _}
  • 若存在命名冲突、且需要从项目根目录导入,可以使用 __root__表示从根目录开始

  • scalajava.langobject Predef默认导入
  • 相较于Java,Scala允许在任何地方使用导入语句

包对象

包对象:作为在整个包中方便共享内容、使用的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// in file gardening/fruits/Fruit.scala
package gardening.fruits

case class Fruit(name: String, color: String)
object Apple extends Fruit("Appple", "green")
object Plum extends Fruit("Plum", "blue")
object Banana extends Fruit("Banana", "yellow")

// in file gardening/fruits/package.scala
package gardening
package object fruits{
val planted = List(Apple, Plum, Banana)
def showFruit(fruit: Fruit): Unit = {
println(s"${fruit.name}s are ${fruit.color}")
}
}
  • 包对象中任何定义都认为时包自身的成员,其中可以定义 包作用域级任何内容

    • 类型别名
    • 隐式转换
  • 包对象和其他对象类似,每个包都允许有一个包对象

    • 可以继承Scala的类、特质
    • 注意:包对象中不能进行方法重载
  • 按照惯例,包对象代码通常放在文件package.scala

Annotations

注解:关联元信息、定义

  • 注解作用于其后的首个定义、声明
  • 定义、声明之前可以有多个注解,注解顺序不重要

确保编码正确性注解

  • 此类注解条件不满足时,会导致编译失败

@tailrec

@tailrec:确保方法尾递归

1
2
3
4
5
6
7
8
9
import scala.annotations.tailrec

def factorial(x: Int): Int = {
@tailrec
def factorialHelper(x: Int, accumulator: Int): Int ={
if (x == 1) accumulator
else factorialHelper(x - 1, accumulator * x)
}
}

影响代码生成注解

  • 此类注解会影响生成字节码

@inline

@inline:内联,在调用点插入被调用方法

  • 生成字节码更长,但可能运行更快
  • 不能确保方法内联,当且仅当满足某些生成代码大小的 启发式算法时,才会出发编译器执行此操作

@BeanProperty

@BeanProperty:为成员常/变量同时生成Java风格getter、setter 方法get<Var>()set<Var>()

  • 对成员变/常量,类编译后会自动生成Scala风格getter、 setter方法:<var>()<var>_$eq()

Java注解

  • Java注解有用户自定义元数据形式
    • 注解依赖于指定的name-value对来初始化其元素

@interface

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
// 定义跟踪某个类的来源的注解
@interface Source{
public String URL();
public String mail();
}

// Scala中注解应用类似构造函数调用
// 必须使用命名参数实例化Java注解
@Source(URL="http://coders.com",
mail="support@coders.com")
public class MyClass

// 若注解中只有单个[无默认值]元素、名称为`value`
// 则可以用类似构造函数的语法在Java、Scala中应用
@interface SourceURL{
public String value();
public String mail() defualt "";
}
@SourceURL("http://coders.com/")
public class MyClass
// 若需要给`mail`显式提供值,Java必须全部使用命名参数
@Source(URL="http://coders.com",
mail="support@coders.com")
public class MyClass
// Scala可以继续利用`value`特性
@SourceURL("http://coders.com/"
mail = "support@coders.com")
public class MyClass

数据模型--基本数据类型

对象、值、类型

对象:python中对数据的抽象

  • python中所有数据都是由对象、对象间关系表示

    • 按冯诺依曼“存储程序计算机”,代码本身也是由对象表示

编号、类型、值

每个对象都有各自编号类型

  • 编号:可以视为对象在内存中地址,对象创建后不变

    • id()函数:获取代表对象编号的整形
    • is算符:比较对象编号判断是否为同一对象
  • 类型:决定对象支持的操作、可能取值

    • 类型会影响对象行为几乎所有方面,甚至对象编号重要性 也受到影响,如:对于会得到新值的运算
      • 不可变类型:可能返回同类型、同取值现有对象引用
        • a = b = 1ab可能指向相同对象1 (取决于具体实现)
      • 可变类型:不允许返回已存在对象
        • c=[];d=[]:会保证cd指向不同、单独 空列表(c=d=[]将同一对象赋给cd
    • 对象创建后保持不变
    • type:返回对象类型
    • CPython:相同整形值都引用同一个对象
  • 值:通过一些特征行为表征的抽象概念

    • 对象值在python中是抽象概念

      • 对象值没有规范的访问方法
      • 不要求具有特定的构建方式,如:值由其全部数据 属性组成
    • 对象值可变性由其类型决定

      • 可变的:值可以改变的对象
      • 不可变的:值(直接包含对象编号)不可改变的对象
    • 比较运算符实现了特定对象值概念,可以认为是 通过实现对象比较间接定义对象值
  • CPython:id(x)返回存放x的地址

对象销毁

对象不会被显式销毁(del仅是移除名称绑定)

  • 无法访问时可能被作为垃圾回收

    • 允许具体实现推迟垃圾回收或完全省略此机制
    • 实现垃圾回收是质量问题,只要可访问对象不会被回收 即可
    • 不要依赖不可访问对象的立即终结机制,应当总是显式 关闭外部资源引用
  • 以下情况下,正常应该被回收的对象可能继续存活

    • 使用实现的跟踪、调试功能
    • 通过try...except...语句捕捉异常
  • CPython:使用带有(可选)延迟检测循环链接垃圾的 引用计数方案
    • 对象不可访问时立即回收其中大部分,但不保证 回收包含循环引用的垃圾

标准类型层级结构

  • 以下是python内置类型的列表,扩展模块可以定义更多类型
  • 以下有些类型有特殊属性,这些特殊属性不应用作通常使用, 其定义在未来可能改变

None

NoneType:只有一种取值,None是具有此值的唯一对象

  • 通过内置名称None访问
  • 多数情况表示空值,如
    • 未显式指明返回值函数返回None
  • 逻辑值:假

NotImplemented

NotImplementedType:只有一种取值,NotImplemented是具有 此值的唯一对象

  • 通过内置名称NotImplemented访问
  • 数值、富比较方法在操作数没有该实现操作时应返回此值
    • 返回NotImplemented前,解释器会依据运算符尝试反射 方法、委托回退方法
  • 逻辑值:真

Ellipsis

ellipsis:只有一种取值,Ellipsis是具有此值的唯一对象

  • 通过字面值...、内置名称Ellipsis访问
  • 逻辑值:真

numbers.Number

number.Number:由数字字面值创建,被作为算法运算符、算数 内置函数返回结果

  • 不可变:一旦创建其值不再改变
  • 类似数学中数字,但也受限于计算机对数字的表示方法

numbers.Integral

numbers.Integral:表示数学中整数集合

  • int:整形,表示任意大小数字,仅受限于可用内存

    • 变换、掩码运算中以二进制表示
    • 负数以2的补码表示(类似符号位向左延伸补满空位)
  • bool:布尔型,表示逻辑值真、假

    • TrueFalse是唯二两个布尔对象
    • 整形子类型:在各类场合中行为类似整形10,仅在 转换为字符串时返回"True""False"

方法、函数

  • int.bit_length():不包括符号位、开头0位长
  • int.to_bytes(length, byteorder, *, signed=False)
  • class int.from_bytes(bytes, byteorder, *, signed=False)

numbers.Real(float)

float:表示机器级双精度浮点数

  • 接受的取值返回、溢出处理取决于底层结构、python实现
  • python不支持单精度浮点
  • 没必要因为节省处理器、内存消耗而增加语言复杂度

特殊取值

1
2
3
4
5
infty = float("inf")
neg_infty = float("-inf")
# 正/负无穷大
nan = float("nan")
# Not a Number
  • 特殊取值根据定义==is肯定返回False

    • float.__eq__内部应该有做检查,保证==返回False
    • 每次会创建“新”的nan/infty
    • 连续执行id(float("nan"))返回值可能相等,这是因为 每次生成的float("nan")对象被回收,不影响
  • np.nan is np.nan返回True,应该是numpy初始化的时候 创建了一个float("nan"),每次都是使用同一个nan

相关操作

  • float.as_integer_ratio()
  • float.is_integer()
  • float.hex()
  • classmethod float.fromhex(s)
  • round(f[,n])
  • math.trunc(f)
  • math.floor(f)
  • math.ceil(f)

numbers.Complex(complex)

complex:以一对机器级双精度浮点数表示复数值

  • 实部、虚部:可通过只读属性z.realz.imag获取

Iterators

迭代器类型

  • 迭代器对象需要自身支持以下两个方法,其共同组成迭代器协议
    • iterator.__iter__()
    • iterator.__next__()
  • 方法详细参考cs_python/py3ref/cls_special_method

Generator

生成器类型:提供了实现迭代器协议的便捷形式

  • 将容器对象的__iter__()方法实现为生成器,方便实现容器对 迭代器支持
  • 创建、使用参见cs_python/py3ref/dm_gfuncs

序列

序列:表示以非负整数作为索引的有限有序集

  • 不可变序列类型:对象一旦创建不能改变

    • 若包含其他可变对象引用,则可变对象“可改变”
    • 但不可变对象所直接引用的对象集是不可变的
    • 包括
      • str
      • tuple
      • bytes
      • range:非基本序列类型
  • 可变序列:创建后仍可被改变值

    • list
    • bytesarray

通用序列操作

  • x in sx not in s

    • strbytesbytearray支持子序列检测
  • s + t:拼接

    • 拼接不可变总会生成新对象
    • 重复拼接构建序列的运行时开销将基于序列总长度乘方
  • s * nn * ss自身拼接n

    • n<0被当作0处理
    • s中项不会被复制,而是被多次引用
  • s[i]s[i:j]s[i:j:step]

    • i<0索引为负值:索引顺序相对于序列s末尾,等价于 对序列长度取模
    • 序列切片:与序列类型相同的新序列
      • 索引从0开始
      • 左闭右开
    • 某些序列支持a[i:j:step]扩展切片
  • s.index(x[, i[, j]])

    • 仅部分序列支持
    • 类似s[i:j].index(x),但返回值是相对序列开头
  • s.count(x):序列中元素x数目

  • len(s):返回序列条目数量

  • min(s)max(s):序列最小、最大值

  • 序列比较运算默认实现参见cs_python/py3ref/expressions
  • 以上运算自定义实现参见 cs_python/py3ref/cls_special_methods

不可变序列

不可变序列普遍实现而可变序列未实现的操作

  • hash()内置函数

可变序列

  • s[i]=xs[i:j]=ts[i:j:k]=t:下标、切片被赋值
    • s[i:j:k]=tt长度必须和被替换切片长度相同
  • del s[i:j]del s[i:j:k]:移除元素
    • 作为del语句的目标
    • 等同于s[i:j]=[]
  • s.append():添加元素
    • 等同于s[len(s):len(s)] = [x]
  • s.clear():移除所有项
    • 等同于del s[:]
  • s.copy():浅拷贝
    • 等同于s[:]
  • s.extend(t):扩展(合并)序列
    • 基本上等于s += t
  • s.insert(i, x):向序列中插入元素
    • 等同于s[i:i] = [x]
  • s.pop(i=-1):弹出序列中元素
  • s.remove(x):删除序列中首个值为x的项
  • s.reverse():反转序列
    • 反转大尺寸序列时,会原地修改序列
    • 为提醒用户此操作通过间接影响进行,不会返回反转后序列
  • arraycollections模块提供额外可变序列类型
  • 可利用collections.abc.MutableSequence抽象类简化自定义 序列操作

tuple

元组

  • 元组中条目可以是任意python对象
  • 元组创建
    • 一对圆括号创建空元组
    • 逗号分隔
      • 单项元组:后缀逗号a,(a,)
      • 多项元组:a,b,c(a,b,c)
    • 内置构建器:tupletuple(iterable)

list

列表

  • 列表中条目可以是任意python对象
  • 构建方式
    • 方括号括起、项以逗号分隔:[][a][a,b]
    • 列表推导式:[x for x in iterable]
    • 类型构造器:list(iterable)

相关操作

.sort
1
2
def list.sort(*, key=None, reverse=False):
pass
  • 用途:对列表原地排序

    • 使用<进行各项之间比较
    • 不屏蔽异常:若有比较操作失败,整个排序操作将失败, 此时列表可能处于部分被修改状态
  • 参数

    • key:带参数函数,遍历处理每个元素提取比较键
      • None:默认,直接使用列表项排序
  • 说明

    • .sort保序,有利于多重排序
    • 为提醒用户此方法原地修改序列保证空间经济性,其不返回 排序后序列(可考虑使用sorted显式请求)
  • CPython:列表排序期间尝试改变、检测会造成未定义影响, CPython将列表排序期间显式为空,若列表排序期间被改变将 raise ValueError

str

1
2
3
4
5
class str(object="")
# 返回`object.__str__()`、`object.__repr__()`
class str(object=b"", encoding="utf-8", errors="strict")
# 给出`encoding`、`errors`之一,须为bytes-like对象
# 等价于`bytes.decode(encoding, errors)`

字符串:由Unicode码位值组成不可变序列(应该是UTF16-bl编码)

  • 范围在U+0000~U+10FFFF内所有码位值均可在字符串中使用
  • 不存在单个“字符”类型
    • 字符串中单个字符为长度为1字符串
  • 不存在可变字符串类型
    • 可以用str.join()io.StringIO高效连接多个字符串 片段
  • 字符串构建
    • 字符串字面值:cs_python/py3ref/lexical_analysis
    • 内置构造器str()

相关操作

  • ord():转换单个字符字符串为(整形)码位
  • chr():转换(整形)码位为单个字符字符串
判断
  • str.isalnum()
  • str.isalpha()
  • str.isascii()
  • str.isdecimal()
  • str.isdigit()
  • str.isidentifier()
  • str.islower()
  • str.isnumeric()
  • str.isprintable()
  • str.isspace()
  • str.istitle()
  • str.isupper()
查找
  • str.rfind(sub[, start[, end]])
  • str.rindex(sub[, start[, end]])
  • str.startswith(prefix[, start[, end]])
  • str.endwith(suffix[, start[, end]])
  • str.count(sub[, start[, end]]):子串出现次数
  • str.find(sub[, start[, end]])
    • 仅检查sub是否为子串,应使用in
    • 找不到子串时返回-1
  • str.index(sub[, start[, end]])
    • 类似str.find,但找不到子串时raise ValueError
分隔
  • str.partition(sep)
  • str.rpartition(sep)
  • str.rsplit(sep=None, maxsplit=-11)
  • str.split(sep=None, maxsplit=-1)
  • str.splitline([keepends])
拼接
  • str.join(iterable)
  • str.strip([chars])
  • str.lstrip([chars])
  • str.rstrip([chars])
  • str.rstrip([chars])
转换
  • str.lower()
  • str.upper()
  • str.swapcase()
  • str.translate(table)
  • str.replace(old, new[, count])
  • static str.maketrans(x[, y[, z]])
  • str.encode(encoding="utf-8", errors="strict"):使用 指定编码方案编码为bytes
  • str.expandtabs(tabsize=8)
  • str.capitalize():首字符大写副本
  • str.casefold():消除大小写副本
  • str.center(width[, fillchar]):字符串位于中间的字符串
  • str.title()
格式化
  • str.ljust(width[, fillchar])
  • str.rjust(width[, fillchar])
  • str.zfill(width)
  • str.format(*args, **kwargs)
  • str.format_map(mapping)
    • 类似str.format(**mapping),但mapping不会被复制 到dict
      1
      2
      3
      4
      class Default(dict):
      def __missing__(self, key):
      return key
      "{name} was born in {country}".format_map(Default(name="Guido"))
printf风格字符串格式化
  • format % values中:format%转换标记符将被转换 为values中条目

    • 效果类似于sprintf
    • values为与format中指定转换符数量等长元组、或映射 对象,除非format要求单个参数
  • 转换标记符按以下顺序构成

    • %字符:标记转换符起始
    • 映射键:可选,圆括号()括起字符序列
      • values为映射时,映射键必须
    • 转换旗标:可选,影响某些类型转换效果
      • #:值转换使用“替代形式”
      • 0:为数字值填充0字符
      • -:转换值左对齐(覆盖0
      • :符号位转换产生整数(空字符串)将留出空格
      • +:符号字符显示在开头(覆盖
    • 最小字段宽度:可选
      • *:从values读取下个元素
    • 精度:可选,.之后加精度值
      • *:从values读取下个元素
    • 长度修饰符:可选
    • 转换类型
      • d/u/i:十进制整形
      • o:8进制整形
        • #替代形式,前端添加0o
      • x/X:小/大写16进制整形
        • #替代形式,前端添加0x/0X
      • e/E:小/大写浮点指数
        • #替代形式,总是包含小数点
      • f/`F:浮点10进制
        • #替代形式,总是包含小数点
      • g/G:指数小于-4、不小于精度使用指数格式
        • #替代形式,总是包含小数点,末尾0不移除
      • c:单个字符(接收整数、单个字符字符串)
      • r/s/a:字符串(repr/str/ascii转换)
        • 按输出精度截断
      • %:输出%字符

技巧

  • 快速字符串拼接
    • 构建包含字符串的列表,利用str.join()方法
    • 写入io.StringIO实例,结束时获取值

bytes/bytearray

1
class bytes([source[, encoding[, errors]]])
  • 字节串:单个字节构成的不可变序列
  • 字节数组:字节串可变对应版本,其他同不可变bytes
  • 字节串构建

    • 字节串字面值:cs_python/py3ref/lexical_analysis
    • 内置构造器bytes()
      • 指定长度零值填充:bytes(10)
      • 整数组成可迭代对象:bytes(range(20))
      • 通过缓冲区协议复制现有二进制数据:bytes(obj)
  • 字节数组构建

    • 字节数组没有字面值语法,只能通过构造器构造
    • 可变,构建空字节数组有意义
  • 类似整数构成序列

    • 每个条目都是8位字节
    • 取值范围0~255,但只允许ASCII字符0~127
    • b[0]产生整数,切片返回bytes对象
    • 可通过list(bytes)bytes对象转换为整数构成列表
  • memeoryview提供支持

相关函数、方法

  • bytes.decode:解码为相关字符串
  • classmethod bytes.fromhex(string)
  • bytes.hex()

技巧

  • 快速字节串拼接
    • 构建包含字节串的列表,利用bytes.join()方法
    • 写入io.BytesIO实例,结束时获取值
    • 使用betaarray对象进行原地拼接

memoryview

1
class memoryview(obj)

内存视图:允许python代码访问对象内部数据

  • 若对象支持缓冲区协议,则无需拷贝

    • 支持缓冲区协议的内置对象包括bytesbytesarray
  • 内存视图元素:原始对象obj处理的基本内存单元

    • 对简单bytesbytesarray对象,一个元素就是一字节
    • array.array等类型可能有更大元素
  • 内存视图支持索引抽取、切片

    • 若下层对象可选,则支持赋值,但切片赋值不允许改变大小

相关操作

  • mv.__eq__(exporter)
  • mv.__len__()
  • mv.tobyte()
  • mv.hex()
  • mv.tolist()
  • mv.release()
  • mv.cast(format[, shape]):将内存视图转换新格式形状

可用属性

以下属性均只读

  • mv.obj:内存视图的下层对象
  • mv.nbytes
    • == product(shape) * itemsize = len(mv.tobytes())
  • mv.readonly
  • mv.format:内存视图中元素格式
    • 表示为struct模块格式
  • mv.itemsize
  • mv.ndim
  • mv.shape
  • mv.strides
  • mv.suboffsets
  • mv.c_contiguous
  • mv.f_contiguous
  • mv.contiguous

Slices Object

切片对象:表示__getitem__()方法得到的切片

  • 可以使用内置的slice()函数创建
  • a[start: stop]形式的调用被转换为 a[slice(start, stop, None)]
  • 切片对象是内部类型,参见cs_python/py3ref/dm_exec,也 不是序列类型

特殊只读属性

  • start:下界
  • stop:上界
  • step:步长值
  • 属性可以具有任意类型

方法

  • .indices(self, length):计算切片对象被应用到length 长度序列时切片相关信息
    • 返回值:(start, stop, step)三元组
    • 索引号缺失、越界按照正规连续切片方式处理

range

range:不可变数字序列类型(非不是基本序列类型)

1
2
class range(stop)
class range(start=0, stop[, step=1])
  • 参数:必须均为整数(int或实现__index__方法)

    • step > 0:对range对象r[i]=start + step * i,其中 i >= 0, r[i] < stop
    • step < 0:对range对象r[i]=start + step * i,其中 i >= 0, r[i] > stop
    • step = 0raise ValueError
  • 说明

    • 允许元素绝对值大于sys.maxsize,但是某些特性如: len()可能raise OverflowError
    • range类型根据需要计算单项、切片值
      • 相较于常规listtuple占用内存较小,且和表示 范围大小无关
      • 只能表示符合严格模式的序列
    • range类型实现了collections.abc.Sequence抽象类
      • 基本实现序列所有操作:检测、索引查找、切片等
      • 除拼接、重复:拼接、重复通常会违反严格模式
    • !===range对象视为序列比较,即提供相同值即 认为相等

集合类型

  • 表示不重复不可变对象组成的无序、有限集合

    • 不能通过下标索引
    • 可以迭代
    • 可以通过内置函数len返回集合中条目数量
  • 常用于

    • 快速成员检测、去除序列中重复项
    • 进行交、并、差、对称差等数学运算

公用操作

  • len(s)
  • x [not ]in s
  • s.isdisjoint(other)
  • s.issubset(other)/s <= other
  • s < other
  • s.issuperset(other)/s >= other
  • s > other
  • s.union(*others)/s | other |...
  • s.intersection(*others)/s & other &...
  • s.difference(*other)/s - other - other
  • s.symmetric_difference(other)/s ^ other
  • s.copy()
  • 集合比较仅定义偏序,集合列表排序无意义

可变集合独有

  • s.update(*others)/s |= other |...
  • s.intersection_update(*others)/s &= other &...
  • s.difference_udpate(*others)/s -= other |...
  • s.symmetric_difference_update(other)/set ^= other
  • s.add(elem)
  • s.remove(elem)
  • s.discard(elem)
  • s.pop()
  • s.clear()

set/frozenset

1
2
class set([iterable])
class frozenset([iterable])
  • 集合:由具有唯一性的hashable对象组成的多项无序集
  • 冻结集合:不可变集合,可哈希,可以用作集合元素、字典键
  • 创建集合

    • set()内置构造器
    • 花括号包括、逗号分隔元组列表:{a, b}
  • 创建冻结集合

    • frozenset()内置构造器
  • python中集合类似dict通过hash实现

    • 集合元素须遵循同字典键的不可变规则
    • 数字:相等的数字1==1.0,同一集合中只能包含一个

操作说明

  • .remove.__contains__discard等可以接收set类型 参数,其将被转换为临时frozenset对象

  • 非运算符版本操作可以接受任意可迭代对象作为参数,运算符 版本只能接受集合类型作为参数

映射

映射:表示任何索引集合所索引的对象的集合

  • 通过下标a[k]可在映射a中选择索引为k的条目
    • 可在表达式中使用
    • 可以作为赋值语句、del语句的目标
  • dbm.ndbmdbm.gnucollections模块提供额外映射类型

通用操作

  • len(d)
  • d[key]
  • key [not ]in d
  • iter(d)
  • d.keys():返回字典视图对象
  • d.values():返回字典视图对象
  • d.items():返回字典视图对象
  • d.get(key[, default])
  • d.copy()
  • classmethod fromkey(iterable[, value])

可变映射独有

  • d[key]=value
  • del d[key]
  • d.clear()
  • d.setdefault(key[, default])
  • d.pop()
  • d.popitem()
  • d.copy()
  • d.update()

dict

1
2
3
class dict(**kwargs)
class dict(mapping, **kwargs)
class dict(iterable, **kwargs)

字典:可由几乎任意值作为索引的有限个对象可变集合

  • 字典的高效实现要求使用键hash值以保持一致性

    • 不可作为键的值类型
      • 包含列表、字典的值
      • 其他通过对象编号而不是值比较的可变对象
    • 数字:相等的数字1==1.0索引相同字典条目
  • 创建字典

    • 花括号括起、逗号分隔键值对:{key:value,}
    • 内置字典构造器:dict()

字典视图对象

字典视图对象:提供字典条目的动态视图,随字典改变而改变

  • len(dictview)
  • iter(dictview)
  • x in dictview