Xác định biểu thức lambda làm tăng Ngoại lệ


137

Làm cách nào tôi có thể viết biểu thức lambda tương đương với:

def x():
    raise Exception()

Những điều sau đây không được phép:

y = lambda : raise Exception()

2
Vì vậy, bạn không thể làm điều đó. Sử dụng các chức năng bình thường.
DrTyrsa

1
Điểm đặt tên cho một chức năng ẩn danh là gì?
John La Rooy

2
@gnibbler Bạn có thể sử dụng tên để chỉ chức năng. y () dễ sử dụng hơn (lambda: 0) () trong REPL.
Thomas Jung

Vì vậy, lợi thế của y=lambda...hơn def y:là gì?
John La Rooy

@gnibbler Một số ngữ cảnh: Tôi muốn xác định hàm def g (f, e) gọi f trong trường hợp hạnh phúc và e nếu phát hiện lỗi. Tùy thuộc vào kịch bản e có thể đưa ra một ngoại lệ hoặc trả về một số giá trị hợp lệ. Để sử dụng g tôi muốn viết g (lambda x: x * 2, lambda e: nâng e) hoặc thay thế g (lambda x: x * 2, lambda e: 0).
Thomas Jung

Câu trả lời:


169

Có nhiều hơn một cách để tạo một Python:

y = lambda: (_ for _ in ()).throw(Exception('foobar'))

Lambdas chấp nhận tuyên bố. Vì raise exlà một tuyên bố, bạn có thể viết một trình tăng mục đích chung:

def raise_(ex):
    raise ex

y = lambda: raise_(Exception('foobar'))

Nhưng nếu mục tiêu của bạn là tránh a def, điều này rõ ràng là không cắt giảm. Tuy nhiên, nó cho phép bạn đưa ra các ngoại lệ một cách có điều kiện, ví dụ:

y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))

Ngoài ra, bạn có thể đưa ra một ngoại lệ mà không cần xác định chức năng được đặt tên. Tất cả bạn cần là một dạ dày mạnh mẽ (và 2.x cho mã đã cho):

type(lambda:0)(type((lambda:0).func_code)(
  1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{}
)(Exception())

Và một giải pháp dạ dày mạnh python3 :

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())

Cảm ơn @WarrenSpencer đã chỉ ra một câu trả lời rất đơn giản nếu bạn không quan tâm ngoại lệ nào được nêu ra : y = lambda: 1/0.


117
OMG nghệ thuật bóng tối đó là gì?
CodeColorist

11
Nếu bạn không quan tâm loại ngoại lệ nào được ném, thì những điều sau đây cũng hoạt động : lambda: 1 / 0. Bạn sẽ kết thúc với việc ZeroDivisionError bị ném thay vì một ngoại lệ thông thường. Hãy nhớ rằng nếu ngoại lệ được phép đưa ra, có vẻ lạ khi ai đó gỡ lỗi mã của bạn để bắt đầu nhìn thấy một loạt ZeroDivisionErrors.
Warren Spencer

Giải pháp tuyệt vời @WarrenSpencer. Hầu hết các mã không có nhiều lỗi phân chia bằng 0, do đó, nó đặc biệt như thể bạn có thể tự chọn loại.
jwg

2
y = 1/0là giải pháp siêu thông minh nếu loại Ngoại lệ không liên quan
Saher Ahwal

3
Bất cứ ai cũng có thể nói với chúng tôi thông qua những gì đang thực sự diễn ra trong các giải pháp 'nghệ thuật tối / dạ dày mạnh mẽ'?
quyết định

56

Làm thế nào về:

lambda x: exec('raise(Exception(x))')

12
Nó khá hack nhưng để viết các bài kiểm tra mà bạn muốn mô phỏng các chức năng này hoạt động gọn gàng !!!
Kannan Ekanath

8
Hoạt động nhưng bạn không nên làm điều đó.
tháng tám

1
Điều này không phù hợp với tôi, tôi nhận được SyntaxErrortrên Python 2.7.11.
Nick Sweeting

Tôi cũng nhận được lỗi trên (SyntaxError) trên Python 2.7.5
Dinesh

1
điều này đặc trưng cho python 3 tuy nhiên tôi không nghĩ python 2 cho phép điều đó.
Saher Ahwal

16

Trên thực tế, có một cách, nhưng nó rất giả tạo.

Bạn có thể tạo một đối tượng mã bằng hàm compile()dựng sẵn. Điều này cho phép bạn sử dụng raisecâu lệnh (hoặc bất kỳ câu lệnh nào khác, cho vấn đề đó), nhưng nó đặt ra một thách thức khác: thực thi đối tượng mã. Cách thông thường sẽ là sử dụng execcâu lệnh, nhưng điều đó dẫn bạn trở lại vấn đề ban đầu, cụ thể là bạn không thể thực thi các câu lệnh trong một lambda(hoặc một eval(), cho vấn đề đó).

Giải pháp là hack. Các hàm gọi như kết quả của một lambdacâu lệnh đều có một thuộc tính __code__, mà thực sự có thể được thay thế. Vì vậy, nếu bạn tạo một __code__giá trị có thể gọi được và thay thế giá trị của nó bằng đối tượng mã từ phía trên, bạn sẽ có được thứ gì đó có thể được đánh giá mà không cần sử dụng câu lệnh. Đạt được tất cả điều này, mặc dù, dẫn đến mã rất tối nghĩa:

map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()

Trên đây là như sau:

  • các compile()cuộc gọi tạo ra một đối tượng mã mà làm tăng ngoại lệ;

  • các lambda: 0lợi nhuận một callable mà không làm gì nhưng trả về giá trị 0 - điều này được sử dụng để thực hiện các đối tượng mã trên sau;

  • các lambda x, y, ztạo ra một chức năng mà các cuộc gọi __setattr__phương pháp của đối số đầu tiên với các đối số còn lại, và trả về đối số đầu tiên! Điều này là cần thiết, bởi vì __setattr__chính nó trở lại None;

  • các map()cuộc gọi có kết quả của lambda: 0, và sử dụng lambda x, y, zthay thế nó của __code__đối tượng với kết quả của các compile()cuộc gọi. Kết quả của hoạt động bản đồ này là một danh sách với một mục nhập, mục được trả về lambda x, y, z, đó là lý do tại sao chúng ta cần điều này lambda: nếu chúng ta sử dụng __setattr__ngay, chúng ta sẽ mất tham chiếu đến lambda: 0đối tượng!

  • cuối cùng, phần tử đầu tiên (và duy nhất) của danh sách được trả về bởi lệnh map()gọi được thực thi, dẫn đến đối tượng mã được gọi, cuối cùng đưa ra ngoại lệ mong muốn.

Nó hoạt động (được thử nghiệm trong Python 2.6), nhưng nó chắc chắn không đẹp.

Một lưu ý cuối cùng: nếu bạn có quyền truy cập vào typesmô-đun (sẽ yêu cầu sử dụng importcâu lệnh trước eval), thì bạn có thể rút ngắn mã này xuống một chút: sử dụng types.FunctionType()bạn có thể tạo một hàm sẽ thực thi đối tượng mã đã cho, vì vậy bạn đã thắng Không cần hack tạo hàm giả lambda: 0và thay thế giá trị __code__thuộc tính của nó .



15

Nếu tất cả những gì bạn muốn là một biểu thức lambda đưa ra một ngoại lệ tùy ý, bạn có thể thực hiện điều này với một biểu thức bất hợp pháp. Chẳng hạn, lambda x: [][0]sẽ cố gắng truy cập phần tử đầu tiên trong một danh sách trống, điều này sẽ làm tăng IndexError.

XIN LƯU Ý : Đây là một hack, không phải là một tính năng. Không sử dụng mã này trong bất kỳ mã (không phải mã golf) nào mà người khác có thể nhìn thấy hoặc sử dụng.


Trong trường hợp của tôi, tôi nhận được : TypeError: <lambda>() takes exactly 1 positional argument (2 given). Bạn có chắc chắn về IndexError?
Jovik

4
Vâng. Có lẽ bạn đã cung cấp sai số lượng đối số? Nếu bạn cần một hàm lambda có thể nhận bất kỳ số lượng đối số, sử dụng lambda *x: [][0]. (Phiên bản gốc chỉ có một đối số; không có đối số, sử dụng lambda : [][0]; đối với hai, sử dụng lambda x,y: [][0]; v.v.)
Kyle Strand

3
Tôi đã mở rộng điều này một chút: lambda x: {}["I want to show this message. Called with: %s" % x] Sản xuất: KeyError: 'I want to show this message. Called with: foo'
ErlVolton

@ErlVolton Thông minh! Mặc dù sử dụng điều này ở bất cứ đâu ngoại trừ trong kịch bản một lần có vẻ như là một ý tưởng tồi tệ ...
Kyle Strand

Tôi đang tạm thời sử dụng trong các bài kiểm tra đơn vị cho một dự án mà tôi không bận tâm để tạo ra một bản giả thực sự của bộ ghi chép của tôi. Nó tăng nếu bạn cố gắng đăng nhập một lỗi hoặc nghiêm trọng. Vì vậy, ... Thật khủng khiếp, mặc dù đồng thuận :)
ErlVolton

10

Tôi muốn đưa ra lời giải thích về CẬP NHẬT 3 của câu trả lời được cung cấp bởi Marcelo Cantos:

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())

Giải trình

lambda: 0là một ví dụ của builtins.functionlớp.
type(lambda: 0)builtins.functionlớp học
(lambda: 0).__code__là một codeđối tượng.
Một codeđối tượng là một đối tượng chứa mã byte được biên dịch trong số những thứ khác. Nó được định nghĩa ở đây trong CPython https://github.com/python/cpython/blob/master/Include/code.h . Các phương thức của nó được triển khai tại đây https://github.com/python/cpython/blob/master/Objects/codeobject.c . Chúng ta có thể chạy trợ giúp trên đối tượng mã:

Help on code object:

class code(object)
 |  code(argcount, kwonlyargcount, nlocals, stacksize, flags, codestring,
 |        constants, names, varnames, filename, name, firstlineno,
 |        lnotab[, freevars[, cellvars]])
 |  
 |  Create a code object.  Not for the faint of heart.

type((lambda: 0).__code__)là lớp mã.
Vì vậy, khi chúng ta nói

type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')

chúng ta đang gọi hàm tạo của đối tượng mã với các đối số sau:

  • đối số = 1
  • kwonlyargcount = 0
  • nlocals = 1
  • xếp chồng = 1
  • cờ = 67
  • mã hóa = b '| \ 0 \ 202 \ 1 \ 0'
  • hằng số = ()
  • tên = ()
  • tên biến = ('x',)
  • tên tệp = ''
  • tên = ''
  • Firstlineno = 1
  • lnotab = b ''

Bạn có thể đọc về ý nghĩa của các đối số trong định nghĩa của PyCodeObject https://github.com/python/cpython/blob/master/Include/code.h . Giá trị 67 choflags đối số là ví dụ CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE.

Đối số nhập khẩu nhiều nhất là đối số codestringchứa opcodes lệnh. Hãy xem ý nghĩa của chúng.

>>> import dis
>>> dis.dis(b'|\0\202\1\0')
          0 LOAD_FAST                0 (0)
          2 RAISE_VARARGS            1
          4 <0>

Tài liệu về opcodes có thể được tìm thấy ở đây https://docs.python.org/3.8/l Library / dis.html.htmlpypy-bytecode-in thi . Byte đầu tiên là opcode cho LOAD_FAST, byte thứ hai là đối số của nó tức là 0.

LOAD_FAST(var_num)
    Pushes a reference to the local co_varnames[var_num] onto the stack.

Vì vậy, chúng tôi đẩy tham chiếu xlên ngăn xếp. Cácvarnames là danh sách các chuỗi chỉ chứa 'x'. Chúng ta sẽ đẩy đối số duy nhất của hàm mà chúng ta đang xác định vào ngăn xếp.

Byte tiếp theo là opcode cho RAISE_VARARGSvà byte tiếp theo là đối số của nó tức là 1.

RAISE_VARARGS(argc)
    Raises an exception using one of the 3 forms of the raise statement, depending on the value of argc:
        0: raise (re-raise previous exception)
        1: raise TOS (raise exception instance or type at TOS)
        2: raise TOS1 from TOS (raise exception instance or type at TOS1 with __cause__ set to TOS)

ĐKDV là đỉnh cao nhất. Vì chúng ta đã đẩy đối số đầu tiên ( x) của hàm của chúng ta lên ngăn xếp và argclà 1, chúng ta sẽ nâng lên xnếu đó là một trường hợp ngoại lệ hoặc tạo một thể hiện xvà nâng nó theo cách khác.

Byte cuối cùng tức là 0 không được sử dụng. Nó không phải là một opcode hợp lệ. Nó cũng có thể không ở đó.

Quay trở lại đoạn mã chúng tôi đang cố gắng:

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())

Chúng tôi đã gọi hàm tạo của đối tượng mã:

type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')

Chúng ta truyền đối tượng mã và một từ điển trống cho hàm tạo của đối tượng hàm:

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)

Chúng ta hãy gọi trợ giúp về một đối tượng hàm để xem các đối số có nghĩa là gì.

Help on class function in module builtins:

class function(object)
 |  function(code, globals, name=None, argdefs=None, closure=None)
 |  
 |  Create a function object.
 |  
 |  code
 |    a code object
 |  globals
 |    the globals dictionary
 |  name
 |    a string that overrides the name from the code object
 |  argdefs
 |    a tuple that specifies the default argument values
 |  closure
 |    a tuple that supplies the bindings for free variables

Sau đó, chúng ta gọi hàm được xây dựng đi qua một thể hiện Ngoại lệ làm đối số. Do đó, chúng tôi đã gọi một hàm lambda làm tăng ngoại lệ. Hãy chạy đoạn trích và thấy rằng nó thực sự hoạt động như dự định.

>>> type(lambda: 0)(type((lambda: 0).__code__)(
...     1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
... )(Exception())
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File "", line 1, in 
Exception

Cải tiến

Chúng tôi đã thấy rằng byte cuối cùng của mã byte là vô dụng. Chúng ta đừng lộn xộn biểu hiện phức tạp này. Hãy xóa byte đó. Ngoài ra, nếu chúng ta muốn chơi gôn một chút, chúng ta có thể bỏ qua phần khởi tạo của Ngoại lệ và thay vào đó vượt qua lớp Ngoại lệ làm đối số. Những thay đổi đó sẽ dẫn đến mã sau đây:

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{}
)(Exception)

Khi chúng tôi chạy nó, chúng tôi sẽ nhận được kết quả như trước đây. Nó chỉ ngắn hơn.

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.