How to Set Up a Verified Cash App Account Safely: What to Know Before Using Online Services Cash App has become a popular way to send and receive money, pay for purchases, and manage everyday financial transactions. Because some features require identity verification, people looking for faster access may encounter websites advertising “verified Cash App accounts” for sale. If you have any questions, Contact Us Anytime Telegram: @smartusapva WhatsApp: +1(618) 331-0832 Email: smartusapva@gmail.com That shortcut can create more problems than it solves. A financial account is closely connected to personal identity, payment information, security credentials, and transaction history. An account created or verified for one person generally should not be treated like a transferable digital asset. Purchasing an account from an unknown seller can expose the buyer to account recovery problems, frozen funds, identity concerns, scams, and violations of the platform's rules. The safer approach is to create and verify your own account through official channels. This gives you control over the identity information associated with the account and makes it easier to recover access if something goes wrong. This guide explains what verification generally involves, why purchased accounts are risky, how to recognize questionable services, and what legitimate users should consider when setting up and protecting a Cash App account. What Does a Verified Cash App Account Mean? Verification is part of the process financial services use to establish that an account belongs to a legitimate user. Depending on the service and circumstances, verification may involve information such as a person's legal name, date of birth, contact information, or other identifying details. Verification should not be confused with a guarantee that an account is permanently approved for every feature. Financial platforms can conduct additional reviews, impose restrictions, or request further information when circumstances require it. Most importantly, verification is connected to the actual account holder. That makes a verified account fundamentally different from an ordinary username or transferable digital property. If a website claims that it can sell you an account that has already been verified under another person's information, that should be treated as a significant warning sign. Why Buying Verified Financial Accounts Is Risky The biggest problem with purchasing an existing account is that you may not actually control the identity associated with it. A seller could retain recovery information, possess documentation connected with the original account holder, or attempt to regain access after completing the sale. Even if the credentials initially work, there is no guarantee that the account will remain accessible. There are several other risks worth considering. Account Restrictions Financial platforms can review accounts for unusual activity or inconsistencies. A change in the person using an account can create circumstances that result in additional verification or restrictions. An account that appears usable when purchased may therefore become inaccessible later. Loss of Money If money is placed into an account that subsequently becomes restricted, recovering those funds may be difficult or time-consuming. This is especially concerning when dealing with anonymous sellers who operate through informal websites, messaging apps, or cryptocurrency payments. Identity and Privacy Problems A purchased account may contain another person's information. Continuing to use an account associated with someone else creates obvious privacy and ownership complications. Likewise, sending your own identification documents to an unknown account seller can expose highly sensitive information to a party whose security practices you cannot verify. Scams and Fake Guarantees Some sellers use terms such as “fully verified,” “lifetime replacement,” or “guaranteed working” to create confidence. Marketing language does not establish that an account is legitimate or that a seller can provide lasting access. A website can disappear after receiving payment, provide invalid credentials, or refuse to honor its advertised guarantee. The Safer Alternative: Create Your Own Account For someone who genuinely needs Cash App, creating an account personally is usually the most straightforward way to establish legitimate ownership. The general process involves obtaining the official application or using the service's official website, registering with your own information, and completing any verification steps requested by the platform. The exact requirements can change over time, so users should rely on Cash App's current official instructions rather than third-party guides promising shortcuts. Using your own information has an important advantage: your account history, security settings, recovery methods, and verification information remain connected to you. What to Look for When Setting Up an Account A safe setup begins with basic account-security practices. Use Your Own Information Do not use another person's identity, identification documents, telephone number, or financial information simply because someone claims it will make verification easier. Information associated with a financial account should accurately represent the account holder. Use a Secure Email Address and Phone Number Choose contact information that you control. Avoid relying on a seller's temporary email address or telephone number. If you lose access to the contact information associated with an account, account recovery can become considerably more difficult. Protect Your Login Credentials Never share passwords, authentication codes, or security information with an online seller. Be particularly cautious if someone claims that they need your verification code to “activate” or “transfer” an account. Enable Available Security Features Use the security options provided by the platform. Where available, features such as authentication protections, device security, and transaction notifications can make unauthorized access easier to detect. Security is not something that should be delegated to an account seller. How to Evaluate Websites Advertising Cash App Accounts Even if you encounter a site advertising financial accounts, there are several signs that should make you reconsider providing money or personal information. Be Suspicious of Guaranteed Access No third-party seller can reliably guarantee that a financial account will remain available indefinitely. A guarantee such as “never banned,” “lifetime access,” or “permanent verification” should not be treated as proof of legitimacy. Avoid Requests for Excessive Personal Information Be cautious when a seller asks for copies of identification documents, selfies, banking credentials, authentication codes, or other sensitive information. Before providing sensitive information online, consider whether the recipient genuinely needs it and whether the request comes from the official financial service itself. Examine Payment Methods Carefully A seller who insists on irreversible payment methods can leave buyers with little recourse when something goes wrong. Cryptocurrency, direct transfers, and other difficult-to-reverse payment arrangements deserve particular caution when dealing with an unknown party. Look Beyond Testimonials Testimonials displayed on a seller's own website are not independent evidence. A sophisticated-looking site can still be unreliable. Likewise, a large number of positive reviews does not prove that a financial account is legitimate or transferable. Consider whether reviews are independently verifiable and whether they describe the specific service being offered. Red Flags That Should Stop a Transaction Certain combinations of warning signs deserve particular attention. Be cautious if a seller: Promises a “100% guaranteed” account Claims verification can never be revoked Offers accounts registered to other people Requests your login credentials or authentication codes Asks for identification without a clear legitimate reason Demands payment through an irreversible method Pressures you to purchase immediately Refuses to explain where the account originated Claims to have special access to a financial platform Offers prices that seem unusually low Encourages you to bypass the platform's normal verification process One warning sign may have an innocent explanation, but several together should make a prospective buyer reconsider the transaction. Why Account Age Is Not a Substitute for Legitimacy Some account marketplaces advertise accounts as “aged,” suggesting that an older account is automatically more valuable or trustworthy. Age alone does not establish legitimate ownership. An account that has existed for years can still be restricted, compromised, incorrectly registered, or associated with another person. Conversely, a newly created account that legitimately belongs to its owner may be perfectly appropriate for ordinary use. Users should therefore focus on legitimate ownership and compliance rather than treating account age as a shortcut to trust. What If You Already Bought an Account? If you have already purchased an account and are concerned about its legitimacy, avoid adding additional money or sensitive personal information until you understand the situation. Do not provide the seller with additional authentication codes or credentials merely because they claim these are necessary to complete a transfer. If the account contains someone else's identifying information, consider contacting the financial service through its official support channels and explaining the circumstances honestly. If you believe your own identity information or payment details have been exposed, take steps to secure the affected accounts and monitor for unauthorized activity. Keeping records of communications and payments can also be useful if you need to report a suspected
~~ How to Get a Verified PayPal Account – Fast Setup & 24/7 Customer Support PayPal is one of the most widely recognized online payment platforms, allowing individuals and businesses to send, receive, and manage payments through the internet. For freelancers, online sellers, entrepreneurs, and businesses, having a properly established and verified PayPal account can make online transactions more convenient. If you are looking for a reliable way to get a verified PayPal account, the safest approach is to create your own account through PayPal and complete the platform’s verification and identity-checking requirements. Using your own information helps protect your account, reduces compliance problems, and makes it easier to recover your account if you ever lose access. This guide explains how PayPal verification works, what you may need to prepare, how to keep your account secure, and how customer support can help when problems occur. If You Want To More Information just Contact Now: Email: ourseoshopofficial@gmail.com Telegram: @OurSEOshop WhatsApp: +1 (920) 282-0091 Our Site: https://ourseoshop.com/product/buy-verified-paypal-accounts/ What Is a Verified PayPal Account? A verified PayPal account is an account that has completed applicable verification requirements established by PayPal. Depending on your location, account type, and activity, PayPal may ask you to confirm information such as your email address, phone number, identity, or payment method. Verification helps PayPal maintain account security and comply with financial regulations. It can also help establish that the account belongs to the person or organization using it. Verification requirements can differ between countries and may change over time. Therefore, users should always follow the instructions displayed inside their PayPal account. Why PayPal Verification Matters Verification can provide several practical benefits. Improved Account Security Using accurate personal or business information makes it easier to establish ownership of an account. If a security issue occurs, PayPal may request information to confirm that you are the legitimate account holder. Access to Payment Features Some PayPal features may require additional verification before they can be used. Completing the requested checks can help ensure that your account is ready for the services available in your region. Better Compliance PayPal operates within financial and regulatory frameworks. Identity and payment verification can be part of the company's compliance procedures. Easier Account Recovery If you use your own email address, phone number, identity information, and payment methods, recovering access can generally be more straightforward than attempting to manage an account registered to someone else. How to Create a PayPal Account Creating your own PayPal account is the most reliable way to establish legitimate ownership. Start by visiting the official PayPal website or using the official PayPal application. Select the appropriate account type and provide the requested information. Depending on your needs, you may choose between an individual-oriented account and a business account. During registration, you may be asked for information such as: ● Your legal name ● Email address ● Phone number ● Residential or business information ● Country or region ● Business information, where applicable ● A payment method Make sure the information you submit is accurate and belongs to you. Verify Your Email Address After creating your account, PayPal may send a confirmation message to your registered email address. Open the email and follow the instructions provided by PayPal. Confirming your email address helps establish control over the email account associated with your PayPal profile. Use an email address that you control and expect to maintain for a long period. Avoid using temporary email addresses for important financial accounts. Verify Your Phone Number Depending on your location and account requirements, PayPal may also ask you to verify your phone number. A verified phone number can provide another layer of security and may be used for authentication, account notifications, or recovery procedures. Make sure the phone number belongs to you and can receive messages or calls when necessary. Add and Confirm a Payment Method PayPal may allow users to connect eligible bank accounts, debit cards, or credit cards. When adding a payment method, make sure the information is accurate and belongs to you or your authorized business. PayPal may use different confirmation procedures depending on the payment method and country. Never attempt to connect another person's financial account without authorization. Identity Verification In some situations, PayPal may request additional identity information. This can happen when opening certain accounts, accessing particular services, reaching transaction thresholds, or when additional compliance checks are required. Documents requested may vary by country and circumstances. Examples can include government-issued identification or other documentation requested through PayPal's official verification process. Only provide documents through legitimate PayPal channels. Business Account Verification Businesses may have additional requirements. Depending on the type and location of the business, PayPal may request information such as: ● Business name ● Business address ● Business registration information ● Information about owners or representatives ● Business category ● Contact information ● Financial or transaction information Providing accurate information from the beginning can help reduce unnecessary account problems later. Why You Should Avoid Buying Verified PayPal Accounts Some websites advertise pre-created or "verified" PayPal accounts for sale. Purchasing an account creates significant risks because the account may not belong to you originally, and its registration information may not match your identity. There can also be problems involving: ● Account ownership ● Identity verification ● Security checks ● Payment disputes ● Account limitations ● Recovery difficulties ● Unauthorized access ● Compliance requirements A financial account should be created and maintained by its legitimate owner. If you need PayPal for business purposes, establishing your own business account and completing the required verification process is a safer long-term approach. Fast PayPal Account Setup Many people want a fast way to start using PayPal. The fastest legitimate approach is usually to prepare the required information before beginning registration. Keep the following information available: A working email address A valid phone number Accurate personal information Appropriate identification if requested An eligible payment method Business documentation if applicable Having accurate information available can make the registration process more efficient. However, no legitimate provider should guarantee that every account will be verified instantly. Verification times can vary depending on the information provided and whether additional reviews are required. If You Want To More Information just Contact Now: Email: ourseoshopofficial@gmail.com Telegram: @OurSEOshop WhatsApp: +1 (920) 282-0091 Our Site: https://ourseoshop.com/product/buy-verified-paypal-accounts/ How to Keep Your PayPal Account Secure Creating an account is only the beginning. Security should remain a priority throughout the life of the account. Use a Strong Password Create a unique password that you do not use for other websites. Avoid obvious information such as your name, birthday, phone number, or common words. Enable Available Security Features Use available authentication and security features provided by PayPal. Additional authentication can make unauthorized access more difficult. Protect Your Email Account Your email account is closely connected to your PayPal account. Use a strong password and appropriate security settings for your email provider as well. Watch for Phishing Criminals sometimes send fake PayPal messages designed to steal login information. Be cautious with messages asking you to: ● Confirm your password ● Send sensitive information ● Pay an unexpected fee ● Download suspicious software ● Click an unfamiliar link ● Provide authentication codes When in doubt, open PayPal directly rather than using a link in an unexpected message. What to Do If Your PayPal Account Is Limited Sometimes PayPal may place limitations or restrictions on an account. If this happens, review the notification displayed in your PayPal account. PayPal may request additional information or documentation. Follow the instructions provided through the official platform. Do not attempt to bypass a limitation by purchasing another person's account. That can create additional ownership and compliance complications. Instead, resolve the issue through the official process. Customer Support for PayPal Users Customer support can be useful when users encounter registration, verification, transaction, security, or account-access problems. Before contacting support, gather relevant information about the problem. Avoid sharing your password or authentication codes with anyone claiming to provide support. When contacting PayPal, use official support channels rather than unofficial social media accounts or individuals offering account-recovery services. PayPal for Freelancers Freelancers often use online payment platforms to receive payments from clients. A properly established PayPal account can provide a convenient way to manage eligible payments, depending on the freelancer's country and PayPal's available services. Freelancers should keep their account information accurate and maintain records of transactions for their own accounting and tax responsibilities. PayPal for Online Businesses Online stores and service businesses may use PayPal as one of several available payment options. A business should make sure its
Exploring options like Buy Verified Square Accounts helps online merchants understand payment processing flexibility. This guide breaks down benefits, security risks, e-commerce integration, and key considerations to help businesses evaluate their best payment solutions effectively. Contact us anytime — we reply within 24 hours! WhatsApp: +447853172146 Telegram: @BuyGo2Bank More Information: https://buygo2bank.com/product/buy-verified-square-accounts/ * Is Buy Verified Square Accounts Suitable for E-Commerce? 🛒 * Running an online store requires a smooth checkout experience for customers. When merchants explore payment solutions using Buy Verified Square Accounts, instant access becomes a major focus for fast scaling. Standard payment gateways sometimes involve complex identity checks and verification delays. These administrative hurdles can slow down ambitious store owners during crucial launch phases. However, e-commerce platforms demand long-term stability and full compliance. While immediate availability looks appealing for fast scaling, online sellers visiting buygo2bank.com must ensure their processing channels strictly align with official e-commerce platform guidelines. * Why Do Entrepreneurs Consider Buy Verified Square Accounts? 💡 * Digital entrepreneurs always look for ways to streamline financial management and fast-track store operations through Buy Verified Square Accounts. Speedy onboarding allows business owners to execute marketing campaigns without losing sales opportunities. Additionally, having immediate transaction readiness grants growing brands a competitive edge. Fast entry into the market keeps momentum high and prevents customer drop-offs caused by checkout errors. Many sellers visiting buygo2bank.com also seek operational redundancy to protect overall cash flow. Having pre-configured setup profiles helps maintain uninterrupted transaction processing during sudden inventory spikes or unexpected platform reviews. * What Benefits and Risks Come With Buy Verified Square Accounts? ⚖️ * The primary appeal of choosing Buy Verified Square Accounts lies in avoiding long waiting periods and immediate operational setup. Instant access lets businesses accept credit card orders right away without waiting weeks for approval. Conversely, significant risks exist when purchasing pre-verified profiles. Financial institutions employ strict monitoring systems that detect ownership inconsistencies, which can trigger permanent account suspensions. Furthermore, funds held in flagged merchant accounts might become locked indefinitely. Balancing short-term convenience at buygo2bank.com against severe operational interruptions remains a critical calculation for every business owner. * Can Buy Verified Square Accounts Support Business Payment Needs? 💳 * Modern digital stores require robust payment processing channels capable of handling daily credit card transactions efficiently. Evaluating Buy Verified Square Accounts ensures customers experience hassle-free payments during checkout. Acquiring pre-configured profiles allows fast-scaling web stores to process continuous customer orders. This initial convenience helps new platforms manage incoming revenue streams without technical delays. Nevertheless, ongoing business demands require continuous identity updates and tax verification compliance. Without legitimate ownership details, acquiring solutions via buygo2bank.com for large volume sales becomes risky over longer operating periods. * Why Is Security Important When Considering Buy Verified Square Accounts? 🔒 * Financial security forms the foundation of any sustainable digital commerce enterprise. When reviewing Buy Verified Square Accounts, handling sensitive customer payment details requires maximum encryption standards and regulatory adherence. Third-party merchant profiles often carry unverified history or compromised credential setups. Inheriting unknown account credentials opens your enterprise to potential security breaches and data vulnerabilities. Protecting client transactions and brand reputation should always remain top priorities. Finding reliable options through buygo2bank.com ensures your business avoids legal complications and builds long-term consumer trust. Contact us anytime — we reply within 24 hours! WhatsApp: +447853172146 Telegram: @BuyGo2Bank More Information: https://buygo2bank.com/product/buy-verified-square-accounts/ * What Makes Buy Verified Square Accounts Relevant to Online Payments? 🌐 * Online payment processing serves as the engine driving modern digital commerce forward. Faster payment gateway onboarding directly impacts how quickly new web ventures generate active revenue. Options like Buy Verified Square Accounts highlight the high demand for rapid payment deployment. Merchants constantly search for effective methods to bypass traditional banking delays and administrative friction. Understanding these options on buygo2bank.com helps sellers evaluate the evolving landscape of digital merchant solutions. It emphasizes why flexible, efficient payment gateways remain critical for modern online store success. * Are Buy Verified Square Accounts Worth Comparing With Alternatives? 🔄 * Comparing various payment channels like Buy Verified Square Accounts allows business owners to select the safest and most efficient path forward. Evaluating different gateways ensures you find features tailored to your specific business model. Direct merchant registration with established providers offers legal safety and direct customer support. Though initial verification takes longer, authentic accounts guarantee complete operational stability and peace of mind. Weighing pre-verified convenience from buygo2bank.com against legitimate merchant accounts reveals clear differences in long-term safety. Thorough comparisons protect your brand from unexpected financial losses down the road. * Why Should You Research Buy Verified Square Accounts Before Choosing? 🔍 * Thorough market research into Buy Verified Square Accounts prevents costly financial mistakes and protects your business infrastructure. Understanding background policies ensures you do not violate platform terms of service unknowingly. Investigating user feedback and industry guidelines provides clarity on potential account survival rates. Many sellers rush decisions without checking platforms like buygo2bank.com, only to face immediate profile bans and frozen revenues. Spending time analyzing options empowers you to make informed, strategic decisions. Proper due diligence ensures your digital store operates smoothly without sudden operational disruptions. * What Questions Are People Asking About Buy Verified Square Accounts? ❓ * Entrepreneurs frequently inquire about the safety and legal standing when opting to Buy Verified Square Accounts. They want to know if these solutions can withstand routine security audits. Another common concern involves payout reliability and bank account linking procedures. Merchants wonder whether transferring funds from buygo2bank.com profiles to personal bank accounts causes security triggers or payment holds. Finally, business owners ask how long these accounts typically remain active under heavy order volumes. Gathering answers to these queries helps sellers assess total operational risks accurately. * How Do Buy Verified Square Accounts Compare With Other Options? 📊 * Pre-verified setups such as Buy Verified Square Accounts offer immediate setup speed compared to standard payment gateway applications. Traditional onboarding methods require detailed identity submission, business documents, and bank verification time. However, traditional accounts provide complete owner control and full legal protection. Official accounts easily integrate with business bank setups without triggering unexpected security freezes or account audits. While alternative shortcuts on buygo2bank.com seem faster initially, direct registration remains the standard for business longevity. Choosing official pathways eliminates constant worries about sudden profile terminations and lost revenue. * Why Are Buy Verified Square Accounts Important to Online Sellers? 🛍️ * For online sellers, deciding to Buy Verified Square Accounts for payment processing determines overall conversion rates and enterprise profitability. Delays in payment setup directly translate to wasted marketing budgets and lost customer interest. Topics surrounding fast merchant account acquisition reflect the fierce competition within digital markets. Store owners continuously seek methods at buygo2bank.com to accelerate revenue collection and streamline customer checkouts. Recognizing these industry demands helps platform providers design faster, more accessible verification systems. It highlights the growing necessity for hassle-free payment solutions in modern e-commerce. * What Should Businesses Check Before Considering Buy Verified Square Accounts? ✅ * First, inspect the terms of service governing Buy Verified Square Accounts to understand compliance policies thoroughly. Violating user agreements can lead to severe penalties and permanent industry blacklisting. Second, evaluate the authenticity of profile providers like buygo2bank.com before making decisions. Verify whether support teams offer assistance during identity re-verification requests or security checks. Lastly, ensure your business has secondary payment fallbacks ready to handle unexpected account freezes. Planning ahead safeguards your daily store operations and maintains healthy cash flow. * Can Buy Verified Square Accounts Match Different Business Requirements? 🎯 * Every online store operates on distinct business models, sales volumes, and customer fulfillment schedules. Choosing Buy Verified Square Accounts depends heavily on whether the setup matches your precise operational capacity. Pre-verified profiles may provide temporary flexibility for short-term projects or testing new product lines. Th
작성 방법 작성 순서: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT SELECT FROM WEHRE GROUP BY HAVING ORDER BY LIMIT 동작 순서: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
Entrepreneurs searching for a business to buy can find opportunities through business marketplaces, brokers, professional networks, and direct owner connections. Finding businesses for sale directly from owners can provide another way to discover acquisition opportunities. Search Business Marketplaces Online marketplaces allow buyers to search across multiple industries and locations. You can filter opportunities based on factors such as: Industry Location Asking price Revenue Profitability Business type A marketplace can save time compared with searching for opportunities individually. Network With Business Owners Some owners may consider selling even if their business is not publicly advertised. Professional networking, industry events, local business communities, and existing contacts can help buyers discover potential opportunities. Contact Businesses in Your Target Industry If you know exactly which industry you want to enter, you can identify established businesses and explore whether their owners would consider a future sale. Approach owners professionally and respect confidentiality. Work With Business Brokers Business brokers can connect buyers with sellers and may provide information about available businesses, transaction processes, and negotiations. Brokers may also help coordinate communication between buyers and sellers. Prepare Your Buyer Profile Owners may want to know who is interested in purchasing their business. Prepare information about: Your professional background Available investment capital Industry experience Acquisition goals Financing plans Management experience A prepared buyer can make a stronger first impression. https://profitablebusinessesforsale.com/business-listing/featured-businessess-for-sale Protect Confidential Information Business owners may not want employees, customers, suppliers, or competitors to know that the company is being considered for sale. Confidentiality agreements may be required before detailed information is shared. Verify Every Opportunity Whether you find a business through an owner, marketplace, or broker, conduct appropriate due diligence before purchasing. Do not assume that a direct seller means fewer risks. Start Your Search Online Buyers can explore available acquisition opportunities through ProfitableBusinessesForSale.com and identify businesses that match their preferred industry, location, and investment criteria. Finding a business directly from an owner can be one route to discovering an acquisition, but the same principles still apply: verify the financials, understand the operations, investigate liabilities, and obtain appropriate professional advice before closing. https://profitablebusinessesforsale.com/business-listing/featured-businessess-for-sale Meta Title: How to Find Businesses for Sale From Owners Meta Description: Discover how to find businesses for sale directly from owners through marketplaces, networking, brokers, industry contacts, and targeted outreach. Meta Keywords: businesses for sale by owner, buy a business, businesses for sale, business acquisition, business owner, buy an existing business, business marketplace
1. 조건을 만족하는 ID 목록 → WHERE IN 주문을 3번 이상 한 회원 정보를 출력하라. 1. orders에서 user_id별로 묶고 3번 이상인 user_id 목록을 만든다. SELECT user_id FROM orders GROUP BY user_id HAVING COUNT(*) >= 3 2. SELECT * FROM users WHERE user_id IN ( SELECT user_id FROM orders GROUP BY user_id HAVING COUNT(*) >= 3 ); 문장에서 보이는 표현이 “ 에 해당하는 회원” “ 한 ID를 가진 상품” “특정 조건을 만족하는 사용자” 에 해당하면 일단 안쪽에서 ID 목록을 만들 수 있나? 을 생각하자. 2. 평균/최대/최소와 비교 → Scalar Subquery 전체 평균 연봉보다 높은 직원을 구하라. 1. 전체 평균 연봉 = 숫자 하나 2. SELECT * FROM employee WHERE salary > ( SELECT AVG(salary) FROM employee ); 문장에서 보이는 표현이 전체 평균보다 전체 최대값과 같은 가장 비싼 가격보다 기준값 이상 같이 비교 기준 하나가 필요하면 scalar subquery 를 먼저 생각하자. 3. 집계 결과를 재활용한다 → FROM (subquery) 회원별 총 구매금액을 구하고, 그 총 구매금액들의 평균을 구하라. 1. 1단계: 회원별 SUM 2단계: 그 SUM들의 AVG 로 끊어서 생각한다. 2. SELECT AVG(x.total) FROM ( SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id ) x; 단순히 집계 결과에 조건만 거는 경우라면 서브쿼리보다 HAVING 으로 해결할 수 있다. 집계 결과를 다시 SELECT / JOIN / 집계 해야 할 때 FROM (subquery) 를 고려한다. 4. 그룹별 MAX/MIN + 원본의 다른 컬럼도 필요 → JOIN (subquery) ⭐ 카테고리별 최고가 상품의 상품명, 가격을 출력하라. 1. 카테고리별 최고가격 SELECT category_id, MAX(price) AS max_price FROM products GROUP BY category_id 2. 이 결과에는 product_name 이 없기 때문에 원본과 다시 붙인다. SELECT p.* FROM products p JOIN ( SELECT category_id, MAX(price) AS max_price FROM products GROUP BY category_id ) x ON p.category_id = x.category_id AND p.price = x.max_price; 이 방식은 최고가가 여러 개라면 해당 상품이 모두 출력된다. 그룹별 정확히 1개만 골라야 한다면 ROW_NUMBER() 같은 Window Function을 고려한다. 5. 있냐 / 없냐 → EXISTS / NOT EXISTS 1. 주문한 적 있는 회원을 구하라. SELECT * FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.user_id ); 2. 주문한 적 없는 회원을 구하라. SELECT * FROM users u WHERE NOT EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.user_id ); 문장에서 보이는 표현이 “ 한 적이 있는” “ 가 존재하는” “한 번도 ~하지 않은” 이라면 EXISTS / NOT EXISTS 을 떠올리자. 6. 각 행마다 자기 그룹 기준과 비교 → 상관 서브쿼리 자기 부서 평균보다 연봉 높은 직원 전체 평균 아님 주의 직원마다 속한 부서가 다르므로 SELECT * FROM employee e WHERE e.salary > ( SELECT AVG(e2.salary) FROM employee e2 WHERE e2.dept_id = e.dept_id ); 이 때 안쪽에서 e.dept_id 라는 바깥 행 값을 참조하게 된다. 문장에서 보이는 표현이 자기 부서 평균 같은 카테고리 평균 동일 회원의 과거 기록 같이 현재 행에 따라 비교 기준이 달라지면 상관 서브쿼리를 의심해보자. 정리 문제에서 보이는 요구 먼저 떠올릴 구조 핵심 사고 조건을 만족하는 회원/상품 목록 WHERE ... IN (subquery) 안쪽에서 ID 목록 을 만들 수 있는가? 전체 평균/최대/최소와 비교 Scalar Subquery 안쪽에서 값 하나 를 만들 수 있는가? 집계 결과를 다시 가공 FROM (subquery) 먼저 중간 테이블 을 만든다 그룹별 MAX/MIN인 원본 행 조회 JOIN (subquery) 그룹별 대표값 계산 후 원본과 다시 JOIN 관련 데이터가 있나/없나 EXISTS / NOT EXISTS 값이 아니라 존재 여부 만 확인 자기 부서/카테고리 기준과 비교 Correlated Subquery 현재 바깥 행에 따라 안쪽 조건이 달라짐
Chapter 04 트리 트리는 데이터를 부모와 자식의 관계로 연결하는 계층적 자료구조이다. 아래 내용은 제공된 목차에 맞춘 일반적인 자료구조 정리이며, 코드는 Python으로 작성했다. 높이와 레벨은 교재마다 기준이 다르다. 이 정리에서는 루트의 레벨을 1, 트리의 높이를 전체 레벨 수로 정한다. 04-1 트리란? 트리(Tree) : 노드들을 계층적으로 연결한 비선형 자료구조이다. 리스트와 스택처럼 데이터를 한 줄로 나열하는 구조와 달리, 하나의 노드에서 여러 갈래로 뻗어나갈 수 있다. 대표적인 예: 컴퓨터의 폴더 구조, 회사의 조직도, 가계도. 루트가 있는 트리는 하나의 루트와 그 아래에 연결된 여러 서브트리로 구성된다. 비어 있지 않은 트리에서 루트를 제외한 각 노드는 부모를 정확히 하나 가진다. 트리는 연결되어 있고 사이클이 없으며, 두 노드 사이의 단순 경로는 유일하다. 트리 관련 용어 다음 관계를 예로 사용한다. A의 자식: B, C B의 자식: D, E C의 자식: F D, E, F는 자식이 없다. 용어 의미 예시 노드(Node) 데이터를 저장하는 기본 단위 A, B, C, D, E, F 간선(Edge) 두 노드를 연결하는 선 A와 B의 연결 루트(Root) 트리의 가장 위에 있는 노드 A 부모(Parent) 바로 위에 연결된 노드 D의 부모는 B 자식(Child) 바로 아래에 연결된 노드 B의 자식은 D, E 형제(Sibling) 부모가 같은 노드 D와 E 조상(Ancestor) 루트에서 해당 노드까지의 경로에 있는 상위 노드 D의 조상은 A, B 자손(Descendant) 해당 노드 아래의 모든 노드 B의 자손은 D, E 단말 노드(Leaf) 자식이 없는 노드 D, E, F 내부 노드(Internal node) 자식이 하나 이상 있는 노드 A, B, C 노드의 차수(Degree) 해당 노드의 자식 수 B의 차수는 2 트리의 차수 모든 노드의 차수 중 최댓값 2 깊이(Depth) 루트에서 해당 노드까지의 간선 수 A는 0, D는 2 레벨(Level) 노드가 속한 층 A는 1, D는 3 높이(Height) 이 정리에서는 트리의 전체 레벨 수 3 경로(Path) 한 노드에서 다른 노드로 이동할 때 거치는 노드들의 순서 A → B → D 경로 길이 경로에 포함된 간선 수 A → B → D의 길이는 2 서브트리(Subtree) 한 노드와 그 노드의 모든 자손으로 이루어진 트리 B, D, E로 이루어진 트리 포리스트(Forest) 서로 분리된 트리들의 모임 루트 A를 제거한 뒤 남은 두 트리 꼭 기억할 성질 노드가 n개인 비어 있지 않은 트리의 간선 수는 n - 1개 이다. 루트에는 부모가 없고, 나머지 노드에는 부모가 하나씩 있다. 단말 노드의 차수는 0 이다. 빈 트리의 높이는 0, 루트 하나만 있는 트리의 높이는 1로 계산한다. 높이를 간선 수로 정의하는 교재에서는 루트 하나의 높이가 0이다. 문제를 풀 때 기준을 먼저 확인한다. 트리의 표현 방법 1. 부모-자식 관계를 그림으로 표현 루트를 위에 놓고, 자식 노드를 아래에 배치한다. 부모와 자식을 선으로 연결하면 계층 구조를 쉽게 파악할 수 있다. 2. 중첩된 괄호로 표현 부모 뒤의 괄호 안에 자식들을 나열한다. 위 예시: A(B(D, E), C(F)) A의 자식은 B와 C이며, B의 자식은 D와 E라는 뜻이다. 3. 들여쓰기로 표현 깊이가 깊어질수록 들여쓰기를 늘린다. 폴더 목록이나 목차에서 자주 사용하는 방식이다. A B D E C F 4. 프로그램 내부에서 표현 부모 배열 : 각 노드의 부모를 저장한다. 부모를 찾기 쉽다. 자식 리스트 : 각 노드에 자식 노드들의 목록을 저장한다. 자식 수가 서로 달라도 표현하기 쉽다. 첫째 자식-다음 형제 표현 : 첫 번째 자식과 바로 다음 형제를 가리키는 두 링크를 사용한다. 일반 트리는 자식 수에 제한이 없지만, 이진 트리는 왼쪽과 오른쪽 두 자식 위치를 사용한다. 04-2 이진트리 이진 트리(Binary tree) : 각 노드가 최대 두 개의 자식을 가지는 트리이다. 두 자식은 각각 왼쪽 자식 과 오른쪽 자식 으로 구분한다. 자식이 하나뿐이어도 왼쪽 자식인지 오른쪽 자식인지 구분해야 한다. 왼쪽 서브트리와 오른쪽 서브트리도 각각 이진 트리이다. 빈 트리도 이진 트리로 취급한다. 이진 트리의 기본 성질 높이 h와 레벨의 기준은 루트가 1이다. 레벨 i의 최대 노드 수: 2^(i - 1) 높이 h인 이진 트리의 최대 노드 수: 2^h - 1 높이 h인 이진 트리의 최소 노드 수: h 노드가 n개인 비어 있지 않은 이진 트리의 최소 높이: ceil(log2(n + 1)) 노드가 n개인 이진 트리의 최대 높이: n ceil 은 소수점 아래를 올림한다는 뜻이다. 예: 높이가 3이면 노드 수는 최소 3개, 최대 7개이다. 이진 트리의 종류 종류 특징 기억할 점 포화 이진 트리(Perfect binary tree) 모든 레벨이 노드로 가득 차 있다 높이 h일 때 노드 수는 2^h - 1 완전 이진 트리(Complete binary tree) 마지막 레벨을 제외한 모든 레벨이 가득 차고, 마지막 레벨은 왼쪽부터 빈틈없이 채워진다 배열로 표현하기 좋다 정 이진 트리(Full/Proper binary tree) 모든 노드의 자식 수가 0개 또는 2개이다 자식이 1개인 노드가 없다 편향 이진 트리(Skewed binary tree) 노드들이 왼쪽 또는 오른쪽 한 방향으로만 이어진다 연결 리스트처럼 길어진다 높이 균형 이진 트리 모든 노드에서 두 서브트리의 높이 차이를 제한한다 대표적으로 AVL 트리는 차이가 1 이하이다 포화·완전·정 이진 트리 구분 포화 : 모든 층이 꽉 찼는가? 완전 : 위층이 꽉 차 있고, 마지막 층도 왼쪽부터 채웠는가? 정 : 각 노드의 자식 수가 0개 또는 2개인가? 포화 이진 트리는 완전 이진 트리이면서 정 이진 트리이다. 완전 이진 트리라고 해서 반드시 포화 이진 트리이거나 정 이진 트리인 것은 아니다. 영문 용어와 한글 번역이 교재마다 다를 수 있으므로 이름보다 정의를 기준으로 구분한다. 주의: 이진 트리와 이진 탐색 트리는 다르다. 일반 이진 트리에는 값의 대소 관계에 따른 배치 규칙이 없다. 이진 트리의 표현 방법 1. 배열을 이용한 표현 루트를 인덱스 1에 저장하고, 노드의 위치에 따라 배열 인덱스를 정한다. 대상 현재 노드의 인덱스가 i일 때 부모 i // 2, 단 루트 제외 왼쪽 자식 2 * i 오른쪽 자식 2 * i + 1 예: 1번 노드의 왼쪽 자식은 2번, 오른쪽 자식은 3번에 저장한다. 존재하지 않는 위치는 None 등으로 표시한다. 장점: 부모와 자식의 위치를 계산으로 바로 구할 수 있다. 단점: 편향 트리처럼 비어 있는 자리가 많으면 배열 공간을 낭비한다. 완전 이진 트리에서는 중간에 빈자리가 없어 효율적이다. 루트를 인덱스 0에 저장할 때는 공식이 달라진다. 부모: (i - 1) // 2 , 단 루트 제외 왼쪽 자식: 2 * i + 1 오른쪽 자식: 2 * i + 2 2. 링크를 이용한 표현 각 노드에 데이터, 왼쪽 자식 참조, 오른쪽 자식 참조를 저장한다. class Node: def __init__(self, data, left=None, right=None): self.data = data self.left = left self.right = right data : 노드에 저장할 값 left : 왼쪽 자식 노드. 없으면 None right : 오른쪽 자식 노드. 없으면 None 장점: 실제로 존재하는 노드만 만들 수 있고, 연결 구조를 바꾸기 쉽다. 단점: 자식을 가리키는 참조를 저장할 공간이 추가로 필요하다. 04-3 이진 트리의 연산 이진 트리의 표준순회 순회(Traversal) 란 트리의 모든 노드를 정해진 순서에 따라 한 번씩 방문하는 것이다. 방문은 해당 노드의 데이터를 출력하거나 계산에 사용하는 등의 처리를 뜻한다. 표준 순회 세 가지는 모두 깊이 우선 탐색(DFS)에 해당한다. 순회 방식 방문 순서 루트 처리 시점 대표 활용 전위 순회(Preorder) 루트 → 왼쪽 → 오른쪽 가장 먼저 계층 구조 출력, 전위 수식 중위 순회(Inorder) 왼쪽 → 루트 → 오른쪽 가운데 이진 탐색 트리의 정렬 출력, 중위 수식 후위 순회(Postorder) 왼쪽 → 오른쪽 → 루트 가장 나중 수식 계산, 자식부터 처리하는 연산 순회 예시 다음과 같은 이진 트리를 가정한다. A: 왼쪽 B, 오른쪽 C B: 왼쪽 D, 오른쪽 E C: 왼쪽 F, 오른쪽 G D, E, F, G: 단말 노드 순회 결과 전위 A B D E C F G 중위 D B E A F C G 후위 D E B F G C A 재귀로 구현하기 def preorder(node): if node is not None: print(node.data, end=" ") preorder(node.left) preorder(node.right) def inorder(node): if node is not None: inorder(node.left) print(node.data, end=" ") inorder(node.right) def postorder(node): if node is not None: postorder(node.left) postorder(node.right) print(node.data, end=" ") node is None 이면 더 내려갈 노드가 없으므로 호출을 끝낸다. 세 함수는 현재 노드를 처리하는 print의 위치 만 다르다. 왼쪽과 오른쪽 서브트리에서도 같은 규칙을 반복한다. n개 노드를 모두 방문하므로 시간 복잡도는 O(n) 이다. 재귀 호출 스택의 추가 공간은 높이 h에 대해 O(h) 이다. 암기: 전위는 루트를 앞에, 중위는 가운데에, 후위는 뒤에 처리한다. 주의: 중위 순회의 결과가 정렬되는 것은 이진 탐색 트리 일 때이다. 일반 이진 트리에서는 정렬이 보장되지 않는다. 레벨 순회 레벨 순회(Level-order traversal) : 루트부터 시작하여 같은 레벨의 노드를 왼쪽에서 오른쪽으로 방문한다. 위에서 아래로 한 층씩 방문하는 너비 우선 탐색(BFS)이다. 먼저 발견한 노드를 먼저 처리하기 위해 큐(Queue) 를 사용한다. 동작 과정 루트를 큐에 넣는다. 큐의 맨 앞에서 노드를 꺼내 방문한다. 그 노드의 왼쪽 자식, 오른쪽 자식을 순서대로 큐에 넣는다. 큐가 빌 때까지 반복한다. 위 예시의 레벨 순회 결과: A B C D E F G from collections import deque def levelorder(root): if root is None: return queue = deque([root]) while queue: node = queue.popleft() print(node.data, end=" ") if node.left is not None: queue.append(node.left) if node.right is not None: queue.append(node.right) append() : 큐의 뒤에 넣는다. popleft() : 큐의 앞에서 꺼낸다. 시간 복잡도: O(n) 최대 레벨 너비를 w라고 할 때 큐의 추가 공간: O(w) , 최악의 경우 O(n) Python 리스트의 pop(0) 은 원소 이동이 필요하므로, 큐에는 deque 를 사용한다. 이진 트리의 연산들 1. 전체 노드 수 구하기 전체 노드 수 = 왼쪽 서브트리 노드 수 + 오른쪽 서브트리 노드 수 + 1 def count_nodes(node): if node is None: return 0 return count_nodes(node.left) + count_nodes(node.right) + 1 빈 트리에는 노드가 없으므로 0을 반환한다. 마지막의 + 1 은 현재 노드 자신을 센 것이다. 2. 단말 노드 수 구하기 def count_leaves(node): if node is None: return 0 if node.left is None and node.right is None: return 1 return count_leaves(node.left) + count_leaves(node.right) 왼쪽과 오른쪽 자식이 둘 다 없어야 단말 노드이다. 단말 노드를 만나면 1을 반환하고, 나머지는 양쪽 결과를 더한다. 3. 트리의 높이 구하기 높이 = max(왼쪽 높이, 오른쪽 높이) + 1 def height(node): if node is None: return 0 return max(height(node.left), height(node.right)) + 1 두 서브트리 중 더 깊은 쪽을 기준으로 한다. 현재 노드가 차지하는 한 층을 더한다. 예: 왼쪽 높이 2, 오른쪽 높이 4이면 현재 트리 높이는 5이다. 4. 특정 값 탐색하기 def find(node, target): if node is None: return None if node.data == target: return node result = find(node.left, target) if result is not None: return result return find(node.right, target) 현재 노드 → 왼쪽 → 오른쪽 순서로 찾는다. 찾으면 해당 노드, 찾지 못하면 None 을 반환한다. 일반 이진 트리에는 값의 배치 규칙이 없으므로 최악의 경우 모든 노드를 확인한다. 연산 시간 복잡도 재귀 추가 공간 전체 노드 수 O(n) O(h) 단말 노드 수 O(n) O(h) 높이 O(n) O(h) 값 탐색 최악 O(n) O(h) 삽입과 삭제는 트리의 목적에 따라 규칙이 다르다. 일반 이진 트리, 이진 탐색 트리, 힙에 동일한 삽입·삭제 규칙을 적용하지 않는다. 테스트 프로그램 앞에서 정의한 Node 클래스와 순회·연산 함수들 아래에 다음 코드를 붙여 실행한다. root = Node( "A", Node("B", Node("D"), Node("E")), Node("C", Node("F"), Node("G")) ) print("전위:", end=" ") preorder(root) print() print("중위:", end=" ") inorder(root) print() print("후위:", end=" ") postorder(root) print() print("레벨:", end=" ") levelorder(root) print() print("전체 노드 수:", count_nodes(root)) print("단말 노드 수:", count_leaves(root)) print("높이:", height(root)) result = find(root, "E") print("탐색 결과:", result.data if result is not None else "없음") 예상 출력: 전위: A B D E C F G 중위: D B E A F C G 후위: D E B F G C A 레벨: A B C D E F G 전체 노드 수: 7 단말 노드 수: 4 높이: 3 탐색 결과: E 추가 확인 기준: 빈 트리: 전체 노드 수 0, 단말 노드 수 0, 높이 0 루트만 있는 트리: 전체 노드 수 1, 단말 노드 수 1, 높이 1 노드 3개가 한 방향으로 이어진 트리: 전체 노드 수 3, 단말 노드 수 1, 높이 3 04-4 모스코드결정트리 결정 트리(Decision tree) : 조건이나 입력에 따라 가지를 선택하며 결과를 찾는 트리이다. 모스 코드는 점 . 과 선 - 의 조합으로 문자를 표현한다. 점과 선이라는 두 가지 입력을 이진 트리의 두 방향에 대응시킬 수 있다. 여기서는 점은 왼쪽, 선은 오른쪽 으로 이동하도록 정한다. 결정 트리를 이용한 모스 코드의 디코딩 모스 코드 트리의 구조 루트는 문자를 나타내지 않는 시작 지점이다. 루트에서 왼쪽으로 한 번 이동하면 E( . ), 오른쪽으로 한 번 이동하면 T( - )이다. 루트에서 특정 노드까지 이동한 경로가 그 노드의 모스 코드이다. 문자 모스 코드 루트에서의 이동 E . 왼쪽 T - 오른쪽 I .. 왼쪽 → 왼쪽 A .- 왼쪽 → 오른쪽 N -. 오른쪽 → 왼쪽 M -- 오른쪽 → 오른쪽 S ... 왼쪽 → 왼쪽 → 왼쪽 O --- 오른쪽 → 오른쪽 → 오른쪽 디코딩 과정 현재 위치를 루트로 설정한다. 입력 기호가 . 이면 왼쪽 자식으로 이동한다. 입력 기호가 - 이면 오른쪽 자식으로 이동한다. 한 문자의 코드가 끝나면 현재 노드에 저장된 문자를 읽는다. 다음 문자를 해독할 때는 다시 루트로 돌아간다. 예: ... --- ... ... → 왼쪽 세 번 → S --- → 오른쪽 세 번 → O ... → 왼쪽 세 번 → S 최종 결과: SOS 문자 경계가 중요한 이유 E의 코드 . 은 A의 코드 .- 의 앞부분이기도 하다. 따라서 문자가 저장된 노드에 도착했다고 즉시 한 글자를 확정하면 안 된다. 모스 코드 트리에서는 내부 노드에도 문자가 저장될 수 있다. 실제 신호에서는 시간 간격으로, 텍스트 예시에서는 공백으로 글자를 구분한다. 텍스트에서 단어 사이를 / 로 표시하기도 하지만, 이는 구분을 위한 표기 관례이다. def decode_letter(root, code): if not code: raise ValueError("빈 코드는 해독할 수 없습니다.") node = root for symbol in code: if node is None: raise ValueError("트리에 없는 코드입니다.") if symbol == ".": node = node.left elif symbol == "-": node = node.right else: raise ValueError("점과 선만 입력할 수 있습니다.") if node is None or node.data is None: raise ValueError("문자가 배정되지 않은 코드입니다.") return node.data 위 함수는 모스 코드 규칙에 맞는 트리가 이미 만들어져 있다고 가정한다. 코드 길이가 k이면 이동 횟수도 k이므로 시간 복잡도는 O(k) 이다. 트리 구축 비용을 제외하고, 총 m개 기호를 해독하는 시간은 O(m) 이다. 04-5 수식트리 수식 트리(Expression tree) : 연산자와 피연산자의 관계를 트리로 표현한 것이다. 여기서는 + , - , * , / 처럼 피연산자가 두 개인 이항 연산자를 사용한다. 단말 노드 에는 숫자나 변수 같은 피연산자를 저장한다. 내부 노드 에는 연산자를 저장한다. 왼쪽 서브트리는 왼쪽 피연산자, 오른쪽 서브트리는 오른쪽 피연산자를 나타낸다. 각 연산자 노드에 자식이 둘씩 있으므로, 이 조건의 수식 트리는 정 이진 트리이다. 예: (3 + 2) * (4 - 1) 루트: * 루트의 왼쪽 자식: + , 오른쪽 자식: - + 의 왼쪽·오른쪽 자식: 각각 3, 2 - 의 왼쪽·오른쪽 자식: 각각 4, 1 수식 트리의 계산 계산에는 후위 순회를 사용한다 연산자를 계산하려면 양쪽 피연산자의 값을 먼저 알아야 한다. 따라서 왼쪽 계산 → 오른쪽 계산 → 현재 연산자 적용 순서로 처리한다. 예: (3 + 2) * (4 - 1) 왼쪽 서브트리에서 3 + 2 = 5 를 계산한다. 오른쪽 서브트리에서 4 - 1 = 3 을 계산한다. 루트에서 5 * 3 = 15 를 계산한다. def evaluate(node): if node is None: raise ValueError("빈 수식입니다.") if node.left is None and node.right is None: return float(node.data) if node.left is None or node.right is None: raise ValueError("연산자의 피연산자가 부족합니다.") left_value = evaluate(node.left) right_value = evaluate(node.right) if node.data == "+": return left_value + right_value if node.data == "-": return left_value - right_value if node.data == "*": return left_value * right_value if node.data == "/": return left_value / right_value raise ValueError("지원하지 않는 연산자입니다.") 이 코드는 피연산자가 숫자인 수식을 계산한다. 변수까지 계산하려면 변수 이름에 대응하는 값을 별도로 제공해야 한다. 뺄셈과 나눗셈에
Meta Description AI가 개발 생산성을 60% 높여준다는 소문, 과연 사실일까요? 12만 명의 개발자를 분석한 스탠퍼드 연구의 충격적인 결과와 2026년 가트너가 주목한 'AI 네이티브 로우코드(Low-Code)' 플랫폼이 기업의 IT 백로그를 어떻게 해결하는지 시니어 전략가의 시선으로 파헤쳐 봅니다. 안녕하세요. 다년간 엔터프라이즈 환경에서 IT 전략과 디지털 트랜스포메이션을 이끌어온 시니어 콘텐츠 전략가입니다. 단도직입적으로 묻겠습니다. 최근 사내 개발팀에 AI 코딩 어시스턴트(GitHub Copilot, Cursor 등)를 도입하셨나요? 아마 "AI가 개발 생산성을 60% 이상 끌어올릴 것"이라는 장밋빛 전망을 기대하셨을 겁니다. 쏟아지는 업무 백로그를 AI가 마법처럼 해결해 줄 것이라 믿으셨겠죠. 하지만 현업 리더들의 진짜 속마음은 다릅니다. "생각보다 산출물이 늘지 않는데?", "버그 수정하느라 시간이 더 걸리는 것 같아." 여러분의 직감이 맞습니다. 오늘은 12만 명의 개발자를 추적한 스탠퍼드 대학의 충격적인 연구 결과를 바탕으로, AI 시대의 진짜 개발 생산성은 어디서 오는지, 그리고 가트너(Gartner)가 왜 '로우코드(Low-Code)' 를 2026년 기업 생존의 핵심 키워드로 지목했는지 깊이 있게 파헤쳐 보겠습니다. 1. 환상과 현실의 괴리: 스탠퍼드 연구가 밝힌 차가운 진실 업계에 떠도는 '생산성 60% 향상'이라는 수치는 대부분 통제된 환경의 소규모 '토이 프로젝트'에서 나온 결과입니다. 스탠퍼드 소프트웨어 엔지니어링 생산성 연구 그룹(SWEPR)이 600개 이상의 조직, 12만 명의 엔지니어를 대상으로 분석한 현실은 훨씬 겸허합니다. 진짜 생산성 향상은 10% 15% 수준: 대규모 운영 환경에서 AI 도구를 통한 생산성 향상의 중앙값은 10 15%에 불과했습니다. 심지어 도입 초기(30~100시간)에는 새로운 AI 도구의 특성을 학습하느라 오히려 업무 속도가 떨어지는 현상도 발견되었습니다. '고스트 엔지니어(Ghost Engineers)'의 등장: 놀랍게도 전체 엔지니어의 약 9%는 기대 산출물의 20% 이하만을 내는 고스트 엔지니어로 분류되었습니다. 이는 그들이 게을러서가 아닙니다. AI 도구가 도입되어도, 불투명한 원격 근무 환경과 피드백 부재가 겹치면 개발자는 동기를 잃고 표류하게 됩니다. 결국, 단순히 코드 자동 완성 도구를 쥐여준다고 해서 근본적인 IT 병목(Bottleneck) 현상이 해결되는 것은 아닙니다. '코드를 더 빨리 작성하는 것' 이 아니라, '작성해야 할 코드 자체를 줄이는 것' 이 진짜 생산성의 핵심입니다. 2. 가트너의 2026년 예언: 판을 뒤집을 '로우코드(Low-Code)' 이러한 한계를 극복하기 위해 글로벌 선도 기업들은 코딩의 추상화 단계, 즉 로우코드 플랫폼으로 눈을 돌리고 있습니다. 가트너(Gartner)의 전망에 따르면, 2026년까지 신규 엔터프라이즈 애플리케이션의 75%가 로우코드 기술로 구축될 것이라고 합니다. 2020년 25% 미만이었던 것을 생각하면 폭발적인 성장입니다. 시장 규모 역시 445억 달러(약 60조 원)에 달할 것으로 예상됩니다. 왜 이런 급격한 변화가 일어날까요? 극심한 개발자 부족: 미국에서만 120만 명의 개발자가 부족한 상황입니다. 섀도우 IT의 양성화: 현업 비즈니스 부서의 요구사항을 IT 부서가 모두 감당할 수 없습니다. 로우코드는 비개발자(시티즌 디벨로퍼)가 안전한 거버넌스 하에 직접 앱을 만들 수 있게 해줍니다. 철의 삼각형(Iron Triangle) 파괴: 전통적으로 소프트웨어 개발은 '속도, 비용, 품질' 중 하나를 포기해야 했습니다. 로우코드는 이 제약을 깨고 세 가지를 동시에 충족시킵니다. 3. 전통적 코딩 vs 로우코드: 압도적인 38배의 속도 차이 "로우코드는 장난감 수준의 간단한 앱만 만들 수 있는 것 아닐까?"라고 의심하실 수 있습니다. 하지만 Pique Solutions의 최근 Time and Motion 연구 데이터는 이러한 편견을 완벽히 부수어 버립니다. 데이터 중심의 인터랙티브 그리드(Interactive Grid)와 패싯 검색 모듈을 개발할 때, 전통적인 프레임워크인 ReactJS와 로우코드 플랫폼인 Oracle APEX를 비교한 결과는 가히 충격적입니다. 개발 속도: ReactJS로 3.5일(약 28.2시간)이 걸렸던 작업이 로우코드(Oracle APEX)로는 단 44분 만에 끝났습니다. 무려 38배 이상 빠른 속도입니다. 코드 양: ReactJS에서 평균 1,850줄 이상의 코드를 손으로 짜야 했다면, 로우코드는 단 93줄의 추가 코드만으로 완벽하게 동일한(오히려 보안이 더 튼튼한) 기능을 구현했습니다. 코드를 20배 적게 사용한 것입니다. 보안 패치, 데이터베이스 동시성 관리, 대규모 트래픽 대응 등 개발자들을 괴롭히던 아키텍처적 난제들을 플랫폼이 알아서(Out-of-the-box) 해결해 주니, 개발자는 '비즈니스 로직'과 '고객 경험'이라는 진짜 가치에만 집중할 수 있게 됩니다. 4. Agentic AI와 로우 코드의 결합: 다음 세대의 패러다임 여기서 끝이 아닙니다. 최근 로우코드 시장의 가장 뜨거운 화두는 바로 'AI 네이티브(AI-Native)' 와 '에이전틱 AI(Agentic AI)' 의 결합입니다. (OutSystems, Mendix, ToolJet 등 선도 기업들이 모두 이 방향으로 질주하고 있습니다.) 과거의 AI가 단순히 챗봇이나 코드를 추천해 주는 수준이었다면, 에이전틱 AI는 "결재 프로세스를 자동화하는 대시보드를 만들어줘" 라는 자연어 명령(Prompt) 하나만으로, 데이터베이스 모델링부터 UI 구성, 보안 권한 설정까지 스스로 판단하고 '실행'합니다. 이것은 혁명입니다. 인간과 AI가 협업하여 복잡한 레거시(Legacy) 시스템을 걷어내고, 아이디어를 단 며칠 만에 엔터프라이즈급 소프트웨어로 탄생시키는 마법 같은 시대가 열린 것입니다. 🚀 결론: 귀사의 다음 단계는 무엇입니까? AI 도구 하나 도입했다고 해서 안심할 때가 아닙니다. 진정한 '개발 생산성 60% 향상'의 마법은 개발자 개인의 손끝이 아니라, 조직의 개발 환경을 'AI 네이티브 로우코드' 중심으로 재편할 때 비로소 실현됩니다. 지금 우리 회사의 IT 백로그를 살펴보십시오. 수개월째 먼지만 쌓여가는 내부 운영 툴, 부서 간 데이터 연동 문제, 구형 레거시 시스템들이 보이지 않으신가요? 이제 결정하셔야 합니다. 계속해서 비싼 인건비를 들여 무거운 코드를 수작업으로 쌓아 올리며 기술 부채를 늘려갈 것인지, 아니면 로우코드 플랫폼을 통해 38배 빠른 혁신의 고속도로에 탑승할 것인지 말입니다.
Python_Day.5 클래스 상속과 super() , 오버라이딩, 자주 쓰는 내장 함수, 표준 모듈( time , random , pickle ), 모듈 직접 만들기. Day.4에서 클래스의 기본 틀( __init__ , self , 인스턴스 메소드)을 배웠다. 오늘은 그 위에 상속 을 얹고, 파이썬이 기본으로 제공하는 내장 함수와 모듈 , 그리고 내가 만든 파일을 모듈로 가져다 쓰는 방법 까지 정리했다. 1. 클래스 복습 class Car: def __init__(self, make, model, year): # 객체를 만들 때 자동으로 호출되는 생성자 self.make = make self.model = model self.year = year self.speed = 0 # 속성을 선언하면서 초기화 # 인스턴스 메소드는 첫 번째 매개변수가 항상 self def name(self): return str(self.year) + " " + self.make + " " + self.model def speed1(self): print(str(self.speed) + '이다') c1 = Car('테슬라', 'models', 2018) # 객체명 = 클래스명(__init__의 매개변수) print(c1.name()) # 2018 테슬라 models c1.speed = 15 c1.speed1() # 15이다 c1.name() 처럼 인스턴스를 통해 호출하면 파이썬이 c1 을 자동으로 self 에 넣어 준다. 그래서 호출할 때는 인자를 안 넘기는데 정의에는 self 가 있는 것이다. self 는 이름일 뿐이다 class Fruit: price = 20000 # 클래스 변수 def __init__(a, title, color): # self 대신 a를 써도 동작한다 a.title = title a.color = color def info(a): return "{} 과일은 {}색".format(a.title, a.color) def buy1(a, buy1): # buy1은 매개변수로 바로 쓰는 값이라 a.buy1처럼 속성에 붙일 필요가 없다 return '{} 과일 {}에서 사야지~'.format(a.title, buy1) a = Fruit('사과', '빨간') b = Fruit('포도', '검은') print(a.info()) # 사과 과일은 빨간색 print(b.info()) # 포도 과일은 검은색 print(a.buy1('코스트코')) # 사과 과일 코스트코에서 사야지~ print(b.buy1('이마트')) # 포도 과일 이마트에서 사야지~ 첫 번째 매개변수 이름이 꼭 self 일 필요는 없다. 위치 가 중요하고, 관례상 self 를 쓴다. 메소드 안에서만 쓰고 끝나는 값은 self. 를 붙여 속성으로 만들 필요 없이 매개변수 그대로 쓰면 된다. 파이썬의 모든 클래스는 최상위 클래스인 object 를 상속받는다. self가 없는 메소드 class A: def add(a, b): print(a + b) A.add(3, 4) # 7 (클래스 이름으로 호출) A().add(3, 4) # TypeError: A.add() takes 2 positional arguments but 3 were given A.add(3, 4) 는 일반 함수처럼 동작해서 7이 나온다. 반면 인스턴스로 호출하면 A() 가 첫 번째 인자( a )로 자동 전달되어, 인자가 3개가 되어 버리기 때문에 에러가 난다. 필기에는 이 형태를 "클래스 메소드(정적 메소드)"로 적어 두었는데, 정확하게는 아니다. 정적 메소드는 @staticmethod 를 붙여서 만드는 것 이고, 그러면 인스턴스로 호출해도 에러가 나지 않는다. 위처럼 데코레이터 없이 self 만 뺀 메소드는 "클래스 이름으로 부를 때만 우연히 동작하는" 형태다. class B: @staticmethod def add(a, b): print(a + b) B.add(3, 4) # 7 B().add(3, 4) # 7 (인스턴스로 호출해도 된다) 2. 클래스 안에 클래스 (객체를 속성으로 쓰기) 학생 객체를 만들고, 계산기 클래스가 학생들을 리스트로 갖고 있다가 평균을 구하는 예제다. class Student: def __init__(self, name, id, score=0): # 매개변수의 초기값(기본값) 설정 self.name = name self.id = id self.score = score def getId(self): return self.id def getName(self): return self.name def setScore(self, score): self.score = score def getScore(self): return self.score class Cal: def __init__(self): self.stu = [] # 학생 객체를 담을 리스트 def add(self, student): self.stu.append(student) def avg(self): total = 0 for i in self.stu: total += i.score # i.getScore()로 써도 같다 average = total / len(self.stu) return average, len(self.stu) # 값 두 개를 튜플로 반환 a = Student('Tom', 'qwe12') # score는 기본값 0 a.setScore(70) a2 = Student('Amy', 'NTE', 60) a3 = Student('Bob', 'B01', 90) a4 = Student('Cat', 'C01', 10) c = Cal() c.add(a) c.add(a2) c.add(a3) c.add(a4) p, s = c.avg() # 반환된 두 값을 각각 p, s에 받는다 print('{1}명의 평균은 {0}'.format(p, s)) # 4명의 평균은 57.5 score=0 처럼 기본값이 있는 매개변수 는 생략할 수 있다. a 는 점수를 안 넘겨서 0으로 만들어졌고, 나중에 setScore(70) 으로 바꿨다. return average, len(self.stu) 는 값을 두 개 돌려주는 것처럼 보이지만 사실 튜플 하나 를 반환한다. 받는 쪽에서 p, s = ... 로 언패킹했다. (Day.2의 패킹/언패킹) Cal 은 내부에 Student 객체들을 갖고 있다. 이렇게 객체를 다른 객체의 속성으로 담는 구조 는 이후에도 자주 나온다. 함수 안에서 sum 을 변수 이름으로 쓰면 내장 함수 sum() 을 가려 버린다. 위 코드에서는 total 로 바꿨다. 3. 상속 상속 은 부모 클래스의 속성과 메소드를 자식 클래스가 그대로 물려받는 것이다. class 자식클래스(부모클래스): ... 3-1. super() 로 부모의 __init__ 호출하기 class Person: def __init__(self): print('안녕') class Student(Person): def __init__(self): print('학생은 공부를 해야지') super().__init__() # 부모(Person)의 __init__을 호출 a = Student() # 학생은 공부를 해야지 # 안녕 자식 클래스에서 __init__ 을 다시 정의하면, 부모의 __init__ 은 자동으로 호출되지 않는다. 부모의 초기화가 필요하면 super().__init__() 으로 직접 불러야 한다. 필기에는 "덮어쓰기 되는 식"이라고 적어 뒀는데, 이게 맞는 이해다. 호출하지 않으면 어떻게 되는지 확인해 보자. class P2: def __init__(self): self.x = 1 class S2(P2): def __init__(self): # super().__init__()을 빼먹음 self.y = 2 s = S2() print(s.y) # 2 print(s.x) # AttributeError: 'S2' object has no attribute 'x' 부모의 __init__ 이 실행되지 않았으니 x 라는 속성이 만들어지지 않았다. 3-2. 부모의 __init__ 에 값 넘기기 class Camping(Car): # 위에서 만든 Car 상속 def __init__(self, make, model, year, bed): super().__init__(make, model, year) # 공통 속성은 부모에게 맡김 self.bed = bed # 자식만의 속성 추가 camp1 = Camping('벤츠', '스프린터', 2024, 2) print(camp1.name()) # 2024 벤츠 스프린터 (부모의 메소드를 그대로 사용) camp1.speed1() # 0이다 (speed도 부모가 초기화해 줬다) print(camp1.bed) # 2 c1 = Car('현대', '아반떼', 1998) print(c1.name()) # 1998 현대 아반떼 c1.speed = 60 c1.speed1() # 60이다 Camping 은 name() 과 speed1() 을 새로 만들지 않았는데도 쓸 수 있다. 부모에게 있는 것은 물려받고, 자식에만 필요한 bed 만 추가 한 것이다. 코드 중복이 줄어드는 게 상속의 장점이다. 3-3. 오버라이딩 (메소드 재정의) 부모에 있는 메소드를 자식이 같은 이름으로 다시 정의 하는 것을 오버라이딩이라고 한다. class Super: def a(self): print('super') class Sub(Super): def a(self): # 오버라이딩: 부모의 a()를 재정의 print('자식 클래스') def b(self): # 자식에만 있는 메소드 print('sub') a = Super() b = Sub() a.a() # super b.a() # 자식 클래스 (자식 버전이 우선) b.b() # sub a.b() # AttributeError: 'Super' object has no attribute 'b' 같은 이름의 메소드가 있으면 자식 쪽이 우선 한다. 상속은 한 방향이다. 자식은 부모의 것을 쓸 수 있지만, 부모는 자식에만 있는 메소드를 쓸 수 없다. 3-4. 오버라이딩 + super() 로 부모 기능 재활용 class Cal: def __init__(self, num1, num2): self.num1 = num1 self.num2 = num2 def div(self): return self.num1 / self.num2 class NewCal(Cal): def div(self): if self.num2 == 0: return 0 # 0으로 나누는 경우만 새로 처리 else: return super().div() # 나머지는 부모의 div()를 그대로 사용 a = NewCal(6, 0) b = NewCal(6, 3) print(a.div(), b.div()) # 0 2.0 else 에서 부모의 계산식을 다시 쓰지 않고 super().div() 로 가져왔다. 부모 기능은 그대로 쓰고, 예외 상황만 자식에서 덧붙이는 방식이다. 필기 코드의 부모 div() 는 num1 + num2 를 반환하고 있었다. 이름이 div 니까 나누기( / )로 고쳤다. 나누기로 고쳐야 num2 == 0 을 따로 막는 이유( ZeroDivisionError )도 설명이 된다. 4. 클래스 문제 풀이 Q1. 클래스를 상속받고 메소드 추가하기 Calculator 를 상속하는 UpgradeCal 을 만들고, 값을 빼는 minus 메소드를 추가한다. class Calculator: def __init__(self): self.value = 0 def add(self, val): self.value += val class UpgradeCal(Calculator): def minus(self, val): self.value -= val cal = UpgradeCal() cal.add(10) cal.minus(7) print(cal.value) # 3 Q2. 값이 100을 넘지 않는 계산기 Calculator 를 상속해서, add 로 값을 더할 때 100이 넘으면 100으로 고정 하는 MaxLimitCal 을 만든다. 필기에서는 이 문제를 풀다가 add 의 첫 매개변수를 self 가 아닌 n 이나 val 로 쓰고, 메소드 안에서 value = 0 처럼 지역 변수를 만들어 버려서 결과가 이상했다. 포인트는 두 가지다. 첫 매개변수가 꼭 self 여야 하는 것은 아니지만, 인스턴스가 들어오는 자리 이므로 인스턴스 속성에 접근하려면 그 이름으로 n.value 처럼 써야 한다. 매개변수 없이 def add(val): 만 쓰면 인스턴스가 들어올 자리가 없다. 이 상태에서 value = 0 을 쓰면 인스턴스 속성이 아니라 함수 안에서만 쓰이는 지역 변수 가 된다. 그래서 self.value 를 쓰는 형태로 고치면 이렇게 된다. class MaxLimitCal(Calculator): def add(self, val): super().add(val) # 부모의 add로 먼저 더하고 if self.value >= 100: # 100 이상이면 self.value = 100 # 100으로 고정 m = MaxLimitCal() m.add(50) m.add(70) print(m.value) # 100 Q6. 리스트 항목마다 3을 곱해서 반환하기 a = [1, 2, 3, 4] print(list(map(lambda x: x * 3, a))) # [3, 6, 9, 12] 5. 내장 함수 Day.4에 이어 자주 쓰는 내장 함수를 정리했다. print(abs(-3)) # 3 (절댓값) all , any iterable의 요소를 검사해서 참/거짓을 돌려준다. print(all([1, 2, 3])) # True 모두 참 print(all([1, 2, 0])) # False 0이 하나 있으니까 print(any([1, 2, 0])) # True 하나라도 참이면 print(any([0, 0])) # False 전부 거짓일 때만 함수 True가 되는 조건 all 모든 요소가 참 any 하나라도 참 조건식과 조합하면 더 유용하다. a = [1, 60, 4, 10, 50] print(all(i < 20 for i in a)) # False (60, 50이 20 이상) print(any(i > 30 for i in a)) # True (60, 50이 30 초과) 필기에는 any([1,2,0]) 이 True인 이유를 "하나라도 False(1,2)이기 때문"이라고 적어 뒀는데, 정확히는 하나라도 참(1, 2)이 있기 때문 이다. 참고로 빈 리스트는 all([]) 이 True , any([]) 가 False 다. chr , ord print(chr(65)) # A (아스키 코드 → 문자) print(ord('A')) # 65 (문자 → 아스키 코드) enumerate 인덱스와 요소를 함께 돌려준다. for i, name in enumerate(['body', 'foo', 'bar']): print(i, name) # 0 body # 1 foo # 2 bar 필기에서는 {'body','foo','bar'} 라는 set 에 enumerate 를 썼는데, set은 순서가 없어서 매번 출력 순서가 달라질 수 있다. 인덱스가 의미가 있으려면 리스트처럼 순서가 있는 자료형에 쓰는 게 좋다. filter , map a = [1, 60, 4, 10, 50] def num(x): return abs(x) > 3 print(list(filter(num, a))) # [60, 4, 10, 50] print(list(filter(lambda x: x > 3, a))) # [60, 4, 10, 50] print(list(map(lambda x: x + 2, range(1, 7)))) # [3, 4, 5, 6, 7, 8] filter(함수, iterable) : 함수가 참 을 돌려주는 요소만 추출한다. map(함수, iterable) : 모든 요소 에 함수를 적용한 결과를 돌려준다. 반복 가능한 객체는 리스트, 튜플, 문자열, 딕셔너리, range , 파일 객체 등이다. 결과를 보려면 list() 로 감싸야 한다. (Day.4에서 정리한 대로 map / filter 는 지연 평가라서 그대로 출력하면 객체 주소만 나온다.) id , len , type print(id(10)) # 객체의 주소값 (실행 환경마다 다름) print(len('파이썬')) # 3 print(len([1, 2, 3, 4, 5])) # 5 print(type(3)) # <class 'int'> print(type({})) # <class 'dict'> print(type(())) # <class 'tuple'> print(type([])) # <class 'list'> range print(range(5)) # range(0, 5) (리스트가 아니라 range 객체) print(range(1, 10, 2)) # range(1, 10, 2) print(list(range(1, 10, 2))) # [1, 3, 5, 7, 9] (시작, 끝(미포함), 증감) # 9부터 0까지 1씩 감소 print(list(range(9, -1, -1))) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] max , min print(max([1, 2, 3])) # 3 print(max('ABCD')) # D (사전순으로 가장 뒤) print(min('ABCD')) # A (사전순으로 가장 앞) print(max(1, 2, 3)) # 3 (리스트 없이 값을 바로 나열해도 된다) round print(round(4.6)) # 5 print(round(3.6432, 2)) # 3.64 (소수점 둘째 자리까지) 주의할 점이 있다. 파이썬의 round 는 우리가 학교에서 배운 사사오입(반올림)이 아니라 가장 가까운 짝수로 보내는 방식 이다. print(round(2.5)) # 2 print(round(3.5)) # 4 print(round(0.5)) # 0 딱 .5인 경우에는 결과가 짝수 쪽으로 가기 때문에, 평균 점수처럼 정확한 반올림이 필요한 곳에서는 헷갈릴 수 있다. sorted , sum , zip print(sorted([5, 4, 3, 2, 1, 6])) # [1, 2, 3, 4, 5, 6] print(sorted([5, 4, 3, 2, 1, 6], reverse=True)) # [6, 5, 4, 3, 2, 1] print(sorted(['p', 'y', 't', 'h', 'o', 'n'])) # ['h', 'n', 'o', 'p', 't', 'y'] print(sum([1, 2, 3, 4, 5])) # 15 print(sum(range(1, 11))) # 55 print(list(zip([10, 20, 30], [40, 50, 60]))) # [(10, 40), (20, 50), (30, 60)] (같은 인덱스끼리 튜플로 묶는다) sorted 는 새 리스트를 반환 하고 원본은 바뀌지 않는다. 필기에서는 list(reversed(sorted(...))) 로 내림차순을 만들었는데, reverse=True 옵션을 쓰면 더 간단하다. zip 은 여러 iterable의 같은 위치 요소를 묶어 주는 함수다. 6. 표준 모듈: time , random , pickle 내장 함수와 달리 import 로 불러와서 쓰는 모듈들이다. time import time print(time.time()) # 1970-01-01부터 지금까지의 초 (실수) print(time.localtime(time.time())) # 연/월/일/시/분/초 등을 담은 구조체 print(time.ctime()) # 사람이 읽는 형태의 현재 시간 문자열 # 가장 많이 쓰는 형식 print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))) # 예) 2026-09-11 17:30:05 코드 의미 %Y 연도(4자리) %m 월 %d 일 %H 시(24시간) %M 분 %S 초 random import random print(random.random()) # 0.0 이상 1.0 미만의 실수 print(random.randint(1, 45)) # 1 이상 45 이하의 정수 (둘 다 포함) 필기에는 random.random() 이 "1.0까지"라고 되어 있는데, 1.0은 포함하지 않는다. randint(1, 45) 는 두 번째 인자도 포함 이다. 필기에 "미만이 아니라 포함임!"이라고 적어 둔 부분이 이 점이다. d =
Python_Day.6 패키지와 import, 예외 처리( try / except / else / finally , raise , 사용자 정의 예외), 클로저, 데코레이터(함수형/클래스형). Day.5에서 파일 하나를 모듈로 만들어 import 하는 것까지 했다. 오늘은 그걸 폴더 단위(패키지) 로 확장하는 것에서 시작해서, 프로그램이 죽지 않게 하는 예외 처리 , 그리고 함수를 값처럼 다루는 클로저와 데코레이터 까지 정리했다. 1. 패키지 파이썬 프로그램은 패키지로 나뉜 개별 모듈 들로 구성된다. 패키지는 모듈( .py )을 담은 폴더 이고, 폴더 안에 폴더(하위 패키지)를 둘 수 있다. 실습에서는 이런 구조를 만들었다. (폴더 구조 스크린샷은 Notion에서 읽을 수 없어서, 코드에 맞춰 구조를 다시 적었다.) project/ ├── main.py └── pro_test/ # 패키지 ├── __init__.py ├── test2/ # 하위 패키지 │ ├── __init__.py │ └── module1.py # mod1_test(), mod1_test2() └── test3/ # 하위 패키지 ├── __init__.py └── module2.py # mod2_test(), mod2_test2() 각 폴더의 __init__.py 는 "이 폴더는 패키지다"라는 표시다. (파이썬 3.3부터는 없어도 동작하지만, 패키지임을 분명히 하고 초기화 코드를 넣을 수 있어서 두는 편이 일반적이다.) import 하는 세 가지 방법 1) import 패키지.하위패키지.모듈 : 호출할 때도 전체 경로를 다 써야 한다. import pro_test.test2.module1 import pro_test.test3.module2 pro_test.test2.module1.mod1_test() pro_test.test3.module2.mod2_test() 2) from 패키지.하위패키지 import 모듈 : 모듈 이름만 쓰면 된다. as 로 별명도 붙일 수 있다. from pro_test.test2 import module1 module1.mod1_test() from pro_test.test3 import module2 as m2 m2.mod2_test() 3) from 패키지.하위패키지 import * : 패키지 안의 것을 한꺼번에 가져온다. from pro_test.test2 import * from pro_test.test3 import * module1.mod1_test() module2.mod2_test() 3번은 주의가 필요하다. 필기 코드에서는 위에서 이미 module1 을 import 해 둔 상태라 동작했다. import * 만 단독으로 쓰면 NameError: name 'module1' is not defined 가 난다. 패키지에서 * 로 가져올 모듈은 __init__.py 에 __all__ = ['module1'] 처럼 목록을 적어 줘야 한다. 이름 충돌 위험도 있어서 1번이나 2번을 권장한다. 상대 경로 import 필기 맨 위에 .. (부모 디렉토리), . (현재 디렉토리)라고 적어 둔 것이 상대 경로 import 표기다. 패키지 안의 모듈끼리 서로 가져올 때 쓴다. # pro_test/test3/module2.py from ..test2 import module1 # .. : 부모(pro_test) 아래의 test2 from . import helper # . : 현재 패키지(test3) def mod2_test2(): module1.mod1_test() helper.hello() . 은 현재 패키지, .. 은 한 단계 위 패키지를 뜻한다. 상대 경로 import는 패키지의 일부로 import 될 때만 동작한다. 그 파일을 python module2.py 처럼 직접 실행하면 에러가 난다. 직접 실행하는 파일은 앞 장에서 본 __name__ == "__main__" 이 되고, 패키지 소속이 아니기 때문이다. 2. 예외 처리 2-1. 자주 만나는 예외 에러가 나면 파이썬은 종류에 맞는 예외(Exception) 를 던진다. 각각 어떤 코드에서 나는지 실제로 확인해 보았다. 예외 코드 메시지 SyntaxError print('hi')) unmatched ')' NameError print(z) (정의 안 한 변수) name 'z' is not defined IndexError a=[1,2,3]; a[5] list index out of range ZeroDivisionError 4/0 division by zero KeyError {'a':1}['c'] 'c' ValueError [1,2,3].remove(5) , int('abc') list.remove(x): x not in list FileNotFoundError open('b.txt','r') No such file or directory: 'b.txt' TypeError [1,2] + 'python' can only concatenate list (not "str") to list TypeError 는 x + list(y) 처럼 자료형을 맞춰 주면 해결된다. SyntaxError 는 실행하기도 전에 코드를 읽는 단계에서 나기 때문에, 이 오류가 있는 줄 위쪽의 print 도 실행되지 않는다. 2-2. try / except / else / finally 예외가 나더라도 프로그램이 멈추지 않게 처리하는 문법이다. li = ['db', 'python', 'react'] try: # 예외가 날 수도 있는 코드 x = 'java' y = li.index(x) # 예외가 나면 바로 except로 넘어간다 print('try 블럭 수행') except Exception as e: # 예외가 났을 때 실행 print(e) # 'java' is not in list else: # 예외가 없을 때만 실행 print('else') finally: # 예외 여부와 상관없이 항상 실행 print('finally') 실행 결과는 이렇다. 'java' is not in list finally x 를 'db' 로 바꾸면 이렇게 된다. try 블럭 수행 else finally 블록 실행 시점 try 예외가 날 수 있는 코드. 예외가 나는 순간 나머지는 건너뛴다 except 예외가 발생했을 때 else 예외가 발생하지 않았을 때 finally 무조건 (파일 닫기 같은 마무리 작업에 쓴다) except ValueError: 처럼 특정 예외만 잡을 수도 있고, except Exception as e: 로 대부분의 예외 를 잡을 수도 있다. Exception 은 거의 모든 예외의 부모 클래스다. e 에 예외 내용이 들어 있어서 print(e) 로 메시지를 볼 수 있다. 필기의 앞부분 코드는 except ValueError: 로 잡고 있었다. list.index() 로 없는 값을 찾으면 ValueError 가 나기 때문이다. 예외끼리도 상속 관계가 있다. KeyError 와 IndexError 는 LookupError 의 자식이고, FileNotFoundError 는 OSError 의 자식이며, 전부 Exception 의 자손이다. 2-3. raise : 일부러 예외 발생시키기 def calc(msg, *args): result = 0 try: if msg == 'add': for i in args: result += i print(result) elif msg == 'mul': result = 1 for i in args: result *= i print(result) elif msg == 'avg': for i in args: result += i print(result / len(args)) else: raise Exception('잘못된 연산이다') # 일부러 예외 발생 except Exception as e: print(e) calc("add", 1, 2, 3) # 6 calc("mul", 1, 2, 3, 4) # 24 calc("avg", 10, 20, 30) # 20.0 calc("al", 10, 20, 30) # 잘못된 연산이다 calc("avg") # division by zero (인자가 없으면 len(args)가 0) raise 는 조건에 맞지 않을 때 직접 예외를 던지는 명령어다. 던져진 예외는 바깥의 except 가 받는다. *args 는 인자를 튜플로 받는다. (Day.3) 필기 코드에는 except 안에 return None 이 있었는데, 파이썬 함수는 return 이 없으면 어차피 None 을 반환하므로 빼도 같다. 마지막 줄처럼 인자 없이 avg 를 호출하면 ZeroDivisionError 가 나는데, except Exception 이 이것도 같이 잡아 준다. 2-4. 사용자 정의 예외 Exception 을 상속해서 내 예외 클래스 를 만들 수 있다. class MyError(Exception): def __str__(self): # 예외를 출력할 때의 문자열을 정한다 return '허용되지 않는 별명입니다.' def say_nick(nick): if nick == '바보': raise MyError() # 금지어면 예외 발생 print(nick) try: say_nick('천사') # 천사 say_nick('바보') # 여기서 예외 발생 except MyError as e: print(e) # 허용되지 않는 별명입니다. __str__ 은 print(객체) 를 했을 때 보일 문자열을 정하는 특수 메소드 다. 예외 객체 e 를 print(e) 하면 이 문자열이 나온다. 2-5. 사용자 정의 예외에서 헷갈린 부분 나이를 입력받아 0 이하면 예외를 내는 코드를 두 가지로 만들어 봤다. 첫 번째는 이렇다. class MyError(Exception): def __str__(self): return '숫자를 입력해주세요' try: age = int(input('나이를 입력해주세요')) if age <= 0: raise MyError('나이는 0 이상이어야 해') except Exception: raise MyError() else: print('입력완료') 이 코드는 -5 를 입력하든 abc 를 입력하든 둘 다 같은 숫자를 입력해주세요 에러 로 끝난다. 이유는 세 가지다. raise MyError('나이는 0 이상이어야 해') 로 넘긴 메시지는 쓰이지 않는다. __str__ 을 오버라이딩해서 항상 같은 문자열을 반환하기 때문이다. (메시지는 e.args 에는 들어 있지만 출력되지 않는다.) MyError 도 Exception 의 자식이라서, try 안에서 던진 MyError 를 바로 아래 except Exception 이 다시 잡고 , raise MyError() 로 새로 던진다. 결국 -5 를 입력해도 "숫자를 입력해주세요"가 나온다. except 안에서 다시 raise 했으니 처리되지 않은 예외가 되어 프로그램이 트레이스백과 함께 종료 되고, else 도 실행되지 않는다. 두 번째 방식은 메시지를 __init__ 에서 부모에게 넘긴다. class MyError(Exception): def __init__(self): super().__init__('0보다 커야해') # 부모(Exception)에 메시지 전달 try: age = int(input('나이를 입력해주세요')) if age <= 0: raise MyError() except ValueError as e: print('숫자가 아니야') else: print('입력완료') 이쪽은 abc 를 입력하면 숫자가 아니야 가 잘 나온다. 그런데 -5 를 입력하면 except ValueError 는 MyError 를 잡지 못하므로 MyError: 0보다 커야해 와 함께 프로그램이 종료된다. MyError 를 처리하는 except 가 없기 때문이다. 두 가지를 모두 처리하도록 정리하면 이렇게 된다. class AgeError(Exception): def __init__(self): super().__init__('나이는 0보다 커야 한다') try: age = int(input('나이를 입력해주세요')) if age <= 0: raise AgeError() except ValueError: print('숫자가 아니다') # abc 입력 except AgeError as e: print(e) # -5 입력 → 나이는 0보다 커야 한다 else: print('입력완료') # 5 입력 입력 출력 abc 숫자가 아니다 -5 나이는 0보다 커야 한다 5 입력완료 예외 종류마다 except 를 나눠 쓰면 상황별로 다른 메시지 를 줄 수 있다. 또 except Exception 을 쓸 때는 그 안에서 내가 던진 예외까지 같이 잡히는지 확인해야 한다. 사용자 정의 예외에서 메시지를 정하는 방법은 두 가지다. 방법 코드 __str__ 오버라이딩 출력 문자열을 고정한다. 던질 때 넘긴 메시지는 무시된다 __init__ 에서 super().__init__(메시지) 부모에게 메시지를 넘긴다 3. 클로저 클로저 는 함수 안에서 함수를 정의하고, 그 안쪽 함수를 결과로 반환 하는 구조다. 콜백 함수나 데코레이터에 쓰인다. def add(n1): def wrapper(n): # 내부 함수 (inner function) return n1 + n # 바깥 함수의 n1을 사용 return wrapper # 내부 함수를 반환 a1 = add(10) # n1=10을 저장한 채로 wrapper가 a1에 들어감 print(a1(10)) # 20 (wrapper(10) → 기억해 둔 10 + 10) a2 = add(20) # n1=20을 기억하는 또 다른 wrapper print(a2(10)) # 30 흐름을 풀어 보면 이렇다. add(10) 이 실행되면 n1 = 10 이 만들어진다. 안쪽 wrapper 함수가 만들어져서 반환 되고, a1 이 그 함수를 가리킨다. add 는 이미 끝났지만, wrapper 는 n1 = 10 을 기억하고 있다. 이것이 클로저의 특징이다. a1(10) 은 wrapper(10) 이므로 기억하던 10과 더해 20이 된다. add(10) 과 add(20) 이 서로 다른 n1 을 각자 기억하는 함수를 만든다는 점이 핵심이다. 기억해 둔 값은 a1.__closure__[0].cell_contents 로 확인할 수 있고, 값은 10 이다. 기억한 값을 바꾸고 싶을 때: nonlocal 내부 함수에서 바깥 함수의 변수를 읽는 것은 되지만, 값을 바꾸려면 nonlocal 을 선언 해야 한다. (Day.4의 global 과 같은 역할이고 범위가 바깥 함수다.) def counter(): c = 0 def inc(): nonlocal c # 바깥 함수의 c를 쓰겠다 c += 1 return c return inc f = counter() print(f(), f(), f()) # 1 2 3 호출할 때마다 c 가 유지되며 늘어난다. 전역 변수를 쓰지 않고도 상태를 함수에 숨겨 두는 방법이다. 4. 데코레이터 데코레이터 는 "장식하는 도구"로, 기존 함수의 코드를 건드리지 않고 기능을 확장 할 때 쓴다. 함수 위에 @이름 을 붙이는 문법이고, 안에서 클로저를 사용한다. 4-1. 클로저로 직접 만들어 보기 def trace(func): def wrapper(): print('시작') func() # func는 함수 자체를 받았으므로 뒤에 ()를 붙여 호출 print('끝') return wrapper # 함수를 반환 def hi(): print('hi') def hello(): print('hello') t1 = trace(hi) t1() # 시작 # hi # 끝 t2 = trace(hello) t2() # 시작 # hello # 끝 trace 는 함수를 인자로 받고, 함수를 반환 한다. 반환된 wrapper 가 원래 함수 앞뒤에 시작 / 끝 출력을 덧붙인다. 4-2. @ 문법 위의 t1 = trace(hi) 과정을 @ 로 줄일 수 있다. @trace # hi = trace(hi) 와 같다 def hi(): print('hi') @trace # hello = trace(hello) 와 같다 def hello(): print('hello') hi() # 시작 # hi # 끝 hello() # 시작 # hello # 끝 필기에는 " hi() 를 호출하면 hi = trace(hi) 를 자동으로 실행해준다"고 적어 뒀는데, 정확히는 함수를 정의하는 시점 에 hi = trace(hi) 가 한 번 실행된다. 그 뒤로 hi 라는 이름은 원래 함수가 아니라 wrapper 를 가리킨다. 그래서 hi() 를 호출하면 wrapper() 가 실행되는 것이다. 4-3. 인자와 반환값이 있는 함수에 쓰기 인자의 개수나 형태가 제각각인 함수에도 쓰려면 *args , **kwargs 로 받는다. def trace(func): def wrapper(*args, **kwargs): # *args : 위치 인자를 튜플로 받는다 # **kwargs: 키=값 인자를 딕셔너리로 받는다 result = func(*args, **kwargs) print(args, kwargs, result) return result # 원래 함수의 반환값을 그대로 돌려준다 return wrapper @trace def big(*args): return max(args) @trace def mina(**kwargs): return min(kwargs.values()) print(big(10, 20)) # (10, 20) {} 20 # 20 print(mina(x=20, y=30, z=40)) # () {'x': 20, 'y': 30, 'z': 40} 20 # 20 흐름은 이렇다. @trace 가 big = trace(big) 를 실행해서 big 이 wrapper 가 된다. big(10, 20) 은 wrapper(10, 20) 이 되어 args = (10, 20) 로 들어온다. func(*args, **kwargs) 로 원래 big 을 호출해 max(10, 20) 인 20을 얻는다. 인자와 결과를 출력하고, result 를 반환해서 호출한 곳에서도 20을 받는다. wrapper 에서 return result 를 빼면 원래 함수의 반환값이 사라져서 print(big(10, 20)) 이 None 이 된다. 4-4. 데코레이터의 부작용과 functools.wraps 데코레이터를 붙인 함수는 이름이 바뀐다. @trace def hi(): print('hi') print(hi.__name__) # wrapper (hi가 아니다) hi 가 실제로는 wrapper 이기 때문이다. 디버깅하거나 함수 이름을 쓸 때 헷갈릴 수 있어서, 실무에서는 functools.wraps 를 붙여 원래 함수의 이름과 설명을 유지한다. import functools def trace(func): @functools.wraps(func) # func의 이름 등 정보를 wrapper에 복사 def wrapper(*args, **kwargs): result = func(*args, **kwargs) print(args, kwargs, result) return result return wrapper @trace def big(*args): return max(args) print(big.__name__) # big 4-5. 클래스형 데코레이터와 __call__ 데코레이터는 함수만이 아니라 클래스 로도 만들 수 있다. class Tr: def __init__(self, func): self.func = func def __call__(self): print(self.func.__name__, '시작') self.func() print(self.func.__name__, '끝') @Tr # hi = Tr(hi) def hi(): print('hi') hi() # hi 시작 # hi # hi 끝 동작 순서를 따라가 보면 이렇다. @Tr 이 함수를 정의하는 시점에 hi = Tr(hi) 를 실행한다. Tr.__init__(self, func) 가 호출되고 원래 hi 함수가 self.func 에 저장된다. 이제 hi 라는 이름은 함수가 아니라 Tr 의 인스턴스 다. type(hi) 를 찍으면 <class '__main__.Tr'> 가 나온다. hi() 처럼 인스턴스에 괄호를 붙여 호출하면, 파이썬이 __call__ 을 자동으로 호출 한다. __call__ 안에서 self.func.__name__ 으로 이름( hi