การทำงานร่วมกันของหลาม

การทำงานร่วมกันของ Python API ถือเป็นข้อกำหนดที่สำคัญสำหรับโปรเจ็กต์นี้ แม้ว่า Swift ได้รับการออกแบบมาเพื่อผสานรวมกับภาษาการเขียนโปรแกรมอื่นๆ (และรันไทม์) แต่ลักษณะของภาษาไดนามิกไม่จำเป็นต้องมีการผสานรวมเชิงลึกซึ่งจำเป็นเพื่อรองรับภาษาคงที่ โดยเฉพาะ Python ได้ รับการออกแบบให้ฝังลง ในแอปพลิเคชันอื่นและมี API อินเทอร์เฟซ C แบบธรรมดา เพื่อวัตถุประสงค์ในการทำงานของเรา เราสามารถจัดให้มีการฝังเมตาได้ ซึ่งช่วยให้โปรแกรม Swift สามารถใช้ Python API ราวกับว่าพวกเขากำลังฝัง Python โดยตรง

เพื่อให้บรรลุเป้าหมายนี้ สคริปต์/โปรแกรม Swift เพียงลิงก์ล่าม Python เข้ากับโค้ดของมัน เป้าหมายของเราเปลี่ยนจาก "เราทำงานอย่างไรกับ Python API" เป็นคำถาม "เราจะทำให้ Python API รู้สึกเป็นธรรมชาติ เข้าถึงได้ และเข้าถึงได้ง่ายจากโค้ด Swift ได้อย่างไร" นี่ไม่ใช่ปัญหาเล็กๆ น้อยๆ - มีความแตกต่างในการออกแบบที่สำคัญระหว่าง Swift และ Python รวมถึงวิธีการจัดการข้อผิดพลาด ลักษณะแบบไดนามิกขั้นสูงของ Python ความแตกต่างในรูปแบบไวยากรณ์ระดับพื้นผิวระหว่างสองภาษา และความปรารถนาที่จะไม่ "ประนีประนอม" สิ่งที่โปรแกรมเมอร์ Swift คาดหวัง นอกจากนี้เรายังให้ความสำคัญกับความสะดวกสบายและการยศาสตร์ และคิดว่าการต้องใช้เครื่องสร้างกระดาษห่อเช่น SWIG เป็นสิ่งที่ยอมรับไม่ได้

แนวทางโดยรวม

วิธีการโดยรวมของเรานั้นอิงจากการสังเกตว่ามีการพิมพ์ Python อย่างรุนแรง แต่เช่นเดียวกับภาษาที่พิมพ์แบบไดนามิกส่วนใหญ่ ระบบประเภทของมันถูกบังคับใช้ในขณะรันไทม์ แม้ว่าจะมีความพยายามหลายครั้งในการติดตั้ง ระบบแบบสแตติก เพิ่มเติม (เช่น mypy , pytype และ other ) แต่พวกเขาก็อาศัยระบบประเภทที่ไม่ปลอดภัย ดังนั้นจึงไม่ใช่โซลูชันเต็มรูปแบบที่เราสามารถพึ่งพาได้ และยิ่งไปกว่านั้น พวกมันยังตัดกับหลายๆ อย่างอีกด้วย ของสถานที่ออกแบบที่ทำให้ Python และไลบรารีของมันยอดเยี่ยมอย่างแท้จริง

หลายๆ คนมองว่า Swift เป็นภาษาที่พิมพ์แบบคงที่ ดังนั้นจึงข้ามไปที่ข้อสรุปว่าวิธีแก้ปัญหาที่ถูกต้องคือการใส่รูปแบบของเหลวของ Python ลงในช่องที่กำหนดแบบคงที่ อย่างไรก็ตาม คนอื่นๆ ตระหนักดีว่า Swift รวมข้อดีของระบบประเภทคงที่อันทรงพลังเข้ากับระบบประเภทไดนามิก (ที่มักไม่ค่อยมีใครชื่นชม!) แทนที่จะพยายามบังคับให้ระบบประเภทไดนามิกของ Python เป็นสิ่งที่ไม่ใช่ เราเลือกที่จะพบกับ Python ในจุดที่มันอยู่ และยอมรับแนวทางการพิมพ์แบบไดนามิกของมันอย่างเต็มที่

ผลลัพธ์ที่ได้คือเราสามารถบรรลุประสบการณ์ Python ที่เป็นธรรมชาติได้โดยตรงในโค้ด Swift นี่คือตัวอย่างของลักษณะนี้ รหัสที่ใส่ความคิดเห็นจะแสดงไวยากรณ์ pure-Python เพื่อการเปรียบเทียบ:

import PythonKit

// Python:
//    import numpy as np
//    a = np.arange(15).reshape(3, 5)
//    b = np.array([6, 7, 8])
let np = Python.import("numpy")
let a = np.arange(15).reshape(3, 5)
let b = np.array([6, 7, 8])

// Python:
//    import gzip as gzip
//    import pickle as pickle
let gzip = Python.import("gzip")
let pickle = Python.import("pickle")

// Python:
//    file = gzip.open("mnist.pkl.gz", "rb")
//    (images, labels) = pickle.load(file)
//    print(images.shape)  # (50000, 784)
let file = gzip.open("mnist.pkl.gz", "rb")
let (images, labels) = pickle.load(file).tuple2
print(images.shape) // (50000, 784)

อย่างที่คุณเห็น ไวยากรณ์ที่นี่สามารถเข้าใจได้ทันทีสำหรับโปรแกรมเมอร์ Python ความแตกต่างที่สำคัญคือ Swift จำเป็นต้องประกาศค่าก่อนใช้งาน (ด้วย let หรือ var ) และเราเลือกที่จะใส่ ฟังก์ชัน Python ในตัว เช่น import , type , slice ฯลฯ ภายใต้ Python. เนมสเปซ (เพื่อหลีกเลี่ยงการเกะกะขอบเขตส่วนกลาง) นี่เป็นผลมาจากความสมดุลระหว่างการพยายามทำให้ Python รู้สึกเป็นธรรมชาติและคุ้นเคย โดยไม่กระทบต่อการออกแบบภาษา Swift ทั่วโลก

บรรทัดนี้สร้างขึ้นตามข้อกำหนดง่ายๆ: เราไม่ควรพึ่งพา คอมไพเลอร์หรือคุณลักษณะภาษาเฉพาะของ Python เพื่อให้เกิดการทำงานร่วมกันของ Python - ควรนำไปใช้งานอย่างสมบูรณ์ในฐานะไลบรารี Swift ท้ายที่สุดแล้ว แม้ว่า Python จะมีความสำคัญอย่างยิ่งต่อชุมชนการเรียนรู้ของเครื่อง แต่ก็มีภาษาไดนามิกอื่นๆ (Javascript, Ruby ฯลฯ) ที่มีความสำคัญในโดเมนอื่น และเราไม่ต้องการให้แต่ละโดเมนเหล่านี้ก่อให้เกิดความซับซ้อนที่ไม่มีที่สิ้นสุด สู่ภาษา Swift

คุณสามารถดูการใช้งาน Bridging Layer ของเราในปัจจุบันได้ใน Python.swift นี่เป็นโค้ด Swift ล้วนๆ ที่ใช้งานได้กับ Swift ที่ไม่ได้แก้ไข

ข้อจำกัดของแนวทางนี้

เนื่องจากเราเลือกที่จะยอมรับลักษณะไดนามิกของ Python ใน Swift เราจึงมีทั้งข้อดีและข้อเสียที่ภาษาไดนามิกนำมาด้วย โดยเฉพาะอย่างยิ่ง โปรแกรมเมอร์ Swift หลายคนคาดหวังและพึ่งพาการเติมโค้ดให้สมบูรณ์อย่างน่าทึ่ง และชื่นชมความสบายใจของการที่คอมไพเลอร์ตรวจจับการพิมพ์ผิดและข้อบกพร่องเล็กๆ น้อยๆ อื่นๆ ในเวลาคอมไพล์ ในทางตรงกันข้าม โปรแกรมเมอร์ Python ไม่มีความสามารถเหล่านี้ (โดยปกติแล้ว จุดบกพร่องมักจะถูกจับได้ที่รันไทม์) และเนื่องจากเรายอมรับลักษณะไดนามิกของ Python ดังนั้น Python API ใน Swift จึงทำงานในลักษณะเดียวกัน

หลังจากการพิจารณาอย่างรอบคอบกับชุมชน Swift ก็ชัดเจนว่านี่คือความสมดุล: ปรัชญาและระบบคุณค่าของ Swift สามารถฉายลงบนระบบนิเวศของไลบรารี Python ได้มากเพียงใด... โดยไม่ทำลายสิ่งเหล่านั้นซึ่งเป็นเรื่องจริงและสวยงามเกี่ยวกับ Python และห้องสมุดของมันเหรอ? ในท้ายที่สุด เราได้ข้อสรุปว่าโมเดลที่เน้น Python เป็นหลักเป็นการประนีประนอมที่ดีที่สุด เราควรยอมรับความจริงที่ว่า Python เป็นภาษาไดนามิก ที่ว่ามันจะไม่มีวันและไม่สามารถมีการเติมโค้ดให้สมบูรณ์และการตรวจจับข้อผิดพลาดในเวลาคอมไพล์แบบคงที่ได้

มันทำงานอย่างไร

เราแมประบบประเภทไดนามิกของ Python ให้เป็นประเภท Swift แบบ คง ที่ชื่อ PythonObject และอนุญาตให้ PythonObject รับค่า Python ไดนามิกใด ๆ ณ รันไทม์ (คล้ายกับแนวทางของ Abadi และคณะ ) PythonObject สอดคล้องโดยตรงกับ PyObject* ที่ใช้ในการผูก Python C และสามารถทำอะไรก็ได้ที่ค่า Python ทำใน Python ตัวอย่างเช่น สิ่งนี้ใช้งานได้เหมือนกับที่คุณคาดหวังใน Python:

var x: PythonObject = 42  // x is an integer represented as a Python value.
print(x + 4)         // Does a Python addition, then prints 46.

x = "stringy now"    // Python values can hold strings, and dynamically change Python type!
print("super " + x)  // Does a Python addition, then prints "super stringy now".

เนื่องจากเราไม่ต้องการประนีประนอมกับการออกแบบ Swift ทั่วโลก เราจึงจำกัดพฤติกรรมของ Python ทั้งหมดไว้เฉพาะนิพจน์ที่เกี่ยวข้องกับประเภท PythonObject นี้ สิ่งนี้ทำให้แน่ใจได้ว่าความหมายของโค้ด Swift ปกติยังคงไม่เปลี่ยนแปลง แม้ว่าจะเป็นการผสม การจับคู่ การเชื่อมต่อ และการผสมผสานกับค่า Python ก็ตาม

การทำงานร่วมกันขั้นพื้นฐาน

ใน Swift 4.0 ความสามารถในการทำงานร่วมกันขั้นพื้นฐานในระดับที่เหมาะสมนั้นสามารถทำได้โดยตรงผ่านฟีเจอร์ภาษาที่มีอยู่: เราเพียงกำหนด PythonObject ให้เป็นโครงสร้าง Swift ที่ล้อมคลาส Swift PyReference ส่วนตัว ทำให้ Swift เข้ามารับผิดชอบในการนับการอ้างอิง Python แทน:

/// Primitive reference to a Python value.  This is always non-null and always
/// owning of the underlying value.
private final class PyReference {
  var state: UnsafeMutablePointer<PyObject>

  init(owned: UnsafeMutablePointer<PyObject>) {
    state = owned
  }

  init(borrowed: UnsafeMutablePointer<PyObject>) {
    state = borrowed
    Py_IncRef(state)
  }

  deinit {
    Py_DecRef(state)
  }
}

// This is the main type users work with.
public struct PythonObject {
  /// This is a handle to the Python object the PythonObject represents.
  fileprivate var state: PyReference
  ...
}

ในทำนองเดียวกัน เราสามารถใช้ func + (และตัวดำเนินการ Python ที่เหลือที่รองรับ) บน PythonObject ในแง่ของอินเทอร์เฟซรันไทม์ Python ที่มีอยู่ การใช้งานของเรามีลักษณะดังนี้:

// Implement the + operator in terms of the standard Python __add__ method.
public static func + (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
  return lhs.__add__.call(with: rhs)
}
// Implement the - operator in terms of the standard Python __sub__ method.
public static func - (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
  return lhs.__sub__.call(with: rhs)
}
// Implement += and -= in terms of + and -, as usual.
public static func += (lhs: inout PythonObject, rhs: PythonObject) {
  lhs = lhs + rhs
}
public static func -= (lhs: inout PythonObject, rhs: PythonObject) {
  lhs = lhs - rhs
}
// etc...

นอกจากนี้เรายังทำให้ PythonObject สอดคล้องกับ Sequence และโปรโตคอลอื่นๆ อีกด้วย ทำให้โค้ดลักษณะนี้ทำงานได้:

func printPythonCollection(_ collection: PythonObject) {
  for elt in collection {
    print(elt)
  }
}

นอกจากนี้ เนื่องจาก PythonObject สอดคล้องกับ MutableCollection คุณจึงสามารถเข้าถึง Swift APIs for Collections ได้อย่างเต็มที่ รวมถึงฟังก์ชันต่างๆ เช่น map , filter , sort ฯลฯ

การแปลงเป็นและจากค่า Swift

ตอนนี้ Swift สามารถแสดงและดำเนินการกับค่า Python ได้ การแปลงระหว่างประเภทเนทีฟของ Swift เช่น Int และ Array<Float> และ Python ที่เทียบเท่ากันจึงกลายเป็นสิ่งสำคัญ สิ่งนี้ได้รับการจัดการโดยโปรโตคอล PythonConvertible ซึ่งประเภท Swift พื้นฐานเช่น Int เป็นไปตามนั้น และประเภทคอลเลกชัน Swift เช่น Array และ Dictionary เป็นไปตามเงื่อนไข (เมื่อองค์ประกอบสอดคล้องกัน) สิ่งนี้ทำให้การแปลงเข้ากับรุ่น Swift ได้อย่างเป็นธรรมชาติ

ตัวอย่างเช่น หากคุณรู้ว่าคุณต้องการจำนวนเต็ม Swift หรือต้องการแปลงจำนวนเต็ม Swift เป็น Python คุณสามารถใช้:

let pyInt = PythonObject(someSwiftInteger)     // Always succeeds.
if let swiftInt = Int(somePythonValue) {  // Succeeds if the Python value is convertible to Int.
  print(swiftInt)
}

ในทำนองเดียวกัน ประเภทการรวม เช่น อาร์เรย์ทำงานในลักษณะเดียวกันทุกประการ:

// This succeeds when somePythonValue is a collection of values that are convertible to Int.
if let swiftIntArray = Array<Int>(somePythonValue) {
  print(swiftIntArray)
}

สิ่งนี้ลงตัวกับโมเดลที่โปรแกรมเมอร์ Swift คาดหวัง: การแปลงที่ล้มเหลวจะถูกฉายเป็นผลลัพธ์ทางเลือก (เช่นเดียวกับการแปลง "string เป็น int") ให้ความปลอดภัยและการคาดการณ์ที่โปรแกรมเมอร์ Swift คาดหวัง

สุดท้ายนี้ เนื่องจากคุณมีสิทธิ์เข้าถึงความสามารถเต็มรูปแบบของ Python ความสามารถในการสะท้อนกลับตามปกติของ Python จึงพร้อมใช้งานโดยตรงเช่นกัน รวมถึง Python.type , Python.id , Python.dir และโมดูล inspect Python

ความท้าทายในการทำงานร่วมกัน

การสนับสนุนข้างต้นเป็นไปได้เนื่องจากการออกแบบของ Swift มีจุดมุ่งหมายและชื่นชมเป้าหมายของการขยายประเภทวากยสัมพันธ์ระดับไลบรารี นอกจากนี้เรายังโชคดีที่ Python และ Swift แบ่งปันไวยากรณ์ระดับพื้นผิวที่คล้ายกันมากสำหรับนิพจน์ (ตัวดำเนินการและการเรียกใช้ฟังก์ชัน/เมธอด) ที่กล่าวว่ามีความท้าทายสองสามประการที่เราพบเนื่องจากข้อจำกัดของการขยายไวยากรณ์ของ Swift 4.0 และความแตกต่างด้านการออกแบบโดยเจตนาที่เราต้องเอาชนะ

การค้นหาสมาชิกแบบไดนามิก

แม้ว่า Swift จะเป็นภาษาที่ขยายได้โดยทั่วไป แต่การค้นหาสมาชิกดั้งเดิมไม่ใช่คุณสมบัติที่ขยายไลบรารีได้ โดยเฉพาะอย่างยิ่ง เมื่อพิจารณาจากนิพจน์ในรูปแบบ xy ประเภทของ x ไม่สามารถควบคุมสิ่งที่เกิดขึ้นเมื่อมีการเข้าถึงสมาชิก y ได้ หากประเภทของ x ได้ประกาศสมาชิกชื่อ y แบบคงที่ นิพจน์นี้จะได้รับการแก้ไข ไม่เช่นนั้นจะถูกปฏิเสธโดยคอมไพเลอร์

ภายใต้ข้อจำกัดของ Swift เราได้สร้างการเชื่อมโยง ที่แก้ไขปัญหานี้ ตัวอย่างเช่น เป็นเรื่องง่ายที่จะใช้การเข้าถึงของสมาชิกในแง่ของ PyObject_GetAttrString และ PyObject_SetAttrString ของ Python รหัสที่อนุญาตนี้เช่น:

// Python: a.x = a.x + 1
a.set(member: "x", to: a.get(member: "x") + 1)

อย่างไรก็ตาม เราทุกคนคงเห็นพ้องต้องกันว่าสิ่งนี้ไม่บรรลุเป้าหมายของเราในการจัดหาอินเทอร์เฟซที่เป็นธรรมชาติและถูกหลักสรีระศาสตร์สำหรับการทำงานกับค่า Python! นอกเหนือจากนั้น มันไม่ได้ให้เงินใด ๆ สำหรับการทำงานกับ Swift L-Values: ไม่มีทางสะกดเทียบเท่ากับ ax += 1 ได้ ปัญหาทั้งสองนี้รวมกันเป็นช่องว่างในการแสดงออกที่สำคัญ

หลังจาก การหารือ กับ ชุมชน Swift วิธีแก้ปัญหานี้คือการอนุญาตให้โค้ดไลบรารีใช้ hook ทางเลือกเพื่อจัดการกับการค้นหาสมาชิก ที่ ล้มเหลว คุณลักษณะนี้มีอยู่ในภาษาไดนามิกหลายภาษา รวมถึง Objective-C ดังนั้นเราจึงเสนอและใช้งาน SE-0195: แนะนำประเภท "การค้นหาสมาชิกแบบไดนามิก" ที่ผู้ใช้กำหนด ซึ่งอนุญาตให้ประเภทคงที่จัดเตรียมตัวจัดการทางเลือกสำหรับการค้นหาที่ยังไม่ได้รับการแก้ไข ข้อเสนอนี้ได้ รับการพิจารณาอย่างละเอียดโดยชุมชน Swift ผ่านกระบวนการ Swift Evolution และในที่สุดก็ได้รับการยอมรับ มีการจัดส่งมาตั้งแต่ Swift 4.1

ด้วยเหตุนี้ ไลบรารีการทำงานร่วมกันของเราจึงสามารถใช้งาน hook ต่อไปนี้ได้:

@dynamicMemberLookup
public struct PythonObject {
...
  subscript(dynamicMember member: String) -> PythonObject {
    get {
      return ... PyObject_GetAttrString(...) ...
    }
    set {
      ... PyObject_SetAttrString(...)
    }
  }
}

ซึ่งช่วยให้โค้ดข้างต้นสามารถแสดงเป็น:

// Python: a.x = a.x + 1
a.x = a.x + 1

... และไวยากรณ์ ax += 1 ทำงานเหมือนที่เราคาดหวัง สิ่งนี้แสดงให้เห็นถึงประโยชน์มหาศาลของความสามารถในการพัฒนาภาษา ไลบรารี และแอปพลิเคชันทั้งหมดร่วมกันเพื่อให้บรรลุเป้าหมาย

ประเภทที่สามารถเรียกได้แบบไดนามิก

นอกเหนือจากการค้นหาสมาชิกแล้ว เรายังมีความท้าทายที่คล้ายกันเมื่อพูดถึงค่าการโทร ภาษาไดนามิกมักจะมีแนวคิดเรื่อง ค่า "callable" ซึ่งสามารถรับลายเซ็นโดยพลการ แต่ Swift 4.1 ไม่รองรับสิ่งนั้น ตัวอย่างเช่น ใน Swift 4.1 ไลบรารีการทำงานร่วมกันของเราสามารถทำงานร่วมกับ Python API ผ่านทางอินเทอร์เฟซดังนี้:

// Python: a = np.arange(15).reshape(3, 5)
let a = np.arange.call(with: 15).reshape.call(with: 3, 5)

// Python: d = np.array([1, 2, 3], dtype="i2")
let d = np.array.call(with: [6, 7, 8], kwargs: [("dtype", "i2")])

แม้ว่าจะเป็นไปได้ที่จะทำสิ่งต่างๆ ได้สำเร็จ แต่ก็เห็นได้ชัดว่าไม่บรรลุเป้าหมายของเราในด้านความสะดวกสบายและการยศาสตร์

จากการประเมินปัญหานี้กับ ชุมชน Swift และ #2 เราสังเกตว่า Python และ Swift รองรับทั้งอาร์กิวเมนต์ที่มีชื่อและไม่มีชื่อ: อาร์กิวเมนต์ที่มีชื่อจะถูกส่งผ่านในรูปแบบพจนานุกรม ในเวลาเดียวกัน ภาษาที่ได้รับจาก Smalltalk จะเพิ่มรอยย่นเพิ่มเติม: การ อ้างอิงเมธอดคือหน่วยอะตอมมิก ซึ่งรวมถึงชื่อพื้นฐานของเมธอดพร้อมกับอาร์กิวเมนต์คำหลักใดๆ แม้ว่าความสามารถในการทำงานร่วมกันกับภาษาสไตล์นี้ไม่สำคัญสำหรับ Python แต่เราต้องการให้แน่ใจว่า Swift ไม่ได้ถูกทาสีในมุมที่ขัดขวางการทำงานร่วมกันอย่างดีเยี่ยมกับ Ruby, Squeak และภาษาอื่น ๆ ที่ได้มาจาก SmallTalk

โซลูชันของเราซึ่ง นำไปใช้ใน Swift 5 คือการแนะนำแอตทริบิวต์ @dynamicCallable ใหม่เพื่อระบุว่าประเภท (เช่น PythonObject ) สามารถจัดการความละเอียดการโทรแบบไดนามิกได้ คุณลักษณะ @dynamicCallable ได้รับการปรับใช้และทำให้พร้อมใช้งานในโมดูล PythonKit interop

// Python: a = np.arange(15).reshape(3, 5)
let a = np.arange(15).reshape(3, 5)

// Python: d = np.array([1, 2, 3], dtype="i2")
let d = np.array([6, 7, 8], dtype: "i2")

เราคิดว่าสิ่งนี้ค่อนข้างน่าสนใจ และปิดช่องว่างการแสดงออกและหลักสรีรศาสตร์ที่เหลืออยู่สำหรับกรณีเหล่านี้ เราเชื่อว่าคุณสมบัตินี้จะเป็นทางออกที่ดีสำหรับ Ruby, Squeak และภาษาไดนามิกอื่นๆ ตลอดจนเป็นคุณสมบัติภาษา Swift ที่มีประโยชน์โดยทั่วไป ซึ่งสามารถใช้ได้กับไลบรารี Swift อื่นๆ

การจัดการข้อยกเว้นเทียบกับการจัดการข้อผิดพลาด

วิธีการจัดการข้อยกเว้นของ Python นั้นคล้ายคลึงกับ C++ และภาษาอื่นๆ มากมาย โดยที่นิพจน์ใดๆ สามารถสร้างข้อยกเว้นได้ตลอดเวลา และผู้เรียกสามารถเลือกที่จะจัดการ (หรือไม่) ได้อย่างอิสระ ในทางตรงกันข้าม วิธีการจัดการข้อผิดพลาด ของ Swift ทำให้ "ความสามารถในการโยน" เป็นส่วนที่ชัดเจนของสัญญา API ของวิธีการ และ บังคับให้ผู้เรียกจัดการ (หรืออย่างน้อยก็รับทราบ) ว่าข้อผิดพลาดสามารถเกิดขึ้นได้

นี่เป็นช่องว่างโดยธรรมชาติระหว่างทั้งสองภาษา และเราไม่ต้องการสรุปความแตกต่างนี้ด้วยส่วนขยายภาษา โซลูชันปัจจุบันของเราสำหรับสิ่งนี้สร้างขึ้นจากการสังเกตว่าแม้ว่าการเรียกใช้ฟังก์ชันใดๆ จะสามารถ ส่งได้ แต่การโทรส่วนใหญ่กลับไม่เป็นเช่นนั้น นอกจากนี้ เนื่องจาก Swift ทำให้การจัดการข้อผิดพลาดชัดเจนในภาษา จึงสมเหตุสมผลสำหรับโปรแกรมเมอร์ Python-in-Swift ที่จะคิดถึงจุดที่พวกเขาคาดหวังว่าข้อผิดพลาดจะถูกโยนทิ้งและจับได้ เราทำสิ่งนี้โดยใช้การฉายภาพ .throwing ที่ชัดเจนบน PythonObject นี่คือตัวอย่าง:

  // Open a file.  If this fails, the program is terminated, just like an
  // unhandled exception in Python.

  // file = open("foo.txt")
  let file = Python.open("foo.txt")
  // blob = file.read()
  let blob = file.read()

  // Open a file, a thrown "file not found" exception is turned into a Swift error.
  do {
    let file = try Python.open.throwing.dynamicallyCall("foo.txt")
    let blob = file.read()
    ...
  } catch {
    print(error)
  }

และแน่นอนว่าสิ่งนี้รวมเข้ากับกลไกปกติทั้งหมดที่ได้รับจากการจัดการข้อผิดพลาดของ Swift รวมถึงความสามารถในการใช้ try? หากคุณต้องการจัดการกับข้อผิดพลาดแต่ไม่สนใจรายละเอียดที่รวมอยู่ในข้อยกเว้น

การดำเนินการและสถานะปัจจุบัน

ดังที่ได้กล่าวไว้ข้างต้น การใช้งานไลบรารีการทำงานร่วมกันของ Python ในปัจจุบันของเรามีอยู่ใน GitHub ในไฟล์ Python.swift ในทางปฏิบัติ เราพบว่าวิธีนี้ทำงานได้ดีกับกรณีการใช้งานหลายๆ กรณี อย่างไรก็ตาม มีบางสิ่งที่ขาดหายไปซึ่งเราต้องพัฒนาและค้นหาต่อไป:

การแบ่งส่วน Python นั้นกว้างกว่าไวยากรณ์การแบ่งส่วนของ Swift ตอนนี้คุณสามารถเข้าถึงมันได้อย่างสมบูรณ์ผ่านฟังก์ชัน Python.slice(a, b, c) อย่างไรก็ตาม เราควรโยงไวยากรณ์ช่วง a...b ปกติจาก Swift และอาจเป็นเรื่องน่าสนใจที่จะพิจารณาใช้ตัวดำเนินการ Striding เป็นส่วนเสริมของไวยากรณ์ช่วงพื้นฐานนั้น เราจำเป็นต้องตรวจสอบและกำหนดโมเดลที่ถูกต้องเพื่อใช้สำหรับคลาสย่อยของคลาส Python ขณะนี้ยังไม่มีวิธีใดที่จะทำให้โครงสร้างเช่น PythonObject ทำงานร่วมกับการจับคู่รูปแบบทูเพิลได้ ดังนั้นเราจึงใช้คุณสมบัติการฉายภาพเช่น .tuple2 หากสิ่งนี้กลายเป็นปัญหาในทางปฏิบัติ เราสามารถตรวจสอบการเพิ่มสิ่งนี้ลงใน Swift ได้ แต่ขณะนี้เราไม่คิดว่าปัญหาจะเพียงพอที่จะแก้ไขได้ในระยะเวลาอันใกล้นี้

สรุปและสรุป

เรารู้สึกดีกับทิศทางนี้และคิดว่างานนี้มีหลายแง่มุมที่น่าสนใจ: เป็นเรื่องดีที่ไม่มีการเปลี่ยนแปลงเฉพาะของ Python ในคอมไพเลอร์หรือภาษา Swift เราสามารถบรรลุการทำงานร่วมกันของ Python ที่ดีผ่านไลบรารีที่เขียนด้วย Swift โดยการเขียนคุณสมบัติภาษาที่ไม่ขึ้นกับ Python เราเชื่อว่าชุมชนอื่นๆ จะสามารถเขียนชุดคุณลักษณะเดียวกันเพื่อบูรณาการโดยตรงกับภาษาไดนามิก (และรันไทม์) ที่มีความสำคัญต่อชุมชนอื่นๆ (เช่น JavaScript, Ruby ฯลฯ)

อีกแง่มุมที่น่าสนใจของงานนี้คือ การรองรับ Python นั้นเป็นอิสระอย่างสมบูรณ์จาก TensorFlow อื่นๆ และตรรกะการสร้างความแตกต่างอัตโนมัติที่เรากำลังสร้างโดยเป็นส่วนหนึ่งของ Swift สำหรับ TensorFlow นี่เป็นส่วนขยายที่มีประโยชน์โดยทั่วไปสำหรับระบบนิเวศ Swift ที่สามารถทำงานเดี่ยวๆ ได้ ซึ่งมีประโยชน์สำหรับการพัฒนาฝั่งเซิร์ฟเวอร์หรือสิ่งอื่นๆ ที่ต้องการทำงานร่วมกับ Python API ที่มีอยู่