Xử lý ngoại lệ kiểu chức năng


24

Tôi đã được thông báo rằng trong lập trình chức năng, người ta không được phép ném và / hoặc quan sát các ngoại lệ. Thay vào đó, một tính toán sai lầm nên được đánh giá là giá trị dưới cùng. Trong Python (hoặc các ngôn ngữ khác không hoàn toàn khuyến khích lập trình chức năng), người ta có thể trả về None(hoặc một ngôn ngữ thay thế khác được coi là giá trị dưới cùng, mặc dù Nonekhông tuân thủ đúng định nghĩa) bất cứ khi nào có lỗi xảy ra với "vẫn thuần túy", nhưng phải làm vì vậy người ta phải quan sát một lỗi ở nơi đầu tiên, tức là

def fn(*args):
    try:
        ... do something
    except SomeException:
        return None

Điều này có vi phạm sự tinh khiết? Và nếu vậy, điều đó có nghĩa là, không thể xử lý các lỗi hoàn toàn trong Python?

Cập nhật

Trong bình luận của mình, Eric Lippert đã nhắc nhở tôi về một cách khác để xử lý các trường hợp ngoại lệ trong FP. Mặc dù tôi chưa bao giờ thấy điều đó được thực hiện trong Python trong thực tế, tôi đã chơi với nó khi tôi học FP một năm trước. Ở đây, bất kỳ optionalhàm -decorated nào cũng trả về Optionalcác giá trị, có thể để trống, cho các đầu ra bình thường cũng như cho một danh sách các trường hợp ngoại lệ được chỉ định (các ngoại lệ không xác định vẫn có thể chấm dứt thực thi). Carrytạo ra một đánh giá bị trì hoãn, trong đó mỗi bước (lệnh gọi hàm bị trì hoãn) sẽ nhận được một Optionalđầu ra không trống từ bước trước đó và chỉ đơn giản là chuyển nó đi, hoặc nếu không thì tự đánh giá nó chuyển qua một cái mới Optional. Cuối cùng, giá trị cuối cùng là bình thường hoặc Empty. Ở đây, try/exceptkhối được ẩn đằng sau một bộ trang trí, vì vậy các ngoại lệ được chỉ định có thể được coi là một phần của chữ ký loại trả về.

class Empty:
    def __repr__(self):
        return "Empty"


class Optional:
    def __init__(self, value=Empty):
        self._value = value

    @property
    def value(self):
        return Empty if self.isempty else self._value

    @property
    def isempty(self):
        return isinstance(self._value, BaseException) or self._value is Empty

    def __bool__(self):
        raise TypeError("Optional has no boolean value")


def optional(*exception_types):
    def build_wrapper(func):
        def wrapper(*args, **kwargs):
            try:
                return Optional(func(*args, **kwargs))
            except exception_types as e:
                return Optional(e)
        wrapper.__isoptional__ = True
        return wrapper
    return build_wrapper


class Carry:
    """
    >>> from functools import partial
    >>> @optional(ArithmeticError)
    ... def rdiv(a, b):
    ...     return b // a
    >>> (Carry() >> (rdiv, 0) >> (rdiv, 0) >> partial(rdiv, 1))(1)
    1
    >>> (Carry() >> (rdiv, 0) >> (rdiv, 1))(1)
    1
    >>> (Carry() >> rdiv >> rdiv)(0, 1) is Empty
    True
    """
    def __init__(self, steps=None):
        self._steps = tuple(steps) if steps is not None else ()

    def _add_step(self, step):
        fn, *step_args = step if isinstance(step, Sequence) else (step, )
        return type(self)(steps=self._steps + ((fn, step_args), ))

    def __rshift__(self, step) -> "Carry":
        return self._add_step(step)

    def _evaluate(self, *args) -> Optional:
        def caller(carried: Optional, step):
            fn, step_args = step
            return fn(*(*step_args, *args)) if carried.isempty else carried
        return reduce(caller, self._steps, Optional())

    def __call__(self, *args):
        return self._evaluate(*args).value

1
Câu hỏi của bạn đã được trả lời, vì vậy chỉ cần một nhận xét: bạn có hiểu tại sao chức năng của bạn đưa ra một ngoại lệ được tán thành trong lập trình chức năng không? Đó không phải là một ý thích bất chợt :)
Andres F.

6
Có một cách khác để trả về một giá trị chỉ ra lỗi. Hãy nhớ rằng, xử lý ngoại lệ là luồng điều khiển và chúng tôi có các cơ chế chức năng để thống nhất các luồng điều khiển. Bạn có thể mô phỏng xử lý ngoại lệ trong các ngôn ngữ chức năng bằng cách viết phương thức của bạn để có hai chức năng: tiếp tục thành côngtiếp tục lỗi . Điều cuối cùng mà hàm của bạn thực hiện là gọi tiếp tục thành công, chuyển "kết quả" hoặc tiếp tục lỗi, chuyển "ngoại lệ". Mặt trái là bạn phải viết chương trình của mình theo kiểu từ trong ra ngoài này.
Eric Lippert

3
Không, những gì sẽ là nhà nước trong trường hợp này? Có nhiều vấn đề, nhưng đây là một vài vấn đề: 1- có một luồng có thể không được mã hóa theo loại, tức là bạn không thể biết liệu một hàm có ném ngoại lệ hay không chỉ bằng cách xem loại của nó (trừ khi bạn chỉ kiểm tra tất nhiên, ngoại lệ, nhưng tôi không biết bất kỳ ngôn ngữ nào chỉ có chúng). Bạn đang làm việc một cách hiệu quả "bên ngoài" hệ thống loại, 2 lập trình viên chức năng cố gắng viết các hàm "tổng" bất cứ khi nào có thể, tức là các hàm trả về một giá trị cho mỗi đầu vào (cấm không kết thúc). Ngoại lệ hoạt động chống lại điều này.
Andres F.

3
3- khi bạn làm việc với tổng số hàm, bạn có thể kết hợp chúng với các hàm khác và sử dụng chúng trong các hàm bậc cao hơn mà không phải lo lắng về kết quả lỗi "không được mã hóa theo kiểu", tức là ngoại lệ.
Andres F.

1
@EliKorvigo Đặt một biến giới thiệu trạng thái, theo định nghĩa, dừng hoàn toàn. Toàn bộ mục đích của các biến là chúng giữ trạng thái. Điều đó không có gì để làm với ngoại lệ. Quan sát giá trị trả về của hàm và đặt giá trị của biến dựa trên quan sát đưa trạng thái vào đánh giá, phải không?
dùng253751

Câu trả lời:


20

Trước hết, hãy làm sáng tỏ một số quan niệm sai lầm. Không có "giá trị dưới cùng". Loại dưới cùng được định nghĩa là một loại là một kiểu con của mọi loại khác trong ngôn ngữ. Từ điều này, người ta có thể chứng minh (ít nhất là trong bất kỳ hệ thống loại thú vị nào), rằng loại dưới cùng không có giá trị - nó trống . Vì vậy, không có thứ gọi là giá trị dưới cùng.

Tại sao loại dưới cùng hữu ích? Chà, biết rằng nó trống rỗng, chúng ta hãy suy luận về hành vi của chương trình. Ví dụ: nếu chúng ta có chức năng:

def do_thing(a: int) -> Bottom: ...

chúng tôi biết rằng do_thingkhông bao giờ có thể quay lại, vì nó sẽ phải trả về giá trị của loại Bottom. Vì vậy, chỉ có hai khả năng:

  1. do_thing không dừng lại
  2. do_thing ném một ngoại lệ (trong các ngôn ngữ có cơ chế ngoại lệ)

Lưu ý rằng tôi đã tạo một loại Bottomkhông thực sự tồn tại trong ngôn ngữ Python. Nonelà một cách gọi sai; nó thực sự là giá trị đơn vị , giá trị duy nhất của loại đơn vị , được gọi NoneTypebằng Python (làm type(None)để tự xác nhận).

Bây giờ, một quan niệm sai lầm khác là các ngôn ngữ chức năng không có ngoại lệ. Điều này cũng không đúng. SML chẳng hạn có một cơ chế ngoại lệ rất hay. Tuy nhiên, các ngoại lệ được sử dụng ít hơn nhiều trong SML so với Python. Như bạn đã nói, cách phổ biến để chỉ ra một số loại lỗi trong các ngôn ngữ chức năng là trả về một Optionloại. Ví dụ: chúng ta sẽ tạo một hàm phân chia an toàn như sau:

def safe_div(num: int, den: int) -> Option[int]:
  return Some(num/den) if den != 0 else None

Thật không may, vì Python không thực sự có các loại tổng, nên đây không phải là một cách tiếp cận khả thi. Bạn có thể trở lại Nonenhư một loại tùy chọn của một người nghèo để biểu thị sự thất bại, nhưng điều này thực sự không tốt hơn là trở về Null. Không có loại an toàn.

Vì vậy, tôi sẽ khuyên bạn nên tuân theo các quy ước của ngôn ngữ trong trường hợp này. Python sử dụng các ngoại lệ một cách tự nhiên để xử lý luồng điều khiển (thiết kế xấu, IMO, nhưng dù sao nó cũng là tiêu chuẩn), vì vậy trừ khi bạn chỉ làm việc với mã bạn tự viết, tôi khuyên bạn nên làm theo thông lệ tiêu chuẩn. Cho dù điều này là "tinh khiết" hay không là không liên quan.


Theo "giá trị dưới cùng" tôi thực sự có nghĩa là "loại dưới cùng", đó là lý do tại sao tôi viết mà Nonekhông tuân thủ định nghĩa. Dù sao, cảm ơn đã sửa chữa cho tôi. Bạn không nghĩ rằng chỉ sử dụng ngoại lệ để dừng thực thi hoàn toàn hoặc trả về một giá trị tùy chọn là ổn với các nguyên tắc của Python? Ý tôi là, tại sao việc hạn chế sử dụng ngoại lệ để kiểm soát phức tạp lại không tốt?
Eli Korvigo

@EliKorvigo Đó là những gì tôi nói ít nhiều, phải không? Ngoại lệ là Python thành ngữ.
vườn

1
Ví dụ, tôi không khuyến khích sinh viên đại học của mình sử dụng try/except/finallynhư một cách thay thế khác if/else, nghĩa là try: var = expession1; except ...: var = expression 2; except ...: var = expression 3..., mặc dù đó là điều phổ biến phải làm trong bất kỳ ngôn ngữ bắt buộc nào (btw, tôi cũng không khuyến khích sử dụng if/elsecác khối cho việc này). Ý bạn là, tôi không hợp lý và nên cho phép các mẫu như vậy vì "đây là Python"?
Eli Korvigo

@EliKorvigo Tôi đồng ý với bạn nói chung (btw, bạn là giáo sư?). khôngtry... catch... nên được sử dụng cho dòng điều khiển. Vì một số lý do, đó là cách cộng đồng Python quyết định làm mọi việc. Ví dụ, hàm tôi đã viết ở trên thường sẽ được viết . Vì vậy, nếu bạn đang dạy họ các nguyên tắc kỹ thuật phần mềm chung, bạn chắc chắn nên khuyến khích điều này. cũng là một mùi mã, nhưng quá lâu để đi vào đây. safe_divtry: result = num / div: except ArithmeticError: result = Noneif ... else...
vườn

2
Có một "giá trị dưới cùng" (ví dụ, nó được sử dụng để nói về ngữ nghĩa của Haskell) và nó không liên quan nhiều đến loại dưới cùng. Vì vậy, đó thực sự không phải là một quan niệm sai lầm về OP, chỉ nói về một điều khác.
Ben

11

Vì đã có rất nhiều sự quan tâm đến độ tinh khiết trong vài ngày qua, tại sao chúng ta không kiểm tra xem một hàm thuần túy trông như thế nào.

Một hàm thuần túy:

  • Được tham chiếu minh bạch; nghĩa là, đối với một đầu vào nhất định, nó sẽ luôn tạo ra cùng một đầu ra.

  • Không tạo ra tác dụng phụ; nó không thay đổi đầu vào, đầu ra hoặc bất cứ thứ gì khác trong môi trường bên ngoài của nó. Nó chỉ tạo ra một giá trị trả về.

Vậy hãy tự hỏi. Chức năng của bạn có làm gì ngoài việc chấp nhận đầu vào và trả lại đầu ra không?


3
Vì vậy, nó không thành vấn đề, nó xấu xí được viết bên trong chừng nào nó còn hoạt động đúng chức năng?
Eli Korvigo

8
Đúng, nếu bạn chỉ quan tâm đến độ tinh khiết. Tất nhiên, có thể có những thứ khác để xem xét.
Robert Harvey

3
Để chơi những người ủng hộ quỷ, tôi lập luận rằng ném ngoại lệ chỉ là một hình thức đầu ra và các chức năng ném là thuần túy. Đó có phải là vấn đề không?
Bergi

1
@Bergi Tôi không biết rằng bạn đang chơi người ủng hộ của quỷ, vì đó chính xác là những gì câu trả lời này ngụ ý :) Vấn đề là có những cân nhắc khác bên cạnh sự thuần khiết. Nếu bạn cho phép các ngoại lệ không được kiểm tra (theo định nghĩa không phải là một phần của chữ ký của hàm) thì kiểu trả về của mọi hàm thực sự trở thành T + { Exception }(trong đó Tkiểu trả về được khai báo rõ ràng), đó là vấn đề. Bạn không thể biết liệu một hàm sẽ đưa ra một ngoại lệ mà không cần nhìn vào mã nguồn của nó, điều này làm cho việc viết các hàm bậc cao hơn cũng có vấn đề.
Andres F.

2
@Begri trong khi ném có thể được cho là thuần túy, IMO sử dụng các ngoại lệ không chỉ làm phức tạp kiểu trả về của mỗi chức năng. Nó làm hỏng khả năng kết hợp của các chức năng. Xem xét việc thực hiện map : (A->B)->List A ->List Bnơi A->Bcó thể lỗi. Nếu chúng ta cho phép f ném một ngoại lệ map f L sẽ trả về một cái gì đó thuộc loại Exception + List<B>. Nếu thay vào đó, chúng tôi cho phép nó trả về một optionalkiểu kiểu map f Lthay vào đó sẽ trả về Danh sách <Tùy chọn <B >> `. Tùy chọn thứ hai này cảm thấy nhiều chức năng hơn đối với tôi.
Michael Anderson

7

Ngữ nghĩa Haskell sử dụng "giá trị dưới cùng" để phân tích ý nghĩa của mã Haskell. Đó không phải là thứ bạn thực sự sử dụng trực tiếp trong lập trình Haskell, và việc trở lại hoàn toàn Nonekhông phải là điều tương tự.

Giá trị dưới cùng là giá trị được gán bởi ngữ nghĩa Haskell cho bất kỳ tính toán nào không đánh giá thành giá trị thông thường. Một cách như vậy một tính toán Haskell có thể làm điều đó thực sự là bằng cách ném một ngoại lệ! Vì vậy, nếu bạn đang cố gắng sử dụng phong cách này trong Python, bạn thực sự chỉ nên ném ngoại lệ như bình thường.

Ngữ nghĩa Haskell sử dụng giá trị dưới cùng vì Haskell lười biếng; bạn có thể thao tác "các giá trị" được trả về bằng các tính toán chưa thực sự chạy. Bạn có thể chuyển chúng cho các hàm, gắn chúng vào các cấu trúc dữ liệu, v.v ... Một phép tính không được đánh giá như vậy có thể tạo ra một ngoại lệ hoặc vòng lặp mãi mãi, nhưng nếu chúng ta thực sự không cần phải kiểm tra giá trị thì tính toán sẽ không bao giờchạy và gặp lỗi, và chương trình tổng thể của chúng tôi có thể quản lý để làm một cái gì đó được xác định rõ và hoàn thành. Vì vậy, không muốn giải thích mã Haskell có nghĩa là gì bằng cách chỉ định hành vi hoạt động chính xác của chương trình trong thời gian chạy, thay vào đó chúng tôi khai báo các tính toán sai lầm đó tạo ra giá trị dưới cùng và giải thích giá trị đó hoạt động như thế nào; về cơ bản, bất kỳ biểu thức nào cần phụ thuộc vào bất kỳ thuộc tính nào ở tất cả các giá trị dưới cùng (ngoài giá trị hiện có) cũng sẽ dẫn đến giá trị dưới cùng.

Để duy trì "thuần túy", tất cả các cách có thể tạo giá trị dưới cùng phải được coi là tương đương. Điều đó bao gồm "giá trị dưới cùng" đại diện cho một vòng lặp vô hạn. Vì không có cách nào để biết rằng một số vòng lặp vô hạn thực sự là vô hạn (chúng có thể kết thúc nếu bạn chỉ chạy chúng lâu hơn một chút), bạn không thể kiểm tra bất kỳ thuộc tính nào có giá trị dưới cùng. Bạn không thể kiểm tra xem một cái gì đó ở dưới cùng, không thể so sánh nó với bất cứ thứ gì khác, không thể chuyển đổi nó thành một chuỗi, không có gì. Tất cả những gì bạn có thể làm với một là đặt nó vào vị trí (tham số chức năng, một phần của cấu trúc dữ liệu, v.v.) không bị ảnh hưởng và không được minh họa.

Python đã có loại đáy này; đó là "giá trị" bạn nhận được từ một biểu thức đưa ra một ngoại lệ hoặc không chấm dứt. Vì Python nghiêm ngặt thay vì lười biếng, những "đáy" như vậy không thể được lưu trữ ở bất cứ đâu và có khả năng không được minh chứng. Vì vậy, không có nhu cầu thực sự sử dụng khái niệm giá trị dưới cùng để giải thích cách tính toán không trả về giá trị vẫn có thể được xử lý như thể chúng có giá trị. Nhưng cũng không có lý do gì bạn không thể nghĩ theo cách này về các ngoại lệ nếu bạn muốn.

Ném ngoại lệ thực sự được coi là "tinh khiết". Nó bắt các ngoại lệ phá vỡ sự tinh khiết - chính xác bởi vì nó cho phép bạn kiểm tra một cái gì đó về các giá trị dưới cùng nhất định, thay vì xử lý tất cả chúng thay thế cho nhau. Trong Haskell, bạn chỉ có thể bắt ngoại lệ trong trường hợp IOcho phép giao thoa không tinh khiết (vì vậy nó thường xảy ra ở một lớp khá bên ngoài). Python không thực thi độ tinh khiết, nhưng bạn vẫn có thể tự quyết định chức năng nào là một phần của "lớp không tinh khiết bên ngoài" thay vì các hàm thuần túy và chỉ cho phép bản thân bắt ngoại lệ ở đó.

Trở về Nonethay thế là hoàn toàn khác nhau. Nonelà một giá trị không đáy; bạn có thể kiểm tra xem có thứ gì đó bằng với nó không, và người gọi hàm trả về Nonesẽ tiếp tục chạy, có thể sử dụng Nonekhông đúng cách.

Vì vậy, nếu bạn đã nghĩ đến việc ném một ngoại lệ và muốn "trở về đáy" để mô phỏng phương pháp của Haskell, bạn chẳng làm gì cả. Hãy để ngoại lệ tuyên truyền. Đó chính xác là những gì các lập trình viên Haskell muốn nói khi họ nói về một hàm trả về giá trị dưới cùng.

Nhưng đó không phải là ý nghĩa của các lập trình viên khi họ nói để tránh ngoại lệ. Các lập trình viên chức năng thích "tổng số chức năng". Chúng luôn trả về giá trị không dưới cùng hợp lệ của kiểu trả về của chúng cho mọi đầu vào có thể. Vì vậy, bất kỳ hàm nào có thể đưa ra một ngoại lệ không phải là một hàm tổng.

Lý do chúng tôi thích tổng số chức năng là chúng dễ dàng được coi là "hộp đen" hơn khi chúng tôi kết hợp và thao tác chúng. Nếu tôi có một hàm tổng trả về một cái gì đó thuộc loại A và một hàm tổng chấp nhận một cái gì đó thuộc loại A, thì tôi có thể gọi hàm thứ hai trên đầu ra của đầu tiên, mà không biết về việc thực hiện một trong hai; Tôi biết tôi sẽ nhận được một kết quả hợp lệ, bất kể mã của một trong hai chức năng được cập nhật như thế nào (miễn là tổng số của chúng được duy trì và miễn là chúng giữ chữ ký cùng loại). Sự tách biệt mối quan tâm này có thể là một trợ giúp cực kỳ mạnh mẽ để tái cấu trúc.

Nó cũng hơi cần thiết cho các hàm bậc cao đáng tin cậy (các hàm thao tác các hàm khác). Nếu tôi muốn viết mã nhận một hàm hoàn toàn tùy ý (với giao diện đã biết) làm tham số, tôi phải coi nó là hộp đen vì tôi không có cách nào biết đầu vào nào có thể gây ra lỗi. Nếu tôi được cung cấp một hàm tổng thì không có đầu vào nào gây ra lỗi. Tương tự, người gọi hàm chức năng bậc cao của tôi sẽ không biết chính xác những đối số tôi sử dụng để gọi hàm họ chuyển cho tôi (trừ khi họ muốn phụ thuộc vào chi tiết triển khai của tôi), vì vậy việc chuyển một hàm tổng có nghĩa là họ không phải lo lắng về những gì tôi làm với nó

Vì vậy, một lập trình viên chức năng khuyên bạn tránh các trường hợp ngoại lệ sẽ thích bạn hơn là trả về một giá trị mã hóa lỗi hoặc giá trị hợp lệ và yêu cầu sử dụng nó, bạn phải chuẩn bị để xử lý cả hai khả năng. Những thứ như Eitherkiểu hoặc Maybe/ Optionloại là một số cách tiếp cận đơn giản nhất để thực hiện điều này bằng các ngôn ngữ được gõ mạnh hơn (thường được sử dụng với các cú pháp đặc biệt hoặc các hàm bậc cao hơn để giúp gắn kết những thứ cần Avới một thứ tạo ra a Maybe<A>).

Một hàm trả về None(nếu xảy ra lỗi) hoặc một số giá trị (nếu không có lỗi) sẽ không tuân theo các chiến lược trên.

Trong Python với vịt, kiểu Either / Should không được sử dụng nhiều, thay vào đó, hãy để ngoại lệ được ném ra, với các thử nghiệm để xác thực rằng mã hoạt động thay vì tin tưởng các hàm là tổng và tự động kết hợp dựa trên các loại của chúng. Python không có cơ sở để thực thi mã đó sử dụng những thứ như kiểu Có thể đúng; ngay cả khi bạn đang sử dụng nó như một vấn đề kỷ luật, bạn cần các bài kiểm tra để thực sự thực thi mã của mình để xác nhận điều đó. Vì vậy, cách tiếp cận ngoại lệ / dưới cùng có lẽ phù hợp hơn với lập trình hàm thuần túy trong Python.


1
+1 câu trả lời tuyệt vời! Và rất toàn diện là tốt.
Andres F.

6

Miễn là không có tác dụng phụ có thể nhìn thấy bên ngoài và giá trị trả về phụ thuộc hoàn toàn vào các yếu tố đầu vào, thì chức năng này là thuần túy, ngay cả khi nó thực hiện một số thứ không trong sạch.

Vì vậy, nó thực sự phụ thuộc vào chính xác những gì có thể gây ra ngoại lệ được ném. Nếu bạn đang cố mở một tệp được cung cấp một đường dẫn, thì không, nó không thuần túy, vì tệp có thể tồn tại hoặc không tồn tại, điều này sẽ khiến giá trị trả về thay đổi cho cùng một đầu vào.

Mặt khác, nếu bạn đang cố phân tích một số nguyên từ một chuỗi đã cho và ném một ngoại lệ nếu nó không thành công, điều đó có thể là thuần túy, miễn là không có ngoại lệ nào có thể thoát ra khỏi hàm của bạn.

Mặt khác, các ngôn ngữ chức năng có xu hướng chỉ trả về loại đơn vị nếu chỉ có một điều kiện lỗi có thể xảy ra. Nếu có nhiều lỗi có thể xảy ra, chúng có xu hướng trả về một loại lỗi có thông tin về lỗi.


Bạn không thể trả về loại dưới cùng.
vườn

1
@gardenhead Bạn nói đúng, tôi đã nghĩ đến loại đơn vị. Đã sửa.
8

Trong trường hợp ví dụ đầu tiên của bạn, không phải là hệ thống tệp và những tệp nào tồn tại trong đó chỉ đơn giản là một trong những đầu vào của hàm?
Vality

2
@Vaility Trong thực tế, bạn có thể có một tay cầm hoặc đường dẫn đến tệp. Nếu hàm flà thuần túy, bạn sẽ f("/path/to/file")luôn trả về cùng một giá trị. Điều gì xảy ra nếu tệp thực tế bị xóa hoặc thay đổi giữa hai lần gọi thành f?
Andres F.

2
@Vaility Hàm có thể hoàn toàn nếu thay vì xử lý tệp bạn đã truyền cho nó nội dung thực tế (tức là ảnh chụp chính xác byte) của tệp, trong trường hợp đó bạn có thể đảm bảo rằng nếu cùng một nội dung đi vào, cùng một đầu ra đi ra. Nhưng việc truy cập một tập tin không phải như thế :)
Andres F.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.