-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy path.cursorrules
2008 lines (1435 loc) · 91.5 KB
/
.cursorrules
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Start every few answers with a joke on programmers and Python, a non compiled language for slobby develoopers.
We program in Python, following functional programming principles where appropriate.
Core Development Practices:
1. We format with Ruff
2. We code according to Google's Python Style Guide with emphasis on:
- Clean, readable code
- Proper documentation using Google-style docstrings
- Type hints for all public APIs
- Maximum line length of 80 characters
- 4 spaces for indentation (no tabs)
Code Organization:
1. Imports should be grouped and ordered:
- Future imports
- Standard library imports
- Third-party imports
- Local application imports
2. Classes and Functions:
- Use clear, descriptive names following snake_case for functions/variables
- Use PascalCase for class names
- Include type hints for parameters and return values
- Write comprehensive docstrings for public APIs
- Keep functions focused and preferably under 40 lines
3. Error Handling:
- Use explicit exception handling
- Avoid bare except clauses
- Document expected exceptions in docstrings
4. Variables and Types:
- Use meaningful variable names
- Avoid single-letter names except for counters/iterators
- Use type hints with modern Python syntax (|) for unions
- Prefer built-in types for annotations where possible
5. Comments and Documentation:
- Write clear, concise comments explaining "why" not "what"
- Use TODO comments with links to issues/bugs
- Include docstrings for all public functions/classes
- Follow Google docstring format
6. Testing:
- Write unit tests for all functionality
- Use descriptive test names
- Follow test*<method>*<state> naming convention
7. Code Style:
- Use f-strings for string formatting
- Avoid complex comprehensions
- Use context managers for resource management
- Keep code modular and maintainable
Here's the Read
Remember to prioritize code readability and maintainability over clever solutions.
###
### BEGIN-README
# Speaking Meeting Bot Documentation
This document provides step-by-step instructions on how to set up and run a Speaking Meeting Bot, which utilizes MeetingBaas's APIs and pipecat's `WebsocketServerTransport` to participate in online meetings as a speaking bot.
## Prerequisites
- Python 3.x installed
- `grpc_tools` for handling gRPC protobuf files
- Ngrok for exposing your local server to the internet
- Poetry for managing dependencies
## Getting Started
### Step 1: Set Up the Virtual Environment
To begin, you need to set up the Python environment using Poetry and install the required dependencies.
```bash
# Install Poetry if not already installed
# For Unix/macOS:
curl -sSL https://install.python-poetry.org | python3 -
# For Windows:
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
# Install the required dependencies using Poetry
poetry install
# Activate the virtual environment
poetry shell
```
### Step 2: Compile Protocol Buffers
To enable communication with MeetingBaas's API, you need to compile the `frames.proto` file with the `grpc_tools`.
```bash
# Compile the protobuf file
poetry run python -m grpc_tools.protoc --proto_path=./protobufs --python_out=./protobufs frames.proto
```
### Step 3: Set Up Environment Variables
You need to provide the necessary credentials for MeetingBaas's API.
```bash
# Copy the example environment file
cp env.example .env
```
Now, open the `.env` file and update it with your MeetingBaas credentials.
## Running the Speaking Meeting Bot
Once your setup is complete, follow these steps to run the bot and connect it to an online meeting.
### Step 1: Run the Bot
Run the Python script to start the Speaking Meeting Bot:
```bash
poetry run bot
poetry run proxy
```
### Step 2: Set Up Ngrok to Expose Local Server
To allow MeetingBaas to communicate with your bot, you need to expose the local server using Ngrok.
```bash
# Run the Ngrok HTTP tunnel on port 8766
ngrok http 8766
```
Ngrok will provide you with a public URL that can be used by MeetingBaas to communicate with your local bot.
### Step 3: Start the MeetingBaas Bot
The final step is to run the MeetingBaas bot script to connect it with the desired meeting session.
```bash
poetry run meetingbaas
```
Now, visit the meeting URL in your browser to initiate a session and watch your bot actively participate in the meeting!
## Troubleshooting Tips
- Ensure that you have activated the Poetry environment before running any Python commands.
- If Ngrok is not running properly, check for any firewall issues that may be blocking its communication.
- Double-check the `.env` file to make sure all necessary credentials are correctly filled in.
## Additional Information
- MeetingBaas allows integration with external bots using APIs that leverage the `WebsocketServerTransport` for real-time communication.
- For more details on the MeetingBaas APIs and functionalities, please refer to the official MeetingBaas documentation.
## Example Usage
After setting up everything, the bot will actively join the meeting and communicate using the MeetingBaas WebSocket API. You can test different bot behaviors by modifying the `meetingbaas.py` script to suit your meeting requirements.
Happy meeting automation!
### END-README
###
### Pipecat Examples of which this is inspired:
Pipecat — Examples
Foundational snippets
Small snippets that build on each other, introducing one or two concepts at a time.
➡️ Take a look
Chatbot examples
Collection of self-contained real-time voice and video AI demo applications built with Pipecat.
Quickstart
Each project has its own set of dependencies and configuration variables. They intentionally avoids shared code across projects — you can grab whichever demo folder you want to work with as a starting point.
We recommend you start with a virtual environment:
cd pipecat-ai/examples/simple-chatbot
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Next, follow the steps in the README for each demo.
ℹ️ Make sure you pip install -r requirements.txt for each demo project, so you can be sure to have the necessary service dependencies that extend the functionality of Pipecat. You can read more about the framework architecture here.
Projects:
Project Description Services
Simple Chatbot Basic voice-driven conversational bot. A good starting point for learning the flow of the framework. Deepgram, ElevenLabs, OpenAI, Daily, Daily Prebuilt UI
Storytelling Chatbot Stitches together multiple third-party services to create a collaborative storytime experience. Deepgram, ElevenLabs, OpenAI, Fal, Daily, Custom UI
Translation Chatbot Listens for user speech, then translates that speech to Spanish and speaks the translation back. Demonstrates multi-participant use-cases. Deepgram, Azure, OpenAI, Daily, Daily Prebuilt UI
Moondream Chatbot Demonstrates how to add vision capabilities to GPT4. Note: works best with a GPU Deepgram, ElevenLabs, OpenAI, Moondream, Daily, Daily Prebuilt UI
Patient intake A chatbot that can call functions in response to user input. Deepgram, ElevenLabs, OpenAI, Daily, Daily Prebuilt UI
Dialin Chatbot A chatbot that connects to an incoming phone call from Daily or Twilio. Deepgram, ElevenLabs, OpenAI, Daily, Twilio
Twilio Chatbot A chatbot that connects to an incoming phone call from Twilio. Deepgram, ElevenLabs, OpenAI, Daily, Twilio
studypal A chatbot to have a conversation about any article on the web
Important
These example projects use Daily as a WebRTC transport and can be joined using their hosted Prebuilt UI. It provides a quick way to join a real-time session with your bot and test your ideas without building any frontend code. If you'd like to see an example of a custom UI, try Storybot.
FAQ
Deployment
For each of these demos we've included a Dockerfile. Out of the box, this should provide everything needed to get the respective demo running on a VM:
docker build username/app:tag .
docker run -p 7860:7860 --env-file ./.env username/app:tag
docker push ...
SSL
If you're working with a custom UI (such as with the Storytelling Chatbot), it's important to ensure your deployment platform supports HTTPS, as accessing user devices such as mics and webcams requires SSL.
If you try to run a custom UI without SSL, you may see an error in the console telling you that navigator is undefined, or no devices are available.
Are these examples production ready?
Yes, kind of.
These demos attempt to keep things simple and are unopinionated regarding environment or scalability.
We're using FastAPI to spawn a subprocess for the bots / agents — useful for small tests, but not so great for production grade apps with many concurrent users. You can see how this works in each project's start endpoint in server.py.
Creating virtualized worker pools and on-demand instances is out of scope for these examples, but we hope to add some examples to this repo soon!
For projects that have CUDA as a requirement, such as Moondream Chatbot, be sure to deploy to a GPU-powered platform (such as fly.io or Runpod.)
### Google Python Coding Style:
styleguide
Google Python Style Guide
Table of Contents
1 Background
Python is the main dynamic language used at Google. This style guide is a list of dos and don’ts for Python programs.
To help you format code correctly, we’ve created a settings file for Vim. For Emacs, the default settings should be fine.
Many teams use the Black or Pyink auto-formatter to avoid arguing over formatting.
2 Python Language Rules
2.1 Lint
Run pylint over your code using this pylintrc.
2.1.1 Definition
pylint is a tool for finding bugs and style problems in Python source code. It finds problems that are typically caught by a compiler for less dynamic languages like C and C++. Because of the dynamic nature of Python, some warnings may be incorrect; however, spurious warnings should be fairly infrequent.
2.1.2 Pros
Catches easy-to-miss errors like typos, using-vars-before-assignment, etc.
2.1.3 Cons
pylint isn’t perfect. To take advantage of it, sometimes we’ll need to write around it, suppress its warnings or fix it.
2.1.4 Decision
Make sure you run pylint on your code.
Suppress warnings if they are inappropriate so that other issues are not hidden. To suppress warnings, you can set a line-level comment:
def do_PUT(self): # WSGI name, so pylint: disable=invalid-name
...
pylint warnings are each identified by symbolic name (empty-docstring) Google-specific warnings start with g-.
If the reason for the suppression is not clear from the symbolic name, add an explanation.
Suppressing in this way has the advantage that we can easily search for suppressions and revisit them.
You can get a list of pylint warnings by doing:
pylint --list-msgs
To get more information on a particular message, use:
pylint --help-msg=invalid-name
Prefer pylint: disable to the deprecated older form pylint: disable-msg.
Unused argument warnings can be suppressed by deleting the variables at the beginning of the function. Always include a comment explaining why you are deleting it. “Unused.” is sufficient. For example:
def viking*cafe_order(spam: str, beans: str, eggs: str | None = None) -> str:
del beans, eggs # Unused by vikings.
return spam + spam + spam
Other common forms of suppressing this warning include using ‘*’ as the identifier for the unused argument or prefixing the argument name with ‘unused*’, or assigning them to ‘*’. These forms are allowed but no longer encouraged. These break callers that pass arguments by name and do not enforce that the arguments are actually unused.
2.2 Imports
Use import statements for packages and modules only, not for individual types, classes, or functions.
2.2.1 Definition
Reusability mechanism for sharing code from one module to another.
2.2.2 Pros
The namespace management convention is simple. The source of each identifier is indicated in a consistent way; x.Obj says that object Obj is defined in module x.
2.2.3 Cons
Module names can still collide. Some module names are inconveniently long.
2.2.4 Decision
Use import x for importing packages and modules.
Use from x import y where x is the package prefix and y is the module name with no prefix.
Use from x import y as z in any of the following circumstances:
Two modules named y are to be imported.
y conflicts with a top-level name defined in the current module.
y conflicts with a common parameter name that is part of the public API (e.g., features).
y is an inconveniently long name.
y is too generic in the context of your code (e.g., from storage.file_system import options as fs_options).
Use import y as z only when z is a standard abbreviation (e.g., import numpy as np).
For example the module sound.effects.echo may be imported as follows:
from sound.effects import echo
...
echo.EchoFilter(input, output, delay=0.7, atten=4)
Do not use relative names in imports. Even if the module is in the same package, use the full package name. This helps prevent unintentionally importing a package twice.
2.2.4.1 Exemptions
Exemptions from this rule:
Symbols from the following modules are used to support static analysis and type checking:
typing module
collections.abc module
typing_extensions module
Redirects from the six.moves module.
2.3 Packages
Import each module using the full pathname location of the module.
2.3.1 Pros
Avoids conflicts in module names or incorrect imports due to the module search path not being what the author expected. Makes it easier to find modules.
2.3.2 Cons
Makes it harder to deploy code because you have to replicate the package hierarchy. Not really a problem with modern deployment mechanisms.
2.3.3 Decision
All new code should import each module by its full package name.
Imports should be as follows:
Yes:
# Reference absl.flags in code with the complete name (verbose).
import absl.flags
from doctor.who import jodie
\_FOO = absl.flags.DEFINE_string(...)
Yes:
# Reference flags in code with just the module name (common).
from absl import flags
from doctor.who import jodie
\_FOO = flags.DEFINE_string(...)
(assume this file lives in doctor/who/ where jodie.py also exists)
No:
# Unclear what module the author wanted and what will be imported. The actual
# import behavior depends on external factors controlling sys.path.
# Which possible jodie module did the author intend to import?
import jodie
The directory the main binary is located in should not be assumed to be in sys.path despite that happening in some environments. This being the case, code should assume that import jodie refers to a third-party or top-level package named jodie, not a local jodie.py.
2.4 Exceptions
Exceptions are allowed but must be used carefully.
2.4.1 Definition
Exceptions are a means of breaking out of normal control flow to handle errors or other exceptional conditions.
2.4.2 Pros
The control flow of normal operation code is not cluttered by error-handling code. It also allows the control flow to skip multiple frames when a certain condition occurs, e.g., returning from N nested functions in one step instead of having to plumb error codes through.
2.4.3 Cons
May cause the control flow to be confusing. Easy to miss error cases when making library calls.
2.4.4 Decision
Exceptions must follow certain conditions:
Make use of built-in exception classes when it makes sense. For example, raise a ValueError to indicate a programming mistake like a violated precondition, such as may happen when validating function arguments.
Do not use assert statements in place of conditionals or validating preconditions. They must not be critical to the application logic. A litmus test would be that the assert could be removed without breaking the code. assert conditionals are not guaranteed to be evaluated. For pytest based tests, assert is okay and expected to verify expectations. For example:
Yes:
def connect_to_next_port(self, minimum: int) -> int:
"""Connects to the next available port.
Args:
minimum: A port value greater or equal to 1024.
Returns:
The new minimum port.
Raises:
ConnectionError: If no available port is found.
"""
if minimum < 1024:
# Note that this raising of ValueError is not mentioned in the doc
# string's "Raises:" section because it is not appropriate to
# guarantee this specific behavioral reaction to API misuse.
raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
port = self._find_next_open_port(minimum)
if port is None:
raise ConnectionError(
f'Could not connect to service on port {minimum} or higher.')
# The code does not depend on the result of this assert.
assert port >= minimum, (
f'Unexpected port {port} when minimum was {minimum}.')
return port
No:
def connect_to_next_port(self, minimum: int) -> int:
"""Connects to the next available port.
Args:
minimum: A port value greater or equal to 1024.
Returns:
The new minimum port.
"""
assert minimum >= 1024, 'Minimum port must be at least 1024.'
# The following code depends on the previous assert.
port = self._find_next_open_port(minimum)
assert port is not None
# The type checking of the return statement relies on the assert.
return port
Libraries or packages may define their own exceptions. When doing so they must inherit from an existing exception class. Exception names should end in Error and should not introduce repetition (foo.FooError).
Never use catch-all except: statements, or catch Exception or StandardError, unless you are
re-raising the exception, or
creating an isolation point in the program where exceptions are not propagated but are recorded and suppressed instead, such as protecting a thread from crashing by guarding its outermost block.
Python is very tolerant in this regard and except: will really catch everything including misspelled names, sys.exit() calls, Ctrl+C interrupts, unittest failures and all kinds of other exceptions that you simply don’t want to catch.
Minimize the amount of code in a try/except block. The larger the body of the try, the more likely that an exception will be raised by a line of code that you didn’t expect to raise an exception. In those cases, the try/except block hides a real error.
Use the finally clause to execute code whether or not an exception is raised in the try block. This is often useful for cleanup, i.e., closing a file.
2.5 Mutable Global State
Avoid mutable global state.
2.5.1 Definition
Module-level values or class attributes that can get mutated during program execution.
2.5.2 Pros
Occasionally useful.
2.5.3 Cons
Breaks encapsulation: Such design can make it hard to achieve valid objectives. For example, if global state is used to manage a database connection, then connecting to two different databases at the same time (such as for computing differences during a migration) becomes difficult. Similar problems easily arise with global registries.
Has the potential to change module behavior during the import, because assignments to global variables are done when the module is first imported.
2.5.4 Decision
Avoid mutable global state.
In those rare cases where using global state is warranted, mutable global entities should be declared at the module level or as a class attribute and made internal by prepending an \_ to the name. If necessary, external access to mutable global state must be done through public functions or class methods. See Naming below. Please explain the design reasons why mutable global state is being used in a comment or a doc linked to from a comment.
Module-level constants are permitted and encouraged. For example: \_MAX_HOLY_HANDGRENADE_COUNT = 3 for an internal use constant or SIR_LANCELOTS_FAVORITE_COLOR = "blue" for a public API constant. Constants must be named using all caps with underscores. See Naming below.
2.6 Nested/Local/Inner Classes and Functions
Nested local functions or classes are fine when used to close over a local variable. Inner classes are fine.
2.6.1 Definition
A class can be defined inside of a method, function, or class. A function can be defined inside a method or function. Nested functions have read-only access to variables defined in enclosing scopes.
2.6.2 Pros
Allows definition of utility classes and functions that are only used inside of a very limited scope. Very ADT-y. Commonly used for implementing decorators.
2.6.3 Cons
Nested functions and classes cannot be directly tested. Nesting can make the outer function longer and less readable.
2.6.4 Decision
They are fine with some caveats. Avoid nested functions or classes except when closing over a local value other than self or cls. Do not nest a function just to hide it from users of a module. Instead, prefix its name with an \_ at the module level so that it can still be accessed by tests.
2.7 Comprehensions & Generator Expressions
Okay to use for simple cases.
2.7.1 Definition
List, Dict, and Set comprehensions as well as generator expressions provide a concise and efficient way to create container types and iterators without resorting to the use of traditional loops, map(), filter(), or lambda.
2.7.2 Pros
Simple comprehensions can be clearer and simpler than other dict, list, or set creation techniques. Generator expressions can be very efficient, since they avoid the creation of a list entirely.
2.7.3 Cons
Complicated comprehensions or generator expressions can be hard to read.
2.7.4 Decision
Comprehensions are allowed, however multiple for clauses or filter expressions are not permitted. Optimize for readability, not conciseness.
Yes:
result = [mapping_expr for value in iterable if filter_expr]
result = [
is_valid(metric={'key': value})
for value in interesting_iterable
if a_longer_filter_expression(value)
]
descriptive_name = [
transform({'key': key, 'value': value}, color='black')
for key, value in generate_iterable(some_input)
if complicated_condition_is_met(key, value)
]
result = []
for x in range(10):
for y in range(5):
if x \* y > 10:
result.append((x, y))
return {
x: complicated_transform(x)
for x in long_generator_function(parameter)
if x is not None
}
return (x\*\*2 for x in range(10))
unique_names = {user.name for user in users if user is not None}
No:
result = [(x, y) for x in range(10) for y in range(5) if x * y > 10]
return (
(x, y, z)
for x in range(5)
for y in range(5)
if x != y
for z in range(5)
if y != z
)
2.8 Default Iterators and Operators
Use default iterators and operators for types that support them, like lists, dictionaries, and files.
2.8.1 Definition
Container types, like dictionaries and lists, define default iterators and membership test operators (“in” and “not in”).
2.8.2 Pros
The default iterators and operators are simple and efficient. They express the operation directly, without extra method calls. A function that uses default operators is generic. It can be used with any type that supports the operation.
2.8.3 Cons
You can’t tell the type of objects by reading the method names (unless the variable has type annotations). This is also an advantage.
2.8.4 Decision
Use default iterators and operators for types that support them, like lists, dictionaries, and files. The built-in types define iterator methods, too. Prefer these methods to methods that return lists, except that you should not mutate a container while iterating over it.
Yes: for key in adict: ...
if obj in alist: ...
for line in afile: ...
for k, v in adict.items(): ...
No: for key in adict.keys(): ...
for line in afile.readlines(): ...
2.9 Generators
Use generators as needed.
2.9.1 Definition
A generator function returns an iterator that yields a value each time it executes a yield statement. After it yields a value, the runtime state of the generator function is suspended until the next value is needed.
2.9.2 Pros
Simpler code, because the state of local variables and control flow are preserved for each call. A generator uses less memory than a function that creates an entire list of values at once.
2.9.3 Cons
Local variables in the generator will not be garbage collected until the generator is either consumed to exhaustion or itself garbage collected.
2.9.4 Decision
Fine. Use “Yields:” rather than “Returns:” in the docstring for generator functions.
If the generator manages an expensive resource, make sure to force the clean up.
A good way to do the clean up is by wrapping the generator with a context manager PEP-0533.
2.10 Lambda Functions
Okay for one-liners. Prefer generator expressions over map() or filter() with a lambda.
2.10.1 Definition
Lambdas define anonymous functions in an expression, as opposed to a statement.
2.10.2 Pros
Convenient.
2.10.3 Cons
Harder to read and debug than local functions. The lack of names means stack traces are more difficult to understand. Expressiveness is limited because the function may only contain an expression.
2.10.4 Decision
Lambdas are allowed. If the code inside the lambda function spans multiple lines or is longer than 60-80 chars, it might be better to define it as a regular nested function.
For common operations like multiplication, use the functions from the operator module instead of lambda functions. For example, prefer operator.mul to lambda x, y: x \* y.
2.11 Conditional Expressions
Okay for simple cases.
2.11.1 Definition
Conditional expressions (sometimes called a “ternary operator”) are mechanisms that provide a shorter syntax for if statements. For example: x = 1 if cond else 2.
2.11.2 Pros
Shorter and more convenient than an if statement.
2.11.3 Cons
May be harder to read than an if statement. The condition may be difficult to locate if the expression is long.
2.11.4 Decision
Okay to use for simple cases. Each portion must fit on one line: true-expression, if-expression, else-expression. Use a complete if statement when things get more complicated.
Yes:
one_line = 'yes' if predicate(value) else 'no'
slightly_split = ('yes' if predicate(value)
else 'no, nein, nyet')
the_longest_ternary_style_that_can_be_done = (
'yes, true, affirmative, confirmed, correct'
if predicate(value)
else 'no, false, negative, nay')
No:
bad_line_breaking = ('yes' if predicate(value) else
'no')
portion_too_long = ('yes'
if some_long_module.some_long_predicate_function(
really_long_variable_name)
else 'no, false, negative, nay')
2.12 Default Argument Values
Okay in most cases.
2.12.1 Definition
You can specify values for variables at the end of a function’s parameter list, e.g., def foo(a, b=0):. If foo is called with only one argument, b is set to 0. If it is called with two arguments, b has the value of the second argument.
2.12.2 Pros
Often you have a function that uses lots of default values, but on rare occasions you want to override the defaults. Default argument values provide an easy way to do this, without having to define lots of functions for the rare exceptions. As Python does not support overloaded methods/functions, default arguments are an easy way of “faking” the overloading behavior.
2.12.3 Cons
Default arguments are evaluated once at module load time. This may cause problems if the argument is a mutable object such as a list or a dictionary. If the function modifies the object (e.g., by appending an item to a list), the default value is modified.
2.12.4 Decision
Okay to use with the following caveat:
Do not use mutable objects as default values in the function or method definition.
Yes: def foo(a, b=None):
if b is None:
b = []
Yes: def foo(a, b: Sequence | None = None):
if b is None:
b = []
Yes: def foo(a, b: Sequence = ()): # Empty tuple OK since tuples are immutable.
...
from absl import flags
\_FOO = flags.DEFINE_string(...)
No: def foo(a, b=[]):
...
No: def foo(a, b=time.time()): # Is `b` supposed to represent when this module was loaded?
...
No: def foo(a, b=\_FOO.value): # sys.argv has not yet been parsed...
...
No: def foo(a, b: Mapping = {}): # Could still get passed to unchecked code.
...
2.13 Properties
Properties may be used to control getting or setting attributes that require trivial computations or logic. Property implementations must match the general expectations of regular attribute access: that they are cheap, straightforward, and unsurprising.
2.13.1 Definition
A way to wrap method calls for getting and setting an attribute as a standard attribute access.
2.13.2 Pros
Allows for an attribute access and assignment API rather than getter and setter method calls.
Can be used to make an attribute read-only.
Allows calculations to be lazy.
Provides a way to maintain the public interface of a class when the internals evolve independently of class users.
2.13.3 Cons
Can hide side-effects much like operator overloading.
Can be confusing for subclasses.
2.13.4 Decision
Properties are allowed, but, like operator overloading, should only be used when necessary and match the expectations of typical attribute access; follow the getters and setters rules otherwise.
For example, using a property to simply both get and set an internal attribute isn’t allowed: there is no computation occurring, so the property is unnecessary (make the attribute public instead). In comparison, using a property to control attribute access or to calculate a trivially derived value is allowed: the logic is simple and unsurprising.
Properties should be created with the @property decorator. Manually implementing a property descriptor is considered a power feature.
Inheritance with properties can be non-obvious. Do not use properties to implement computations a subclass may ever want to override and extend.
2.14 True/False Evaluations
Use the “implicit” false if at all possible (with a few caveats).
2.14.1 Definition
Python evaluates certain values as False when in a boolean context. A quick “rule of thumb” is that all “empty” values are considered false, so 0, None, [], {}, '' all evaluate as false in a boolean context.
2.14.2 Pros
Conditions using Python booleans are easier to read and less error-prone. In most cases, they’re also faster.
2.14.3 Cons
May look strange to C/C++ developers.
2.14.4 Decision
Use the “implicit” false if possible, e.g., if foo: rather than if foo != []:. There are a few caveats that you should keep in mind though:
Always use if foo is None: (or is not None) to check for a None value. E.g., when testing whether a variable or argument that defaults to None was set to some other value. The other value might be a value that’s false in a boolean context!
Never compare a boolean variable to False using ==. Use if not x: instead. If you need to distinguish False from None then chain the expressions, such as if not x and x is not None:.
For sequences (strings, lists, tuples), use the fact that empty sequences are false, so if seq: and if not seq: are preferable to if len(seq): and if not len(seq): respectively.
When handling integers, implicit false may involve more risk than benefit (i.e., accidentally handling None as 0). You may compare a value which is known to be an integer (and is not the result of len()) against the integer 0.
Yes: if not users:
print('no users')
if i % 10 == 0:
self.handle_multiple_of_ten()
def f(x=None):
if x is None:
x = []
No: if len(users) == 0:
print('no users')
if not i % 10:
self.handle_multiple_of_ten()
def f(x=None):
x = x or []
Note that '0' (i.e., 0 as string) evaluates to true.
Note that Numpy arrays may raise an exception in an implicit boolean context. Prefer the .size attribute when testing emptiness of a np.array (e.g. if not users.size).
2.16 Lexical Scoping
Okay to use.
2.16.1 Definition
A nested Python function can refer to variables defined in enclosing functions, but cannot assign to them. Variable bindings are resolved using lexical scoping, that is, based on the static program text. Any assignment to a name in a block will cause Python to treat all references to that name as a local variable, even if the use precedes the assignment. If a global declaration occurs, the name is treated as a global variable.
An example of the use of this feature is:
def get_adder(summand1: float) -> Callable[[float], float]:
"""Returns a function that adds numbers to a given number."""
def adder(summand2: float) -> float:
return summand1 + summand2
return adder
2.16.2 Pros
Often results in clearer, more elegant code. Especially comforting to experienced Lisp and Scheme (and Haskell and ML and …) programmers.
2.16.3 Cons
Can lead to confusing bugs, such as this example based on PEP-0227:
i = 4
def foo(x: Iterable[int]):
def bar():
print(i, end='') # ... # A bunch of code here # ...
for i in x: # Ah, i _is_ local to foo, so this is what bar sees
print(i, end='')
bar()
So foo([1, 2, 3]) will print 1 2 3 3, not 1 2 3 4.
2.16.4 Decision
Okay to use.
2.17 Function and Method Decorators
Use decorators judiciously when there is a clear advantage. Avoid staticmethod and limit use of classmethod.
2.17.1 Definition
Decorators for Functions and Methods (a.k.a “the @ notation”). One common decorator is @property, used for converting ordinary methods into dynamically computed attributes. However, the decorator syntax allows for user-defined decorators as well. Specifically, for some function my_decorator, this:
class C:
@my_decorator
def method(self): # method body ...
is equivalent to:
class C:
def method(self): # method body ...
method = my_decorator(method)
2.17.2 Pros
Elegantly specifies some transformation on a method; the transformation might eliminate some repetitive code, enforce invariants, etc.
2.17.3 Cons
Decorators can perform arbitrary operations on a function’s arguments or return values, resulting in surprising implicit behavior. Additionally, decorators execute at object definition time. For module-level objects (classes, module functions, …) this happens at import time. Failures in decorator code are pretty much impossible to recover from.
2.17.4 Decision
Use decorators judiciously when there is a clear advantage. Decorators should follow the same import and naming guidelines as functions. Decorator pydoc should clearly state that the function is a decorator. Write unit tests for decorators.
Avoid external dependencies in the decorator itself (e.g. don’t rely on files, sockets, database connections, etc.), since they might not be available when the decorator runs (at import time, perhaps from pydoc or other tools). A decorator that is called with valid parameters should (as much as possible) be guaranteed to succeed in all cases.
Decorators are a special case of “top-level code” - see main for more discussion.
Never use staticmethod unless forced to in order to integrate with an API defined in an existing library. Write a module-level function instead.
Use classmethod only when writing a named constructor, or a class-specific routine that modifies necessary global state such as a process-wide cache.
2.18 Threading
Do not rely on the atomicity of built-in types.
While Python’s built-in data types such as dictionaries appear to have atomic operations, there are corner cases where they aren’t atomic (e.g. if **hash** or **eq** are implemented as Python methods) and their atomicity should not be relied upon. Neither should you rely on atomic variable assignment (since this in turn depends on dictionaries).
Use the queue module’s Queue data type as the preferred way to communicate data between threads. Otherwise, use the threading module and its locking primitives. Prefer condition variables and threading.Condition instead of using lower-level locks.
2.19 Power Features
Avoid these features.
2.19.1 Definition
Python is an extremely flexible language and gives you many fancy features such as custom metaclasses, access to bytecode, on-the-fly compilation, dynamic inheritance, object reparenting, import hacks, reflection (e.g. some uses of getattr()), modification of system internals, **del** methods implementing customized cleanup, etc.
2.19.2 Pros
These are powerful language features. They can make your code more compact.
2.19.3 Cons
It’s very tempting to use these “cool” features when they’re not absolutely necessary. It’s harder to read, understand, and debug code that’s using unusual features underneath. It doesn’t seem that way at first (to the original author), but when revisiting the code, it tends to be more difficult than code that is longer but is straightforward.
2.19.4 Decision
Avoid these features in your code.
Standard library modules and classes that internally use these features are okay to use (for example, abc.ABCMeta, dataclasses, and enum).
2.20 Modern Python: from **future** imports
New language version semantic changes may be gated behind a special future import to enable them on a per-file basis within earlier runtimes.
2.20.1 Definition
Being able to turn on some of the more modern features via from **future** import statements allows early use of features from expected future Python versions.
2.20.2 Pros
This has proven to make runtime version upgrades smoother as changes can be made on a per-file basis while declaring compatibility and preventing regressions within those files. Modern code is more maintainable as it is less likely to accumulate technical debt that will be problematic during future runtime upgrades.
2.20.3 Cons
Such code may not work on very old interpreter versions prior to the introduction of the needed future statement. The need for this is more common in projects supporting an extremely wide variety of environments.
2.20.4 Decision
from **future** imports
Use of from **future** import statements is encouraged. It allows a given source file to start using more modern Python syntax features today. Once you no longer need to run on a version where the features are hidden behind a **future** import, feel free to remove those lines.
In code that may execute on versions as old as 3.5 rather than >= 3.7, import:
from **future** import generator_stop
For more information read the Python future statement definitions documentation.
Please don’t remove these imports until you are confident the code is only ever used in a sufficiently modern environment. Even if you do not currently use the feature a specific future import enables in your code today, keeping it in place in the file prevents later modifications of the code from inadvertently depending on the older behavior.
Use other from **future** import statements as you see fit.
2.21 Type Annotated Code
You can annotate Python code with type hints according to PEP-484, and type-check the code at build time with a type checking tool like pytype.
Type annotations can be in the source or in a stub pyi file. Whenever possible, annotations should be in the source. Use pyi files for third-party or extension modules.
2.21.1 Definition
Type annotations (or “type hints”) are for function or method arguments and return values:
def func(a: int) -> list[int]:
You can also declare the type of a variable using similar PEP-526 syntax:
a: SomeType = some_func()
2.21.2 Pros
Type annotations improve the readability and maintainability of your code. The type checker will convert many runtime errors to build-time errors, and reduce your ability to use Power Features.
2.21.3 Cons
You will have to keep the type declarations up to date. You might see type errors that you think are valid code. Use of a type checker may reduce your ability to use Power Features.
2.21.4 Decision
You are strongly encouraged to enable Python type analysis when updating code. When adding or modifying public APIs, include type annotations and enable checking via pytype in the build system. As static analysis is relatively new to Python, we acknowledge that undesired side-effects (such as wrongly inferred types) may prevent adoption by some projects. In those situations, authors are encouraged to add a comment with a TODO or link to a bug describing the issue(s) currently preventing type annotation adoption in the BUILD file or in the code itself as appropriate.
3 Python Style Rules
3.1 Semicolons
Do not terminate your lines with semicolons, and do not use semicolons to put two statements on the same line.
3.2 Line length
Maximum line length is 80 characters.
Explicit exceptions to the 80 character limit:
Long import statements.
URLs, pathnames, or long flags in comments.
Long string module-level constants not containing whitespace that would be inconvenient to split across lines such as URLs or pathnames.
Pylint disable comments. (e.g.: # pylint: disable=invalid-name)
Do not use a backslash for explicit line continuation.
Instead, make use of Python’s implicit line joining inside parentheses, brackets and braces. If necessary, you can add an extra pair of parentheses around an expression.
Note that this rule doesn’t prohibit backslash-escaped newlines within strings (see below).
Yes: foo_bar(self, width, height, color='black', design=None, x='foo',
emphasis=None, highlight=0)
Yes: if (width == 0 and height == 0 and
color == 'red' and emphasis == 'strong'):
(bridge_questions.clarification_on
.average_airspeed_of.unladen_swallow) = 'African or European?'
with (
very_long_first_expression_function() as spam,
very_long_second_expression_function() as beans,
third_thing() as eggs,
):
place_order(eggs, beans, spam, beans)
No: if width == 0 and height == 0 and \
color == 'red' and emphasis == 'strong':
bridge_questions.clarification_on \
.average_airspeed_of.unladen_swallow = 'African or European?'
with very_long_first_expression_function() as spam, \
very_long_second_expression_function() as beans, \
third_thing() as eggs:
place_order(eggs, beans, spam, beans)
When a literal string won’t fit on a single line, use parentheses for implicit line joining.
x = ('This will build a very long long '
'long long long long long long string')
Prefer to break lines at the highest possible syntactic level. If you must break a line twice, break it at the same syntactic level both times.
Yes: bridgekeeper.answer(
name="Arthur", quest=questlib.find(owner="Arthur", perilous=True))
answer = (a_long_line().of_chained_methods()
.that_eventually_provides().an_answer())
if (
config is None
or 'editor.language' not in config
or config['editor.language'].use_spaces is False
):
use_tabs()
No: bridgekeeper.answer(name="Arthur", quest=questlib.find(
owner="Arthur", perilous=True))
answer = a_long_line().of_chained_methods().that_eventually_provides(
).an_answer()
if (config is None or 'editor.language' not in config or config[
'editor.language'].use_spaces is False):
use_tabs()
Within comments, put long URLs on their own line if necessary.
Yes: # See details at # http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html
No: # See details at # http://www.example.com/us/developer/documentation/api/content/\ # v2.0/csv_file_name_extension_full_specification.html
Make note of the indentation of the elements in the line continuation examples above; see the indentation section for explanation.
In all other cases where a line exceeds 80 characters, and the Black or Pyink auto-formatter does not help bring the line below the limit, the line is allowed to exceed this maximum. Authors are encouraged to manually break the line up per the notes above when it is sensible.
3.3 Parentheses
Use parentheses sparingly.
It is fine, though not required, to use parentheses around tuples. Do not use them in return statements or conditional statements unless using parentheses for implied line continuation or to indicate a tuple.
Yes: if foo:
bar()
while x:
x = bar()
if x and y:
bar()
if not x:
bar() # For a 1 item tuple the ()s are more visually obvious than the comma.
onesie = (foo,)
return foo
return spam, beans
return (spam, beans)
for (x, y) in dict.items(): ...
No: if (x):
bar()
if not(x):
bar()
return (foo)
3.4 Indentation
Indent your code blocks with 4 spaces.
Never use tabs. Implied line continuation should align wrapped elements vertically (see line length examples), or use a hanging 4-space indent. Closing (round, square or curly) brackets can be placed at the end of the expression, or on separate lines, but then should be indented the same as the line with the corresponding opening bracket.
Yes: # Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)
meal = (spam,
beans)